public inbox for isar-users@googlegroups.com
 help / color / mirror / Atom feed
From: Henning Schild <henning.schild@siemens.com>
To: Cedric Hombourger <Cedric_Hombourger@mentor.com>
Cc: <isar-users@googlegroups.com>
Subject: Re: [PATCH 1/1] bitbake: update to version 1.44.0
Date: Tue, 22 Oct 2019 09:36:09 +0200	[thread overview]
Message-ID: <20191022093609.5ed343a5@md1za8fc.ad001.siemens.net> (raw)
In-Reply-To: <1571667598-657-2-git-send-email-Cedric_Hombourger@mentor.com>

Hi Cedric,

i would suggest a split between the actual bump and the following
Isar-changes. But i do not care too much and both ways have their pros
and cons.

Henning

Am Mon, 21 Oct 2019 16:19:58 +0200
schrieb Cedric Hombourger <Cedric_Hombourger@mentor.com>:

> Found that the gitsm fetcher we ship is rather out-dated compared to
> upstream and was failing to pull git sub-modules from repositories
> such as azure-umqtt-c.
> 
> It shall be noted that bitbake has renamed its multiconfig targets:
> the "multiconfig:" prefix was changed to "mc:". Updated our scripts
> as well as documentation.
> 
>     Origin: https://github.com/openembedded/bitbake.git
>     Commit 5d83d828cacb58ccb7c464e799c85fd2d2a50ccc (tag: 1.44.0)
>     Author: Richard Purdie <richard.purdie@linuxfoundation.org>
>     Date:   Wed Oct 9 14:10:21 2019 +0100
> 
>     bitbake: Update to version 1.44.0
> 
>     Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
> 
> Signed-off-by: Cedric Hombourger <Cedric_Hombourger@mentor.com>
> ---
>  RECIPE-API-CHANGELOG.md                       |   16 +
>  bitbake/.gitattributes                        |    2 +
>  bitbake/HEADER                                |   19 -
>  bitbake/LICENSE                               |   12 +-
>  bitbake/{COPYING => LICENSE.GPL-2.0-only}     |   61 +-
>  bitbake/LICENSE.MIT                           |   25 +
>  bitbake/MANIFEST.in                           |    5 +-
>  bitbake/bin/bitbake                           |   16 +-
>  bitbake/bin/bitbake-diffsigs                  |  155 +-
>  bitbake/bin/bitbake-dumpsig                   |   95 +-
>  bitbake/bin/bitbake-hashclient                |  170 ++
>  bitbake/bin/bitbake-hashserv                  |   62 +
>  bitbake/bin/bitbake-layers                    |   12 +-
>  bitbake/bin/bitbake-prserv                    |    4 +
>  bitbake/bin/bitbake-selftest                  |   16 +-
>  bitbake/bin/bitbake-worker                    |   19 +-
>  bitbake/bin/bitdoc                            |   14 +-
>  bitbake/bin/git-make-shallow                  |    4 +
>  bitbake/bin/toaster                           |   12 +-
>  bitbake/bin/toaster-eventreplay               |   17 +-
>  bitbake/classes/base.bbclass                  |    2 +-
>  bitbake/contrib/dump_cache.py                 |    2 -
>  .../bitbake-user-manual-execution.xml         |   78 +-
>  .../bitbake-user-manual-fetching.xml          |   69 +-
>  .../bitbake-user-manual-hello.xml             |   28 +-
>  .../bitbake-user-manual-intro.xml             |   18 +-
>  .../bitbake-user-manual-metadata.xml          |  179 +-
>  .../bitbake-user-manual-ref-variables.xml     |  422 ++---
>  bitbake/doc/poky.ent                          |    8 -
>  bitbake/lib/bb/COW.py                         |   15 -
>  bitbake/lib/bb/__init__.py                    |   16 +-
>  bitbake/lib/bb/build.py                       |  151 +-
>  bitbake/lib/bb/cache.py                       |  109 +-
>  bitbake/lib/bb/cache_extra.py                 |   14 +-
>  bitbake/lib/bb/checksum.py                    |   12 +-
>  bitbake/lib/bb/codeparser.py                  |   13 +-
>  bitbake/lib/bb/command.py                     |   12 +-
>  bitbake/lib/bb/compat.py                      |    4 +
>  bitbake/lib/bb/cooker.py                      |  218 +--
>  bitbake/lib/bb/cookerdata.py                  |   44 +-
>  bitbake/lib/bb/daemonize.py                   |    4 +
>  bitbake/lib/bb/data.py                        |   23 +-
>  bitbake/lib/bb/data_smart.py                  |   25 +-
>  bitbake/lib/bb/event.py                       |   33 +-
>  bitbake/lib/bb/exceptions.py                  |    3 +
>  bitbake/lib/bb/fetch2/__init__.py             |   55 +-
>  bitbake/lib/bb/fetch2/bzr.py                  |   12 +-
>  bitbake/lib/bb/fetch2/clearcase.py            |   19 +-
>  bitbake/lib/bb/fetch2/cvs.py                  |   17 +-
>  bitbake/lib/bb/fetch2/git.py                  |   68 +-
>  bitbake/lib/bb/fetch2/gitannex.py             |   14 +-
>  bitbake/lib/bb/fetch2/gitsm.py                |  281 ++-
>  bitbake/lib/bb/fetch2/hg.py                   |   18 +-
>  bitbake/lib/bb/fetch2/local.py                |   16 +-
>  bitbake/lib/bb/fetch2/npm.py                  |   69 +-
>  bitbake/lib/bb/fetch2/osc.py                  |    5 +-
>  bitbake/lib/bb/fetch2/perforce.py             |   15 +-
>  bitbake/lib/bb/fetch2/repo.py                 |   16 +-
>  bitbake/lib/bb/fetch2/s3.py                   |   15 +-
>  bitbake/lib/bb/fetch2/sftp.py                 |   15 +-
>  bitbake/lib/bb/fetch2/ssh.py                  |   14 +-
>  bitbake/lib/bb/fetch2/svn.py                  |   98 +-
>  bitbake/lib/bb/fetch2/wget.py                 |  104 +-
>  bitbake/lib/bb/main.py                        |   41 +-
>  bitbake/lib/bb/methodpool.py                  |   15 +-
>  bitbake/lib/bb/monitordisk.py                 |   27 +-
>  bitbake/lib/bb/msg.py                         |   14 +-
>  bitbake/lib/bb/namedtuple_with_abc.py         |    4 +-
>  bitbake/lib/bb/parse/__init__.py              |   14 +-
>  bitbake/lib/bb/parse/ast.py                   |   15 +-
>  bitbake/lib/bb/parse/parse_py/BBHandler.py    |   59 +-
>  bitbake/lib/bb/parse/parse_py/ConfHandler.py  |   17 +-
>  bitbake/lib/bb/parse/parse_py/__init__.py     |   17 +-
>  bitbake/lib/bb/persist_data.py                |  234 ++-
>  bitbake/lib/bb/process.py                     |    4 +
>  bitbake/lib/bb/progress.py                    |   28 +-
>  bitbake/lib/bb/providers.py                   |   18 +-
>  bitbake/lib/bb/pysh/builtin.py                |  710 --------
>  bitbake/lib/bb/pysh/interp.py                 | 1367 ---------------
>  bitbake/lib/bb/pysh/lsprof.py                 |  116 --
>  bitbake/lib/bb/pysh/pysh.py                   |  167 --
>  bitbake/lib/bb/pysh/pyshlex.py                |    5 -
>  bitbake/lib/bb/pysh/pyshyacc.py               |   17 +-
>  bitbake/lib/bb/pysh/sherrors.py               |   26 -
>  bitbake/lib/bb/pysh/subprocess_fix.py         |   77 -
>  bitbake/lib/bb/remotedata.py                  |   12 +-
>  bitbake/lib/bb/runqueue.py                    | 1523
> ++++++++++------- bitbake/lib/bb/server/__init__.py             |
> 14 +- bitbake/lib/bb/server/process.py              |  123 +-
>  bitbake/lib/bb/server/xmlrpcclient.py         |   12 +-
>  bitbake/lib/bb/server/xmlrpcserver.py         |   12 +-
>  bitbake/lib/bb/siggen.py                      |  350 +++-
>  bitbake/lib/bb/taskdata.py                    |   17 +-
>  bitbake/lib/bb/tests/codeparser.py            |   22 +-
>  bitbake/lib/bb/tests/cooker.py                |   15 +-
>  bitbake/lib/bb/tests/cow.py                   |   17 +-
>  bitbake/lib/bb/tests/data.py                  |   39 +-
>  bitbake/lib/bb/tests/event.py                 |   23 +-
>  bitbake/lib/bb/tests/fetch.py                 |  308 +++-
>  bitbake/lib/bb/tests/parse.py                 |   33 +-
>  bitbake/lib/bb/tests/persist_data.py          |  129 ++
>  .../tests/runqueue-tests/classes/base.bbclass |  262 +++
>  .../runqueue-tests/classes/image.bbclass      |    5 +
>  .../runqueue-tests/classes/native.bbclass     |    2 +
>  .../bb/tests/runqueue-tests/conf/bitbake.conf |   16 +
>  .../runqueue-tests/conf/multiconfig/mc1.conf  |    1 +
>  .../runqueue-tests/conf/multiconfig/mc2.conf  |    1 +
>  .../lib/bb/tests/runqueue-tests/recipes/a1.bb |    0
>  .../lib/bb/tests/runqueue-tests/recipes/b1.bb |    1 +
>  .../lib/bb/tests/runqueue-tests/recipes/c1.bb |    0
>  .../lib/bb/tests/runqueue-tests/recipes/d1.bb |    3 +
>  .../lib/bb/tests/runqueue-tests/recipes/e1.bb |    1 +
>  bitbake/lib/bb/tests/runqueue.py              |  323 ++++
>  bitbake/lib/bb/tests/utils.py                 |   23 +-
>  bitbake/lib/bb/tinfoil.py                     |   12 +-
>  bitbake/lib/bb/ui/__init__.py                 |   12 +-
>  bitbake/lib/bb/ui/buildinfohelper.py          |   22 +-
>  bitbake/lib/bb/ui/knotty.py                   |   60 +-
>  bitbake/lib/bb/ui/ncurses.py                  |   12 +-
>  bitbake/lib/bb/ui/taskexp.py                  |   12 +-
>  bitbake/lib/bb/ui/toasterui.py                |   12 +-
>  bitbake/lib/bb/ui/uievent.py                  |   15 +-
>  bitbake/lib/bb/ui/uihelper.py                 |   16 +-
>  bitbake/lib/bb/utils.py                       |   49 +-
>  bitbake/lib/bblayers/__init__.py              |    4 +
>  bitbake/lib/bblayers/action.py                |    4 +
>  bitbake/lib/bblayers/common.py                |    4 +
>  bitbake/lib/bblayers/layerindex.py            |    6 +-
>  bitbake/lib/bblayers/query.py                 |   43 +-
>  bitbake/lib/bs4/dammit.py                     |   12 +-
>  bitbake/lib/bs4/element.py                    |   22 +-
>  bitbake/lib/hashserv/__init__.py              |   93 +
>  bitbake/lib/hashserv/client.py                |  157 ++
>  bitbake/lib/hashserv/server.py                |  414 +++++
>  bitbake/lib/hashserv/tests.py                 |  142 ++
>  bitbake/lib/layerindexlib/__init__.py         |   33 +-
>  bitbake/lib/layerindexlib/cooker.py           |   12 +-
>  bitbake/lib/layerindexlib/plugin.py           |   13 +-
>  bitbake/lib/layerindexlib/restapi.py          |   12 +-
>  bitbake/lib/layerindexlib/tests/common.py     |   12 +-
>  bitbake/lib/layerindexlib/tests/cooker.py     |   12 +-
>  .../lib/layerindexlib/tests/layerindexobj.py  |   12 +-
>  bitbake/lib/layerindexlib/tests/restapi.py    |   12 +-
>  bitbake/lib/progressbar/__init__.py           |    2 +
>  bitbake/lib/progressbar/compat.py             |    2 +
>  bitbake/lib/progressbar/progressbar.py        |    2 +
>  bitbake/lib/progressbar/widgets.py            |    2 +
>  bitbake/lib/prserv/__init__.py                |    4 +
>  bitbake/lib/prserv/db.py                      |    6 +-
>  bitbake/lib/prserv/serv.py                    |    4 +
>  bitbake/lib/pyinotify.py                      |   20 +-
>  bitbake/lib/toaster/bldcollector/admin.py     |    4 +
>  bitbake/lib/toaster/bldcollector/urls.py      |   13 +-
>  bitbake/lib/toaster/bldcollector/views.py     |   12 +-
>  bitbake/lib/toaster/bldcontrol/admin.py       |    4 +
>  .../lib/toaster/bldcontrol/bbcontroller.py    |   16 +-
>  .../bldcontrol/localhostbecontroller.py       |   16 +-
>  .../management/commands/checksettings.py      |    4 +
>  .../management/commands/runbuilds.py          |    4 +
>  bitbake/lib/toaster/bldcontrol/models.py      |    4 +
>  bitbake/lib/toaster/bldcontrol/views.py       |    4 +
>  bitbake/lib/toaster/manage.py                 |    4 +
>  bitbake/lib/toaster/orm/fixtures/oe-core.xml  |   26 +-
>  bitbake/lib/toaster/orm/fixtures/poky.xml     |   38 +-
>  .../orm/management/commands/lsupdates.py      |   15 +-
>  bitbake/lib/toaster/orm/models.py             |   25 +-
>  .../toaster/tests/browser/selenium_helpers.py |   17 +-
>  .../tests/browser/selenium_helpers_base.py    |   17 +-
>  .../tests/browser/test_all_builds_page.py     |   16 +-
>  .../tests/browser/test_all_projects_page.py   |   16 +-
>  .../tests/browser/test_builddashboard_page.py |   16 +-
>  .../test_builddashboard_page_artifacts.py     |   16 +-
>  .../test_builddashboard_page_recipes.py       |   16 +-
>  .../browser/test_builddashboard_page_tasks.py |   16 +-
>  .../tests/browser/test_js_unit_tests.py       |   16 +-
>  .../tests/browser/test_landing_page.py        |   18 +-
>  .../tests/browser/test_layerdetails_page.py   |   18 +-
>  .../browser/test_most_recent_builds_states.py |   18 +-
>  .../browser/test_new_custom_image_page.py     |   16 +-
>  .../tests/browser/test_new_project_page.py    |   16 +-
>  .../tests/browser/test_project_builds_page.py |   16 +-
>  .../tests/browser/test_project_config_page.py |   16 +-
>  .../tests/browser/test_project_page.py        |   16 +-
>  .../lib/toaster/tests/browser/test_sample.py  |   16 +-
>  .../toaster/tests/browser/test_task_page.py   |   16 +-
>  .../tests/browser/test_toastertable_ui.py     |   16 +-
>  bitbake/lib/toaster/tests/builds/buildtest.py |   16 +-
>  .../tests/builds/test_core_image_min.py       |   17 +-
>  .../toaster/tests/commands/test_loaddata.py   |   16 +-
>  .../toaster/tests/commands/test_lsupdates.py  |   16 +-
>  .../toaster/tests/commands/test_runbuilds.py  |   16 +-
>  bitbake/lib/toaster/tests/db/test_db.py       |    2 +
>  .../lib/toaster/tests/eventreplay/__init__.py |   16 +-
>  .../tests/functional/functional_helpers.py    |   16 +-
>  .../tests/functional/test_functional_basic.py |   16 +-
>  bitbake/lib/toaster/tests/views/test_views.py |   16 +-
>  bitbake/lib/toaster/toastergui/api.py         |   13 +-
>  bitbake/lib/toaster/toastergui/buildtables.py |   15 +-
>  .../toastergui/static/js/importlayer.js       |   12 +-
>  bitbake/lib/toaster/toastergui/tablefilter.py |   15 +-
>  bitbake/lib/toaster/toastergui/tables.py      |   15 +-
>  .../templatetags/field_values_filter.py       |    4 +
>  .../objects_to_dictionaries_filter.py         |    4 +
>  .../templatetags/project_url_tag.py           |    4 +
>  .../toastergui/templatetags/projecttags.py    |   15 +-
>  bitbake/lib/toaster/toastergui/typeaheads.py  |   12 +-
>  bitbake/lib/toaster/toastergui/urls.py        |   12 +-
>  bitbake/lib/toaster/toastergui/views.py       |   16 +-
>  bitbake/lib/toaster/toastergui/widgets.py     |   15 +-
>  .../management/commands/builddelete.py        |    4 +
>  .../management/commands/buildimport.py        |   15 +-
>  .../management/commands/buildslist.py         |    4 +
>  .../management/commands/checksocket.py        |   16 +-
>  .../toastermain/management/commands/perf.py   |    4 +
>  bitbake/lib/toaster/toastermain/settings.py   |   15 +-
>  .../settings_production_example.py            |   15 +-
>  .../lib/toaster/toastermain/settings_test.py  |   15 +-
>  bitbake/lib/toaster/toastermain/urls.py       |   15 +-
>  bitbake/lib/toaster/toastermain/wsgi.py       |    7 +-
>  doc/user_manual.md                            |   38 +-
>  meta-isar/conf/conf-notes.txt                 |    6 +-
>  scripts/ci_build.sh                           |   62 +-
>  scripts/start_vm                              |    8 +-
>  testsuite/build_test/build_test.py            |    2 +-
>  testsuite/start_vm.py                         |    2 +-
>  225 files changed, 5375 insertions(+), 6320 deletions(-)
>  create mode 100644 bitbake/.gitattributes
>  delete mode 100644 bitbake/HEADER
>  rename bitbake/{COPYING => LICENSE.GPL-2.0-only} (84%)
>  create mode 100644 bitbake/LICENSE.MIT
>  mode change 100755 => 120000 bitbake/bin/bitbake-dumpsig
>  create mode 100755 bitbake/bin/bitbake-hashclient
>  create mode 100755 bitbake/bin/bitbake-hashserv
>  delete mode 100644 bitbake/lib/bb/pysh/builtin.py
>  delete mode 100644 bitbake/lib/bb/pysh/interp.py
>  delete mode 100644 bitbake/lib/bb/pysh/lsprof.py
>  delete mode 100644 bitbake/lib/bb/pysh/pysh.py
>  delete mode 100644 bitbake/lib/bb/pysh/subprocess_fix.py
>  create mode 100644 bitbake/lib/bb/tests/persist_data.py
>  create mode 100644
> bitbake/lib/bb/tests/runqueue-tests/classes/base.bbclass create mode
> 100644 bitbake/lib/bb/tests/runqueue-tests/classes/image.bbclass
> create mode 100644
> bitbake/lib/bb/tests/runqueue-tests/classes/native.bbclass create
> mode 100644 bitbake/lib/bb/tests/runqueue-tests/conf/bitbake.conf
> create mode 100644
> bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc1.conf create
> mode 100644
> bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc2.conf create
> mode 100644 bitbake/lib/bb/tests/runqueue-tests/recipes/a1.bb create
> mode 100644 bitbake/lib/bb/tests/runqueue-tests/recipes/b1.bb create
> mode 100644 bitbake/lib/bb/tests/runqueue-tests/recipes/c1.bb create
> mode 100644 bitbake/lib/bb/tests/runqueue-tests/recipes/d1.bb create
> mode 100644 bitbake/lib/bb/tests/runqueue-tests/recipes/e1.bb create
> mode 100644 bitbake/lib/bb/tests/runqueue.py create mode 100644
> bitbake/lib/hashserv/__init__.py create mode 100644
> bitbake/lib/hashserv/client.py create mode 100644
> bitbake/lib/hashserv/server.py create mode 100644
> bitbake/lib/hashserv/tests.py
> 
> diff --git a/RECIPE-API-CHANGELOG.md b/RECIPE-API-CHANGELOG.md
> index bbef1a3..81ec430 100644
> --- a/RECIPE-API-CHANGELOG.md
> +++ b/RECIPE-API-CHANGELOG.md
> @@ -176,3 +176,19 @@ Otherwise set a encrypted root password like
> this: USERS += "root"
>  USER_root[password] =
> "$6$rounds=10000$RXeWrnFmkY$DtuS/OmsAS2cCEDo0BF5qQsizIrq6jPgXnwv3PHqREJeKd1sXdHX/ayQtuQWVDHe0KIO0/sVH8dvQm1KthF0d/"
> ``` +
> +### multiconfig build targets were renamed
> +
> +bitbake was upgraded to version 1.44.0 where "multiconfig" build
> targets were +renamed "mc". As an example, builds for the
> qemuarm-stretch machine should now +be done as follows:
> +
> +```
> +bitbake mc:qemuarm-stretch:isar-image-base
> +```
> +
> +The old syntax is no longer supported and will produce an error:
> +
> +```
> +bitbake multiconfig:qemuarm-stretch:isar-image-base
> +```
> diff --git a/bitbake/.gitattributes b/bitbake/.gitattributes
> new file mode 100644
> index 0000000..e4f8f62
> --- /dev/null
> +++ b/bitbake/.gitattributes
> @@ -0,0 +1,2 @@
> +*min.js binary
> +*min.css binary
> diff --git a/bitbake/HEADER b/bitbake/HEADER
> deleted file mode 100644
> index 9859255..0000000
> --- a/bitbake/HEADER
> +++ /dev/null
> @@ -1,19 +0,0 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
> -# <one line to give the program's name and a brief idea of what it
> does.> -# Copyright (C) <year>  <name of author>
> -#
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -
> diff --git a/bitbake/LICENSE b/bitbake/LICENSE
> index 7d4e5f4..8458042 100644
> --- a/bitbake/LICENSE
> +++ b/bitbake/LICENSE
> @@ -1,4 +1,13 @@
> -BitBake is licensed under the GNU General Public License version
> 2.0. See COPYING for further details. +BitBake is licensed under the
> GNU General Public License version 2.0. See +LICENSE.GPL-2.0-only for
> further details. +
> +Individual files contain the following style tags instead of the
> full license text: +
> +    SPDX-License-Identifier:	GPL-2.0-only
> +
> +This enables machine processing of license information based on the
> SPDX +License Identifiers that are here available:
> http://spdx.org/licenses/ +
>  
>  The following external components are distributed with this software:
>  
> @@ -17,3 +26,4 @@ Foundation and individual contributors.
>  * Font Awesome fonts redistributed under the SIL Open Font License
> 1.1 
>  * simplediff is distributed under the zlib license.
> +
> diff --git a/bitbake/COPYING b/bitbake/LICENSE.GPL-2.0-only
> similarity index 84%
> rename from bitbake/COPYING
> rename to bitbake/LICENSE.GPL-2.0-only
> index d511905..5db3c0a 100644
> --- a/bitbake/COPYING
> +++ b/bitbake/LICENSE.GPL-2.0-only
> @@ -279,61 +279,10 @@ POSSIBILITY OF SUCH DAMAGES.
>  
>  		     END OF TERMS AND CONDITIONS
>  
> -	    How to Apply These Terms to Your New Programs
> +Note:
> +Individual files contain the following tag instead of the full
> license text. 
> -  If you develop a new program, and you want it to be of the greatest
> -possible use to the public, the best way to achieve this is to make
> it -free software which everyone can redistribute and change under
> these terms.
> +    SPDX-License-Identifier: GPL-2.0-only
>  
> -  To do so, attach the following notices to the program.  It is
> safest -to attach them to the start of each source file to most
> effectively -convey the exclusion of warranty; and each file should
> have at least -the "copyright" line and a pointer to where the full
> notice is found. -
> -    <one line to give the program's name and a brief idea of what it
> does.>
> -    Copyright (C) <year>  <name of author>
> -
> -    This program is free software; you can redistribute it and/or
> modify
> -    it under the terms of the GNU General Public License as
> published by
> -    the Free Software Foundation; either version 2 of the License, or
> -    (at your option) any later version.
> -
> -    This program is distributed in the hope that it will be useful,
> -    but WITHOUT ANY WARRANTY; without even the implied warranty of
> -    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -    GNU General Public License for more details.
> -
> -    You should have received a copy of the GNU General Public
> License along
> -    with this program; if not, write to the Free Software
> Foundation, Inc.,
> -    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
> -
> -Also add information on how to contact you by electronic and paper
> mail. -
> -If the program is interactive, make it output a short notice like
> this -when it starts in an interactive mode:
> -
> -    Gnomovision version 69, Copyright (C) year name of author
> -    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type
> `show w'.
> -    This is free software, and you are welcome to redistribute it
> -    under certain conditions; type `show c' for details.
> -
> -The hypothetical commands `show w' and `show c' should show the
> appropriate -parts of the General Public License.  Of course, the
> commands you use may -be called something other than `show w' and
> `show c'; they could even be -mouse-clicks or menu items--whatever
> suits your program. -
> -You should also get your employer (if you work as a programmer) or
> your -school, if any, to sign a "copyright disclaimer" for the
> program, if -necessary.  Here is a sample; alter the names:
> -
> -  Yoyodyne, Inc., hereby disclaims all copyright interest in the
> program
> -  `Gnomovision' (which makes passes at compilers) written by James
> Hacker. -
> -  <signature of Ty Coon>, 1 April 1989
> -  Ty Coon, President of Vice
> -
> -This General Public License does not permit incorporating your
> program into -proprietary programs.  If your program is a subroutine
> library, you may -consider it more useful to permit linking
> proprietary applications with the -library.  If this is what you want
> to do, use the GNU Lesser General -Public License instead of this
> License. +This enables machine processing of license information
> based on the SPDX +License Identifiers that are here available:
> http://spdx.org/licenses/ diff --git a/bitbake/LICENSE.MIT
> b/bitbake/LICENSE.MIT new file mode 100644
> index 0000000..a6919eb
> --- /dev/null
> +++ b/bitbake/LICENSE.MIT
> @@ -0,0 +1,25 @@
> +Permission is hereby granted, free of charge, to any person
> obtaining a copy +of this software and associated documentation files
> (the "Software"), to deal +in the Software without restriction,
> including without limitation the rights +to use, copy, modify, merge,
> publish, distribute, sublicense, and/or sell +copies of the Software,
> and to permit persons to whom the Software is +furnished to do so,
> subject to the following conditions: +
> +The above copyright notice and this permission notice shall be
> included in +all copies or substantial portions of the Software.
> +
> +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND
> NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS
> BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN
> ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN
> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> +SOFTWARE. +
> +Note:
> +Individual files contain the following tag instead of the full
> license text. +
> +    SPDX-License-Identifier: MIT
> +
> +This enables machine processing of license information based on the
> SPDX +License Identifiers that are here available:
> http://spdx.org/licenses/ diff --git a/bitbake/MANIFEST.in
> b/bitbake/MANIFEST.in index b197378..f24969a 100644
> --- a/bitbake/MANIFEST.in
> +++ b/bitbake/MANIFEST.in
> @@ -1,6 +1,8 @@
> -include COPYING
>  include ChangeLog
>  include AUTHORS
> +include LICENSE
> +include LICENSE.GPL-2.0-only
> +include LICENSE.MIT
>  include contrib/*
>  include contrib/vim/*/*
>  include conf/*
> @@ -8,4 +10,3 @@ include classes/*
>  include doc/*
>  include doc/manual/*
>  include ez_setup.py
> -include HEADER
> diff --git a/bitbake/bin/bitbake b/bitbake/bin/bitbake
> index 57dec2a..66d08f8 100755
> --- a/bitbake/bin/bitbake
> +++ b/bitbake/bin/bitbake
> @@ -1,6 +1,4 @@
>  #!/usr/bin/env python3
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # Copyright (C) 2003, 2004  Chris Larson
>  # Copyright (C) 2003, 2004  Phil Blundell
> @@ -9,18 +7,8 @@
>  # Copyright (C) 2005        ROAD GmbH
>  # Copyright (C) 2006        Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  import sys
> @@ -38,7 +26,7 @@ from bb.main import bitbake_main,
> BitBakeConfigParameters, BBMainException if
> sys.getfilesystemencoding() != "utf-8": sys.exit("Please use a locale
> setting which supports UTF-8 (such as LANG=en_US.UTF-8).\nPython
> can't change the filesystem locale after loading so we need a UTF-8
> when Python starts or things won't work.") -__version__ = "1.40.0"
> +__version__ = "1.44.0" 
>  if __name__ == "__main__":
>      if __version__ != bb.__version__:
> diff --git a/bitbake/bin/bitbake-diffsigs
> b/bitbake/bin/bitbake-diffsigs index 4e6bbdd..19420a2 100755
> --- a/bitbake/bin/bitbake-diffsigs
> +++ b/bitbake/bin/bitbake-diffsigs
> @@ -1,27 +1,16 @@
>  #!/usr/bin/env python3
>  
> -# bitbake-diffsigs
> -# BitBake task signature data comparison utility
> +# bitbake-diffsigs / bitbake-dumpsig
> +# BitBake task signature data dump and comparison utility
>  #
>  # Copyright (C) 2012-2013, 2017 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  import sys
>  import warnings
> -import fnmatch
>  import argparse
>  import logging
>  import pickle
> @@ -32,7 +21,10 @@ import bb.tinfoil
>  import bb.siggen
>  import bb.msg
>  
> -logger = bb.msg.logger_create('bitbake-diffsigs')
> +myname = os.path.basename(sys.argv[0])
> +logger = bb.msg.logger_create(myname)
> +
> +is_dump = myname == 'bitbake-dumpsig'
>  
>  def find_siginfo(tinfoil, pn, taskname, sigs=None):
>      result = None
> @@ -59,8 +51,8 @@ def find_siginfo(tinfoil, pn, taskname, sigs=None):
>          sys.exit(2)
>      return result
>  
> -def find_compare_task(bbhandler, pn, taskname, sig1=None, sig2=None,
> color=False):
> -    """ Find the most recent signature files for the specified
> PN/task and compare them """ +def find_siginfo_task(bbhandler, pn,
> taskname, sig1=None, sig2=None):
> +    """ Find the most recent signature files for the specified
> PN/task """ 
>      if not taskname.startswith('do_'):
>          taskname = 'do_%s' % taskname
> @@ -79,73 +71,81 @@ def find_compare_task(bbhandler, pn, taskname,
> sig1=None, sig2=None, color=False latestfiles = [sigfiles[sig1],
> sigfiles[sig2]] else:
>          filedates = find_siginfo(bbhandler, pn, taskname)
> -        latestfiles = sorted(filedates.keys(), key=lambda f:
> filedates[f])[-3:]
> +        latestfiles = sorted(filedates.keys(), key=lambda f:
> filedates[f])[-2:] if not latestfiles:
>              logger.error('No sigdata files found matching %s %s' %
> (pn, taskname)) sys.exit(1)
> -        elif len(latestfiles) < 2:
> -            logger.error('Only one matching sigdata file found for
> the specified task (%s %s)' % (pn, taskname))
> -            sys.exit(1)
>  
> -    # Define recursion callback
> -    def recursecb(key, hash1, hash2):
> -        hashes = [hash1, hash2]
> -        hashfiles = find_siginfo(bbhandler, key, None, hashes)
> -
> -        recout = []
> -        if len(hashfiles) == 0:
> -            recout.append("Unable to find matching sigdata for %s
> with hashes %s or %s" % (key, hash1, hash2))
> -        elif not hash1 in hashfiles:
> -            recout.append("Unable to find matching sigdata for %s
> with hash %s" % (key, hash1))
> -        elif not hash2 in hashfiles:
> -            recout.append("Unable to find matching sigdata for %s
> with hash %s" % (key, hash2))
> -        else:
> -            out2 = bb.siggen.compare_sigfiles(hashfiles[hash1],
> hashfiles[hash2], recursecb, color=color)
> -            for change in out2:
> -                for line in change.splitlines():
> -                    recout.append('  ' + line)
> +    return latestfiles
> +
>  
> -        return recout
> +# Define recursion callback
> +def recursecb(key, hash1, hash2):
> +    hashes = [hash1, hash2]
> +    hashfiles = find_siginfo(tinfoil, key, None, hashes)
>  
> -    # Recurse into signature comparison
> -    logger.debug("Signature file (previous): %s" % latestfiles[-2])
> -    logger.debug("Signature file (latest): %s" % latestfiles[-1])
> -    output = bb.siggen.compare_sigfiles(latestfiles[-2],
> latestfiles[-1], recursecb, color=color)
> -    if output:
> -        print('\n'.join(output))
> -    sys.exit(0)
> +    recout = []
> +    if len(hashfiles) == 0:
> +        recout.append("Unable to find matching sigdata for %s with
> hashes %s or %s" % (key, hash1, hash2))
> +    elif not hash1 in hashfiles:
> +        recout.append("Unable to find matching sigdata for %s with
> hash %s" % (key, hash1))
> +    elif not hash2 in hashfiles:
> +        recout.append("Unable to find matching sigdata for %s with
> hash %s" % (key, hash2))
> +    else:
> +        out2 = bb.siggen.compare_sigfiles(hashfiles[hash1],
> hashfiles[hash2], recursecb, color=color)
> +        for change in out2:
> +            for line in change.splitlines():
> +                recout.append('    ' + line)
>  
> +    return recout
>  
>  
>  parser = argparse.ArgumentParser(
> -    description="Compares siginfo/sigdata files written out by
> BitBake")
> +    description=("Dumps" if is_dump else "Compares") + "
> siginfo/sigdata files written out by BitBake") 
> -parser.add_argument('-d', '--debug',
> +parser.add_argument('-D', '--debug',
>                      help='Enable debug output',
>                      action='store_true')
>  
> -parser.add_argument('--color',
> -        help='Colorize output (where %(metavar)s is %(choices)s)',
> -        choices=['auto', 'always', 'never'], default='auto',
> metavar='color') +if is_dump:
> +    parser.add_argument("-t", "--task",
> +            help="find the signature data file for the last run of
> the specified task",
> +            action="store", dest="taskargs", nargs=2,
> metavar=('recipename', 'taskname')) +
> +    parser.add_argument("sigdatafile1",
> +            help="Signature file to dump. Not used when using
> -t/--task.",
> +            action="store", nargs='?', metavar="sigdatafile")
> +else:
> +    parser.add_argument('-c', '--color',
> +            help='Colorize the output (where %(metavar)s is
> %(choices)s)',
> +            choices=['auto', 'always', 'never'], default='auto',
> metavar='color') 
> -parser.add_argument("-t", "--task",
> -        help="find the signature data files for last two runs of the
> specified task and compare them",
> -        action="store", dest="taskargs", nargs=2,
> metavar=('recipename', 'taskname'))
> +    parser.add_argument('-d', '--dump',
> +            help='Dump the last signature data instead of comparing
> (equivalent to using bitbake-dumpsig)',
> +            action='store_true')
>  
> -parser.add_argument("-s", "--signature",
> -        help="With -t/--task, specify the signatures to look for
> instead of taking the last two",
> -        action="store", dest="sigargs", nargs=2, metavar=('fromsig',
> 'tosig'))
> +    parser.add_argument("-t", "--task",
> +            help="find the signature data files for the last two
> runs of the specified task and compare them",
> +            action="store", dest="taskargs", nargs=2,
> metavar=('recipename', 'taskname')) 
> -parser.add_argument("sigdatafile1",
> -        help="First signature file to compare (or signature file to
> dump, if second not specified). Not used when using -t/--task.",
> -        action="store", nargs='?')
> +    parser.add_argument("-s", "--signature",
> +            help="With -t/--task, specify the signatures to look for
> instead of taking the last two",
> +            action="store", dest="sigargs", nargs=2,
> metavar=('fromsig', 'tosig')) 
> -parser.add_argument("sigdatafile2",
> -        help="Second signature file to compare",
> -        action="store", nargs='?')
> +    parser.add_argument("sigdatafile1",
> +            help="First signature file to compare (or signature file
> to dump, if second not specified). Not used when using -t/--task.",
> +            action="store", nargs='?')
>  
> +    parser.add_argument("sigdatafile2",
> +            help="Second signature file to compare",
> +            action="store", nargs='?')
>  
>  options = parser.parse_args()
> +if is_dump:
> +    options.color = 'never'
> +    options.dump = True
> +    options.sigdatafile2 = None
> +    options.sigargs = None
>  
>  if options.debug:
>      logger.setLevel(logging.DEBUG)
> @@ -155,17 +155,32 @@ color = (options.color == 'always' or
> (options.color == 'auto' and sys.stdout.is if options.taskargs:
>      with bb.tinfoil.Tinfoil() as tinfoil:
>          tinfoil.prepare(config_only=True)
> -        if options.sigargs:
> -            find_compare_task(tinfoil, options.taskargs[0],
> options.taskargs[1], options.sigargs[0], options.sigargs[1],
> color=color)
> +        if not options.dump and options.sigargs:
> +            files = find_siginfo_task(tinfoil, options.taskargs[0],
> options.taskargs[1], options.sigargs[0], options.sigargs[1])
> +        else:
> +            files = find_siginfo_task(tinfoil, options.taskargs[0],
> options.taskargs[1]) +
> +        if options.dump:
> +            logger.debug("Signature file: %s" % files[-1])
> +            output = bb.siggen.dump_sigfile(files[-1])
>          else:
> -            find_compare_task(tinfoil, options.taskargs[0],
> options.taskargs[1], color=color)
> +            if len(files) < 2:
> +                logger.error('Only one matching sigdata file found
> for the specified task (%s %s)' % (options.taskargs[0],
> options.taskargs[1]))
> +                sys.exit(1)
> +
> +            # Recurse into signature comparison
> +            logger.debug("Signature file (previous): %s" % files[-2])
> +            logger.debug("Signature file (latest): %s" % files[-1])
> +            output = bb.siggen.compare_sigfiles(files[-2],
> files[-1], recursecb, color=color) else:
>      if options.sigargs:
>          logger.error('-s/--signature can only be used together with
> -t/--task') sys.exit(1)
>      try:
> -        if options.sigdatafile1 and options.sigdatafile2:
> -            output =
> bb.siggen.compare_sigfiles(options.sigdatafile1,
> options.sigdatafile2, color=color)
> +        if not options.dump and options.sigdatafile1 and
> options.sigdatafile2:
> +            with bb.tinfoil.Tinfoil() as tinfoil:
> +                tinfoil.prepare(config_only=True)
> +                output =
> bb.siggen.compare_sigfiles(options.sigdatafile1,
> options.sigdatafile2, recursecb, color=color) elif
> options.sigdatafile1: output =
> bb.siggen.dump_sigfile(options.sigdatafile1) else: @@ -179,5 +194,5
> @@ else: logger.error('Invalid signature data - ensure you are
> specifying sigdata/siginfo files') sys.exit(1)
>  
> -    if output:
> -        print('\n'.join(output))
> +if output:
> +    print('\n'.join(output))
> diff --git a/bitbake/bin/bitbake-dumpsig b/bitbake/bin/bitbake-dumpsig
> deleted file mode 100755
> index 95ebd93..0000000
> --- a/bitbake/bin/bitbake-dumpsig
> +++ /dev/null
> @@ -1,94 +0,0 @@
> -#!/usr/bin/env python3
> -
> -# bitbake-dumpsig
> -# BitBake task signature dump utility
> -#
> -# Copyright (C) 2013 Intel Corporation
> -#
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -
> -import os
> -import sys
> -import warnings
> -import optparse
> -import logging
> -import pickle
> -
> -sys.path.insert(0,
> os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) -
> -import bb.tinfoil
> -import bb.siggen
> -import bb.msg
> -
> -logger = bb.msg.logger_create('bitbake-dumpsig')
> -
> -def find_siginfo_task(bbhandler, pn, taskname):
> -    """ Find the most recent signature file for the specified
> PN/task """ -
> -    if not hasattr(bb.siggen, 'find_siginfo'):
> -        logger.error('Metadata does not support finding signature
> data files')
> -        sys.exit(1)
> -
> -    if not taskname.startswith('do_'):
> -        taskname = 'do_%s' % taskname
> -
> -    filedates = bb.siggen.find_siginfo(pn, taskname, None,
> bbhandler.config_data)
> -    latestfiles = sorted(filedates.keys(), key=lambda f:
> filedates[f])[-1:]
> -    if not latestfiles:
> -        logger.error('No sigdata files found matching %s %s' % (pn,
> taskname))
> -        sys.exit(1)
> -
> -    return latestfiles[0]
> -
> -parser = optparse.OptionParser(
> -    description = "Dumps siginfo/sigdata files written out by
> BitBake",
> -    usage = """
> -  %prog -t recipename taskname
> -  %prog sigdatafile""")
> -
> -parser.add_option("-D", "--debug",
> -        help = "enable debug",
> -        action = "store_true", dest="debug", default = False)
> -
> -parser.add_option("-t", "--task",
> -        help = "find the signature data file for the specified task",
> -        action="store", dest="taskargs", nargs=2,
> metavar='recipename taskname') -
> -options, args = parser.parse_args(sys.argv)
> -
> -if options.debug:
> -    logger.setLevel(logging.DEBUG)
> -
> -if options.taskargs:
> -    tinfoil = bb.tinfoil.Tinfoil()
> -    tinfoil.prepare(config_only = True)
> -    file = find_siginfo_task(tinfoil, options.taskargs[0],
> options.taskargs[1])
> -    logger.debug("Signature file: %s" % file)
> -elif len(args) == 1:
> -    parser.print_help()
> -    sys.exit(0)
> -else:
> -    file = args[1]
> -
> -try:
> -    output = bb.siggen.dump_sigfile(file)
> -except IOError as e:
> -    logger.error(str(e))
> -    sys.exit(1)
> -except (pickle.UnpicklingError, EOFError):
> -    logger.error('Invalid signature data - ensure you are specifying
> a sigdata/siginfo file')
> -    sys.exit(1)
> -
> -if output:
> -    print('\n'.join(output))
> diff --git a/bitbake/bin/bitbake-dumpsig b/bitbake/bin/bitbake-dumpsig
> new file mode 120000
> index 0000000..b1e8489
> --- /dev/null
> +++ b/bitbake/bin/bitbake-dumpsig
> @@ -0,0 +1 @@
> +bitbake-diffsigs
> \ No newline at end of file
> diff --git a/bitbake/bin/bitbake-hashclient
> b/bitbake/bin/bitbake-hashclient new file mode 100755
> index 0000000..29ab65f
> --- /dev/null
> +++ b/bitbake/bin/bitbake-hashclient
> @@ -0,0 +1,170 @@
> +#! /usr/bin/env python3
> +#
> +# Copyright (C) 2019 Garmin Ltd.
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
> +import argparse
> +import hashlib
> +import logging
> +import os
> +import pprint
> +import sys
> +import threading
> +import time
> +
> +try:
> +    import tqdm
> +    ProgressBar = tqdm.tqdm
> +except ImportError:
> +    class ProgressBar(object):
> +        def __init__(self, *args, **kwargs):
> +            pass
> +
> +        def __enter__(self):
> +            return self
> +
> +        def __exit__(self, *args, **kwargs):
> +            pass
> +
> +        def update(self):
> +            pass
> +
> +sys.path.insert(0,
> os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) +
> +import hashserv
> +
> +DEFAULT_ADDRESS = 'unix://./hashserve.sock'
> +METHOD = 'stress.test.method'
> +
> +
> +def main():
> +    def handle_stats(args, client):
> +        if args.reset:
> +            s = client.reset_stats()
> +        else:
> +            s = client.get_stats()
> +        pprint.pprint(s)
> +        return 0
> +
> +    def handle_stress(args, client):
> +        def thread_main(pbar, lock):
> +            nonlocal found_hashes
> +            nonlocal missed_hashes
> +            nonlocal max_time
> +
> +            client = hashserv.create_client(args.address)
> +
> +            for i in range(args.requests):
> +                taskhash = hashlib.sha256()
> +                taskhash.update(args.taskhash_seed.encode('utf-8'))
> +                taskhash.update(str(i).encode('utf-8'))
> +
> +                start_time = time.perf_counter()
> +                l = client.get_unihash(METHOD, taskhash.hexdigest())
> +                elapsed = time.perf_counter() - start_time
> +
> +                with lock:
> +                    if l:
> +                        found_hashes += 1
> +                    else:
> +                        missed_hashes += 1
> +
> +                    max_time = max(elapsed, max_time)
> +                    pbar.update()
> +
> +        max_time = 0
> +        found_hashes = 0
> +        missed_hashes = 0
> +        lock = threading.Lock()
> +        total_requests = args.clients * args.requests
> +        start_time = time.perf_counter()
> +        with ProgressBar(total=total_requests) as pbar:
> +            threads = [threading.Thread(target=thread_main,
> args=(pbar, lock), daemon=False) for _ in range(args.clients)]
> +            for t in threads:
> +                t.start()
> +
> +            for t in threads:
> +                t.join()
> +
> +        elapsed = time.perf_counter() - start_time
> +        with lock:
> +            print("%d requests in %.1fs. %.1f requests per second" %
> (total_requests, elapsed, total_requests / elapsed))
> +            print("Average request time %.8fs" % (elapsed /
> total_requests))
> +            print("Max request time was %.8fs" % max_time)
> +            print("Found %d hashes, missed %d" % (found_hashes,
> missed_hashes)) +
> +        if args.report:
> +            with ProgressBar(total=args.requests) as pbar:
> +                for i in range(args.requests):
> +                    taskhash = hashlib.sha256()
> +
> taskhash.update(args.taskhash_seed.encode('utf-8'))
> +                    taskhash.update(str(i).encode('utf-8'))
> +
> +                    outhash = hashlib.sha256()
> +                    outhash.update(args.outhash_seed.encode('utf-8'))
> +                    outhash.update(str(i).encode('utf-8'))
> +
> +                    client.report_unihash(taskhash.hexdigest(),
> METHOD, outhash.hexdigest(), taskhash.hexdigest()) +
> +                    with lock:
> +                        pbar.update()
> +
> +    parser = argparse.ArgumentParser(description='Hash Equivalence
> Client')
> +    parser.add_argument('--address', default=DEFAULT_ADDRESS,
> help='Server address (default "%(default)s")')
> +    parser.add_argument('--log', default='WARNING', help='Set
> logging level') +
> +    subparsers = parser.add_subparsers()
> +
> +    stats_parser = subparsers.add_parser('stats', help='Show server
> stats')
> +    stats_parser.add_argument('--reset', action='store_true',
> +                              help='Reset server stats')
> +    stats_parser.set_defaults(func=handle_stats)
> +
> +    stress_parser = subparsers.add_parser('stress', help='Run stress
> test')
> +    stress_parser.add_argument('--clients', type=int, default=10,
> +                               help='Number of simultaneous clients')
> +    stress_parser.add_argument('--requests', type=int, default=1000,
> +                               help='Number of requests each client
> will perform')
> +    stress_parser.add_argument('--report', action='store_true',
> +                               help='Report new hashes')
> +    stress_parser.add_argument('--taskhash-seed', default='',
> +                               help='Include string in taskhash')
> +    stress_parser.add_argument('--outhash-seed', default='',
> +                               help='Include string in outhash')
> +    stress_parser.set_defaults(func=handle_stress)
> +
> +    args = parser.parse_args()
> +
> +    logger = logging.getLogger('hashserv')
> +
> +    level = getattr(logging, args.log.upper(), None)
> +    if not isinstance(level, int):
> +        raise ValueError('Invalid log level: %s' % args.log)
> +
> +    logger.setLevel(level)
> +    console = logging.StreamHandler()
> +    console.setLevel(level)
> +    logger.addHandler(console)
> +
> +    func = getattr(args, 'func', None)
> +    if func:
> +        client = hashserv.create_client(args.address)
> +        # Try to establish a connection to the server now to detect
> failures
> +        # early
> +        client.connect()
> +
> +        return func(args, client)
> +
> +    return 0
> +
> +
> +if __name__ == '__main__':
> +    try:
> +        ret = main()
> +    except Exception:
> +        ret = 1
> +        import traceback
> +        traceback.print_exc()
> +    sys.exit(ret)
> diff --git a/bitbake/bin/bitbake-hashserv
> b/bitbake/bin/bitbake-hashserv new file mode 100755
> index 0000000..1bc1f91
> --- /dev/null
> +++ b/bitbake/bin/bitbake-hashserv
> @@ -0,0 +1,62 @@
> +#! /usr/bin/env python3
> +#
> +# Copyright (C) 2018 Garmin Ltd.
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
> +import os
> +import sys
> +import logging
> +import argparse
> +import sqlite3
> +
> +sys.path.insert(0,
> os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) +
> +import hashserv
> +
> +VERSION = "1.0.0"
> +
> +DEFAULT_BIND = 'unix://./hashserve.sock'
> +
> +
> +def main():
> +    parser = argparse.ArgumentParser(description='Hash Equivalence
> Reference Server. Version=%s' % VERSION,
> +                                     epilog='''The bind address is
> the path to a unix domain socket if it is
> +                                               prefixed with
> "unix://". Otherwise, it is an IP address
> +                                               and port in form
> ADDRESS:PORT. To bind to all addresses, leave
> +                                               the ADDRESS empty,
> e.g. "--bind :8686". To bind to a specific
> +                                               IPv6 address, enclose
> the address in "[]", e.g.
> +                                               "--bind [::1]:8686"'''
> +                                     )
> +
> +    parser.add_argument('--bind', default=DEFAULT_BIND, help='Bind
> address (default "%(default)s")')
> +    parser.add_argument('--database', default='./hashserv.db',
> help='Database file (default "%(default)s")')
> +    parser.add_argument('--log', default='WARNING', help='Set
> logging level') +
> +    args = parser.parse_args()
> +
> +    logger = logging.getLogger('hashserv')
> +
> +    level = getattr(logging, args.log.upper(), None)
> +    if not isinstance(level, int):
> +        raise ValueError('Invalid log level: %s' % args.log)
> +
> +    logger.setLevel(level)
> +    console = logging.StreamHandler()
> +    console.setLevel(level)
> +    logger.addHandler(console)
> +
> +    server = hashserv.create_server(args.bind, args.database)
> +    server.serve_forever()
> +    return 0
> +
> +
> +if __name__ == '__main__':
> +    try:
> +        ret = main()
> +    except Exception:
> +        ret = 1
> +        import traceback
> +        traceback.print_exc()
> +    sys.exit(ret)
> diff --git a/bitbake/bin/bitbake-layers b/bitbake/bin/bitbake-layers
> index d184011..a884dc1 100755
> --- a/bitbake/bin/bitbake-layers
> +++ b/bitbake/bin/bitbake-layers
> @@ -7,18 +7,8 @@
>  # Copyright (C) 2011 Mentor Graphics Corporation
>  # Copyright (C) 2011-2015 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import logging
>  import os
> diff --git a/bitbake/bin/bitbake-prserv b/bitbake/bin/bitbake-prserv
> index f38d2dd..1e9b6cb 100755
> --- a/bitbake/bin/bitbake-prserv
> +++ b/bitbake/bin/bitbake-prserv
> @@ -1,4 +1,8 @@
>  #!/usr/bin/env python3
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  import os
>  import sys,logging
>  import optparse
> diff --git a/bitbake/bin/bitbake-selftest
> b/bitbake/bin/bitbake-selftest index cfa7ac5..041a271 100755
> --- a/bitbake/bin/bitbake-selftest
> +++ b/bitbake/bin/bitbake-selftest
> @@ -2,18 +2,8 @@
>  #
>  # Copyright (C) 2012 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  import sys, logging
> @@ -22,6 +12,7 @@ sys.path.insert(0,
> os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib import
> unittest try:
>      import bb
> +    import hashserv
>      import layerindexlib
>  except RuntimeError as exc:
>      sys.exit(str(exc))
> @@ -33,7 +24,10 @@ tests = ["bb.tests.codeparser",
>           "bb.tests.event",
>           "bb.tests.fetch",
>           "bb.tests.parse",
> +         "bb.tests.persist_data",
> +         "bb.tests.runqueue",
>           "bb.tests.utils",
> +         "hashserv.tests",
>           "layerindexlib.tests.layerindexobj",
>           "layerindexlib.tests.restapi",
>           "layerindexlib.tests.cooker"]
> diff --git a/bitbake/bin/bitbake-worker b/bitbake/bin/bitbake-worker
> index e925054..6776cad 100755
> --- a/bitbake/bin/bitbake-worker
> +++ b/bitbake/bin/bitbake-worker
> @@ -1,4 +1,7 @@
>  #!/usr/bin/env python3
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
>  
>  import os
>  import sys
> @@ -136,7 +139,7 @@ def sigterm_handler(signum, frame):
>      os.killpg(0, signal.SIGTERM)
>      sys.exit()
>  
> -def fork_off_task(cfg, data, databuilder, workerdata, fn, task,
> taskname, appends, taskdepdata, extraconfigdata, quieterrors=False,
> dry_run_exec=False): +def fork_off_task(cfg, data, databuilder,
> workerdata, fn, task, taskname, taskhash, unihash, appends,
> taskdepdata, extraconfigdata, quieterrors=False, dry_run_exec=False):
> # We need to setup the environment BEFORE the fork, since # a fork()
> or exec*() activates PSEUDO... @@ -231,10 +234,13 @@ def
> fork_off_task(cfg, data, databuilder, workerdata, fn, task, taskname,
> append the_data.setVar(varname, value)
> bb.parse.siggen.set_taskdata(workerdata["sigdata"])
> +                if "newhashes" in workerdata:
> +
> bb.parse.siggen.set_taskhashes(workerdata["newhashes"]) ret = 0
>  
>                  the_data = bb_cache.loadDataFull(fn, appends)
> -                the_data.setVar('BB_TASKHASH',
> workerdata["runq_hash"][task])
> +                the_data.setVar('BB_TASKHASH', taskhash)
> +                the_data.setVar('BB_UNIHASH', unihash)
>  
>                  bb.utils.set_process_name("%s:%s" %
> (the_data.getVar("PN"), taskname.replace("do_", ""))) 
> @@ -373,6 +379,7 @@ class BitbakeWorker(object):
>                  self.handle_item(b"cookerconfig",
> self.handle_cookercfg) self.handle_item(b"extraconfigdata",
> self.handle_extraconfigdata) self.handle_item(b"workerdata",
> self.handle_workerdata)
> +                self.handle_item(b"newtaskhashes",
> self.handle_newtaskhashes) self.handle_item(b"runtask",
> self.handle_runtask) self.handle_item(b"finishnow",
> self.handle_finishnow) self.handle_item(b"ping", self.handle_ping)
> @@ -411,6 +418,10 @@ class BitbakeWorker(object):
>          bb.msg.loggerDefaultDomains =
> self.workerdata["logdefaultdomain"] for mc in self.databuilder.mcdata:
>              self.databuilder.mcdata[mc].setVar("PRSERV_HOST",
> self.workerdata["prhost"])
> +            self.databuilder.mcdata[mc].setVar("BB_HASHSERVE",
> self.workerdata["hashservaddr"]) +
> +    def handle_newtaskhashes(self, data):
> +        self.workerdata["newhashes"] = pickle.loads(data)
>  
>      def handle_ping(self, _):
>          workerlog_write("Handling ping\n")
> @@ -425,10 +436,10 @@ class BitbakeWorker(object):
>          sys.exit(0)
>  
>      def handle_runtask(self, data):
> -        fn, task, taskname, quieterrors, appends, taskdepdata,
> dry_run_exec = pickle.loads(data)
> +        fn, task, taskname, taskhash, unihash, quieterrors, appends,
> taskdepdata, dry_run_exec = pickle.loads(data)
> workerlog_write("Handling runtask %s %s %s\n" % (task, fn, taskname)) 
> -        pid, pipein, pipeout = fork_off_task(self.cookercfg,
> self.data, self.databuilder, self.workerdata, fn, task, taskname,
> appends, taskdepdata, self.extraconfigdata, quieterrors, dry_run_exec)
> +        pid, pipein, pipeout = fork_off_task(self.cookercfg,
> self.data, self.databuilder, self.workerdata, fn, task, taskname,
> taskhash, unihash, appends, taskdepdata, self.extraconfigdata,
> quieterrors, dry_run_exec) self.build_pids[pid] = task
> self.build_pipes[pid] = runQueueWorkerPipe(pipein, pipeout) diff
> --git a/bitbake/bin/bitdoc b/bitbake/bin/bitdoc index
> 2744678..9bd02be 100755 --- a/bitbake/bin/bitdoc
> +++ b/bitbake/bin/bitdoc
> @@ -1,21 +1,9 @@
>  #!/usr/bin/env python3
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # Copyright (C) 2005 Holger Hans Peter Freyther
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import optparse, os, sys
>  
> diff --git a/bitbake/bin/git-make-shallow
> b/bitbake/bin/git-make-shallow index 296d3a3..57069f7 100755
> --- a/bitbake/bin/git-make-shallow
> +++ b/bitbake/bin/git-make-shallow
> @@ -1,4 +1,8 @@
>  #!/usr/bin/env python3
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  """git-make-shallow: make the current git repository shallow
>  
>  Remove the history of the specified revisions, then optionally
> filter the diff --git a/bitbake/bin/toaster b/bitbake/bin/toaster
> index ecf66fa..c3472df 100755
> --- a/bitbake/bin/toaster
> +++ b/bitbake/bin/toaster
> @@ -3,19 +3,9 @@
>  # toaster - shell script to start Toaster
>  
>  # Copyright (C) 2013-2015 Intel Corp.
> -
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License as
> published by -# the Free Software Foundation; either version 2 of the
> License, or -# (at your option) any later version.
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-or-later
>  #
> -# You should have received a copy of the GNU General Public License
> -# along with this program. If not, see http://www.gnu.org/licenses/.
>  
>  HELP="
>  Usage: source toaster start|stop [webport=<address:port>] [noweb]
> [nobuild] [toasterdir] diff --git a/bitbake/bin/toaster-eventreplay
> b/bitbake/bin/toaster-eventreplay index 80967a0..8fa4ab7 100755
> --- a/bitbake/bin/toaster-eventreplay
> +++ b/bitbake/bin/toaster-eventreplay
> @@ -1,25 +1,12 @@
>  #!/usr/bin/env python3
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # Copyright (C) 2014        Alex Damian
>  #
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
>  # This file re-uses code spread throughout other Bitbake source
> files. # As such, all other copyrights belong to their own right
> holders. #
> -#
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  """
>  This command takes a filename as a single parameter. The filename is
> read diff --git a/bitbake/classes/base.bbclass
> b/bitbake/classes/base.bbclass index 71d9ec1..08441fe 100644
> --- a/bitbake/classes/base.bbclass
> +++ b/bitbake/classes/base.bbclass
> @@ -44,7 +44,7 @@ python do_showdata() {
>  	# emit the metadata which isnt valid shell
>  	for e in bb.data.keys(d):
>  		if d.getVarFlag(e, 'python', False):
> -			bb.plain("\npython %s () {\n%s}" % (e,
> d.getVar(e, True)))
> +			bb.plain("\npython %s () {\n%s}" % (e,
> d.getVar(e))) }
>  
>  addtask listtasks
> diff --git a/bitbake/contrib/dump_cache.py
> b/bitbake/contrib/dump_cache.py index 8963ca4..c6723cb 100755
> --- a/bitbake/contrib/dump_cache.py
> +++ b/bitbake/contrib/dump_cache.py
> @@ -1,6 +1,4 @@
>  #!/usr/bin/env python3
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # Copyright (C) 2012, 2018 Wind River Systems, Inc.
>  #
> diff --git
> a/bitbake/doc/bitbake-user-manual/bitbake-user-manual-execution.xml
> b/bitbake/doc/bitbake-user-manual/bitbake-user-manual-execution.xml
> index f1caaec..46dafee 100644 ---
> a/bitbake/doc/bitbake-user-manual/bitbake-user-manual-execution.xml
> +++
> b/bitbake/doc/bitbake-user-manual/bitbake-user-manual-execution.xml
> @@ -31,7 +31,7 @@ <para> Prior to executing BitBake, you should take
> advantage of available parallel thread execution on your build host
> by setting the
> -                <link
> linkend='var-BB_NUMBER_THREADS'><filename>BB_NUMBER_THREADS</filename></link>
> +                <link
> linkend='var-bb-BB_NUMBER_THREADS'><filename>BB_NUMBER_THREADS</filename></link>
> variable in your project's <filename>local.conf</filename>
> configuration file. </para>
> @@ -87,9 +87,9 @@
>          <para>
>              The <filename>layer.conf</filename> files are used to
>              construct key variables such as
> -            <link
> linkend='var-BBPATH'><filename>BBPATH</filename></link>
> +            <link
> linkend='var-bb-BBPATH'><filename>BBPATH</filename></link> and
> -            <link
> linkend='var-BBFILES'><filename>BBFILES</filename></link>.
> +            <link
> linkend='var-bb-BBFILES'><filename>BBFILES</filename></link>.
> <filename>BBPATH</filename> is used to search for configuration and
> class files under the <filename>conf</filename> and
> <filename>classes</filename> @@ -117,19 +117,19 @@
>              at certain variables, including:
>              <itemizedlist>
>                  <listitem><para>
> -                    <link
> linkend='var-BB_ENV_WHITELIST'><filename>BB_ENV_WHITELIST</filename></link>
> +                    <link
> linkend='var-bb-BB_ENV_WHITELIST'><filename>BB_ENV_WHITELIST</filename></link>
> </para></listitem> <listitem><para>
> -                    <link
> linkend='var-BB_ENV_EXTRAWHITE'><filename>BB_ENV_EXTRAWHITE</filename></link>
> +                    <link
> linkend='var-bb-BB_ENV_EXTRAWHITE'><filename>BB_ENV_EXTRAWHITE</filename></link>
> </para></listitem> <listitem><para>
> -                    <link
> linkend='var-BB_PRESERVE_ENV'><filename>BB_PRESERVE_ENV</filename></link>
> +                    <link
> linkend='var-bb-BB_PRESERVE_ENV'><filename>BB_PRESERVE_ENV</filename></link>
> </para></listitem> <listitem><para>
> -                    <link
> linkend='var-BB_ORIGENV'><filename>BB_ORIGENV</filename></link>
> +                    <link
> linkend='var-bb-BB_ORIGENV'><filename>BB_ORIGENV</filename></link>
> </para></listitem> <listitem><para>
> -                    <link
> linkend='var-BITBAKE_UI'><filename>BITBAKE_UI</filename></link>
> +                    <link
> linkend='var-bb-BITBAKE_UI'><filename>BITBAKE_UI</filename></link>
> </para></listitem> </itemizedlist>
>              The first four variables in this list relate to how
> BitBake treats shell @@ -156,7 +156,7 @@
>              BitBake first searches the current working directory for
> an optional <filename>conf/bblayers.conf</filename> configuration
> file. This file is expected to contain a
> -            <link
> linkend='var-BBLAYERS'><filename>BBLAYERS</filename></link>
> +            <link
> linkend='var-bb-BBLAYERS'><filename>BBLAYERS</filename></link>
> variable that is a space-delimited list of 'layer' directories.
> Recall that if BitBake cannot find a
> <filename>bblayers.conf</filename> file, then it is assumed the user
> has set the <filename>BBPATH</filename> @@ -166,10 +166,10 @@ <para>
>              For each directory (layer) in this list, a
> <filename>conf/layer.conf</filename> file is located and parsed with
> the
> -            <link
> linkend='var-LAYERDIR'><filename>LAYERDIR</filename></link>
> +            <link
> linkend='var-bb-LAYERDIR'><filename>LAYERDIR</filename></link>
> variable being set to the directory where the layer was found. The
> idea is these files automatically set up
> -            <link
> linkend='var-BBPATH'><filename>BBPATH</filename></link>
> +            <link
> linkend='var-bb-BBPATH'><filename>BBPATH</filename></link> and other
> variables correctly for a given build directory. </para>
>  
> @@ -189,7 +189,7 @@
>              depending on the environment variables previously
>              mentioned or set in the configuration files.
>              The
> -            "<link linkend='ref-variables-glos'>Variables
> Glossary</link>"
> +            "<link linkend='ref-bb-variables-glos'>Variables
> Glossary</link>" chapter presents a full list of variables.
>          </para>
>  
> @@ -204,7 +204,7 @@
>          <para>
>              The <filename>base.bbclass</filename> file is always
> included. Other classes that are specified in the configuration using
> the
> -            <link
> linkend='var-INHERIT'><filename>INHERIT</filename></link>
> +            <link
> linkend='var-bb-INHERIT'><filename>INHERIT</filename></link> variable
> are also included. BitBake searches for class files in a
>              <filename>classes</filename> subdirectory under
> @@ -270,7 +270,7 @@
>  
>          <para>
>              During the configuration phase, BitBake will have set
> -            <link
> linkend='var-BBFILES'><filename>BBFILES</filename></link>.
> +            <link
> linkend='var-bb-BBFILES'><filename>BBFILES</filename></link>. BitBake
> now uses it to construct a list of recipes to parse, along with any
> append files (<filename>.bbappend</filename>) to apply.
> @@ -292,7 +292,7 @@
>              Any inherit statements cause BitBake to find and
>              then parse class files (<filename>.bbclass</filename>)
>              using
> -            <link
> linkend='var-BBPATH'><filename>BBPATH</filename></link>
> +            <link
> linkend='var-bb-BBPATH'><filename>BBPATH</filename></link> as the
> search path. Finally, BitBake parses in order any append files found
> in <filename>BBFILES</filename>.
> @@ -303,8 +303,8 @@
>              pieces of metadata.
>              For example, in <filename>bitbake.conf</filename> the
> recipe name and version are used to set the variables
> -            <link linkend='var-PN'><filename>PN</filename></link> and
> -            <link linkend='var-PV'><filename>PV</filename></link>:
> +            <link linkend='var-bb-PN'><filename>PN</filename></link>
> and
> +            <link linkend='var-bb-PV'><filename>PV</filename></link>:
>              <literallayout class='monospaced'>
>       PN = "${@bb.parse.BBHandler.vars_from_file(d.getVar('FILE',
> False),d)[0] or 'defaultpkgname'}" PV =
> "${@bb.parse.BBHandler.vars_from_file(d.getVar('FILE', False),d)[1]
> or '1.0'}" @@ -336,7 +336,7 @@ recipe information.
>              The validity of this cache is determined by first
> computing a checksum of the base configuration data (see
> -            <link
> linkend='var-BB_HASHCONFIG_WHITELIST'><filename>BB_HASHCONFIG_WHITELIST</filename></link>)
> +            <link
> linkend='var-bb-BB_HASHCONFIG_WHITELIST'><filename>BB_HASHCONFIG_WHITELIST</filename></link>)
> and then checking if the checksum matches. If that checksum matches
> what is in the cache and the recipe and class files have not changed,
> Bitbake is able to use @@ -384,9 +384,9 @@
>              the recipe can be known.
>              Each recipe's <filename>PROVIDES</filename> list is
> created implicitly through the recipe's
> -            <link linkend='var-PN'><filename>PN</filename></link>
> variable
> +            <link linkend='var-bb-PN'><filename>PN</filename></link>
> variable and explicitly through the recipe's
> -            <link
> linkend='var-PROVIDES'><filename>PROVIDES</filename></link>
> +            <link
> linkend='var-bb-PROVIDES'><filename>PROVIDES</filename></link>
> variable, which is optional. </para>
>  
> @@ -427,7 +427,7 @@
>       PREFERRED_PROVIDER_virtual/kernel = "linux-yocto"
>              </literallayout>
>              The default
> -            <link
> linkend='var-PREFERRED_PROVIDER'><filename>PREFERRED_PROVIDER</filename></link>
> +            <link
> linkend='var-bb-PREFERRED_PROVIDER'><filename>PREFERRED_PROVIDER</filename></link>
> is the provider with the same name as the target. Bitbake iterates
> through each target it needs to build and resolves them and their
> dependencies using this process. @@ -439,10 +439,10 @@
>              BitBake defaults to the highest version of a provider.
>              Version comparisons are made using the same method as
> Debian. You can use the
> -            <link
> linkend='var-PREFERRED_VERSION'><filename>PREFERRED_VERSION</filename></link>
> +            <link
> linkend='var-bb-PREFERRED_VERSION'><filename>PREFERRED_VERSION</filename></link>
> variable to specify a particular version. You can influence the order
> by using the
> -            <link
> linkend='var-DEFAULT_PREFERENCE'><filename>DEFAULT_PREFERENCE</filename></link>
> +            <link
> linkend='var-bb-DEFAULT_PREFERENCE'><filename>DEFAULT_PREFERENCE</filename></link>
> variable. </para>
>  
> @@ -464,7 +464,7 @@
>              BitBake defaults to selecting the most recent
>              version, unless otherwise specified.
>              If the recipe in question has a
> -            <link
> linkend='var-DEFAULT_PREFERENCE'><filename>DEFAULT_PREFERENCE</filename></link>
> +            <link
> linkend='var-bb-DEFAULT_PREFERENCE'><filename>DEFAULT_PREFERENCE</filename></link>
> set lower than the other recipes (default is 0), then it will not be
> selected. This allows the person or persons maintaining
> @@ -475,9 +475,9 @@
>  
>          <para>
>              If the first recipe is named
> <filename>a_1.1.bb</filename>, then the
> -            <link linkend='var-PN'><filename>PN</filename></link>
> variable
> +            <link linkend='var-bb-PN'><filename>PN</filename></link>
> variable will be set to “a”, and the
> -            <link linkend='var-PV'><filename>PV</filename></link>
> +            <link linkend='var-bb-PV'><filename>PV</filename></link>
>              variable will be set to 1.1.
>          </para>
>  
> @@ -532,11 +532,11 @@
>          <para>
>              Dependencies are defined through several variables.
>              You can find information about variables BitBake uses in
> -            the <link linkend='ref-variables-glos'>Variables
> Glossary</link>
> +            the <link linkend='ref-bb-variables-glos'>Variables
> Glossary</link> near the end of this manual.
>              At a basic level, it is sufficient to know that BitBake
> uses the
> -            <link
> linkend='var-DEPENDS'><filename>DEPENDS</filename></link> and
> -            <link
> linkend='var-RDEPENDS'><filename>RDEPENDS</filename></link> variables
> when
> +            <link
> linkend='var-bb-DEPENDS'><filename>DEPENDS</filename></link> and
> +            <link
> linkend='var-bb-RDEPENDS'><filename>RDEPENDS</filename></link>
> variables when calculating dependencies. </para>
>  
> @@ -560,7 +560,7 @@
>  
>          <para>
>              The build now starts with BitBake forking off threads up
> to the limit set in the
> -            <link
> linkend='var-BB_NUMBER_THREADS'><filename>BB_NUMBER_THREADS</filename></link>
> +            <link
> linkend='var-bb-BB_NUMBER_THREADS'><filename>BB_NUMBER_THREADS</filename></link>
> variable. BitBake continues to fork threads as long as there are
> tasks ready to run, those tasks have all their dependencies met, and
> the thread threshold has not been @@ -574,7 +574,7 @@
>  
>          <para>
>              As each task completes, a timestamp is written to the
> directory specified by the
> -            <link
> linkend='var-STAMP'><filename>STAMP</filename></link> variable.
> +            <link
> linkend='var-bb-STAMP'><filename>STAMP</filename></link> variable. On
> subsequent runs, BitBake looks in the build directory within
> <filename>tmp/stamps</filename> and does not rerun tasks that are
> already completed unless a timestamp is found to be invalid. @@
> -618,7 +618,7 @@ <para>
>              Tasks can be either a shell task or a Python task.
>              For shell tasks, BitBake writes a shell script to
> -            <filename>${</filename><link
> linkend='var-T'><filename>T</filename></link><filename>}/run.do_taskname.pid</filename>
> +            <filename>${</filename><link
> linkend='var-bb-T'><filename>T</filename></link><filename>}/run.do_taskname.pid</filename>
> and then executes the script. The generated shell script contains all
> the exported variables, and the shell functions with all variables
> expanded. @@ -645,10 +645,10 @@
>              behavior:
>              <itemizedlist>
>                  <listitem><para>
> -                    <link
> linkend='var-BB_SCHEDULER'><filename>BB_SCHEDULER</filename></link>
> +                    <link
> linkend='var-bb-BB_SCHEDULER'><filename>BB_SCHEDULER</filename></link>
> </para></listitem> <listitem><para>
> -                    <link
> linkend='var-BB_SCHEDULERS'><filename>BB_SCHEDULERS</filename></link>
> +                    <link
> linkend='var-bb-BB_SCHEDULERS'><filename>BB_SCHEDULERS</filename></link>
> </para></listitem> </itemizedlist>
>              It is possible to have functions run before and after a
> task's main @@ -684,7 +684,7 @@
>              The simplistic approach for excluding the working
> directory is to set it to some fixed value and create the checksum
> for the "run" script. BitBake goes one step better and uses the
> -            <link
> linkend='var-BB_HASHBASE_WHITELIST'><filename>BB_HASHBASE_WHITELIST</filename></link>
> +            <link
> linkend='var-bb-BB_HASHBASE_WHITELIST'><filename>BB_HASHBASE_WHITELIST</filename></link>
> variable to define a list of variables that should never be included
> when generating the signatures. </para>
> @@ -795,7 +795,7 @@
>              This results in any metadata change that changes the
> task hash, automatically causing the task to be run again.
>              This removes the need to bump
> -            <link linkend='var-PR'><filename>PR</filename></link>
> +            <link linkend='var-bb-PR'><filename>PR</filename></link>
>              values, and changes to metadata automatically ripple
> across the build. </para>
>  
> @@ -884,7 +884,7 @@
>  
>          <para>
>              BitBake first calls the function defined by the
> -            <link
> linkend='var-BB_HASHCHECK_FUNCTION'><filename>BB_HASHCHECK_FUNCTION</filename></link>
> +            <link
> linkend='var-bb-BB_HASHCHECK_FUNCTION'><filename>BB_HASHCHECK_FUNCTION</filename></link>
> variable with a list of tasks and corresponding hashes it wants to
> build. This function is designed to be fast and returns a list
> @@ -908,7 +908,7 @@
>              For example, it is pointless to obtain a compiler if you
>              already have the compiled binary.
>              To handle this, BitBake calls the
> -            <link
> linkend='var-BB_SETSCENE_DEPVALID'><filename>BB_SETSCENE_DEPVALID</filename></link>
> +            <link
> linkend='var-bb-BB_SETSCENE_DEPVALID'><filename>BB_SETSCENE_DEPVALID</filename></link>
> function for each successful setscene task to know whether or not it
> needs to obtain the dependencies of that task. </para>
> @@ -916,7 +916,7 @@
>          <para>
>              Finally, after all the setscene tasks have executed,
> BitBake calls the function listed in
> -            <link
> linkend='var-BB_SETSCENE_VERIFY_FUNCTION2'><filename>BB_SETSCENE_VERIFY_FUNCTION2</filename></link>
> +            <link
> linkend='var-bb-BB_SETSCENE_VERIFY_FUNCTION2'><filename>BB_SETSCENE_VERIFY_FUNCTION2</filename></link>
> with the list of tasks BitBake thinks has been "covered". The
> metadata can then ensure that this list is correct and can inform
> BitBake that it wants specific tasks to be run regardless diff --git
> a/bitbake/doc/bitbake-user-manual/bitbake-user-manual-fetching.xml
> b/bitbake/doc/bitbake-user-manual/bitbake-user-manual-fetching.xml
> index 29ae486..6840408 100644 ---
> a/bitbake/doc/bitbake-user-manual/bitbake-user-manual-fetching.xml
> +++
> b/bitbake/doc/bitbake-user-manual/bitbake-user-manual-fetching.xml @@
> -44,7 +44,7 @@ </literallayout> This code sets up an instance of the
> fetch class. The instance uses a space-separated list of URLs from the
> -            <link
> linkend='var-SRC_URI'><filename>SRC_URI</filename></link>
> +            <link
> linkend='var-bb-SRC_URI'><filename>SRC_URI</filename></link> variable
> and then calls the <filename>download</filename> method to download
> the files. </para>
> @@ -78,7 +78,7 @@
>                  <listitem><para><emphasis>Pre-mirror
> Sites:</emphasis> BitBake first uses pre-mirrors to try and find
> source files. These locations are defined using the
> -                    <link
> linkend='var-PREMIRRORS'><filename>PREMIRRORS</filename></link>
> +                    <link
> linkend='var-bb-PREMIRRORS'><filename>PREMIRRORS</filename></link>
> variable. </para></listitem>
>                  <listitem><para><emphasis>Source URI:</emphasis>
> @@ -88,7 +88,7 @@
>                  <listitem><para><emphasis>Mirror Sites:</emphasis>
>                      If fetch failures occur, BitBake next uses
> mirror locations as defined by the
> -                    <link
> linkend='var-MIRRORS'><filename>MIRRORS</filename></link>
> +                    <link
> linkend='var-bb-MIRRORS'><filename>MIRRORS</filename></link> variable.
>                      </para></listitem>
>              </itemizedlist>
> @@ -144,7 +144,7 @@
>              Any source files that are not local (i.e.
>              downloaded from the Internet) are placed into the
> download directory, which is specified by the
> -            <link
> linkend='var-DL_DIR'><filename>DL_DIR</filename></link>
> +            <link
> linkend='var-bb-DL_DIR'><filename>DL_DIR</filename></link> variable.
>          </para>
>  
> @@ -184,11 +184,11 @@
>  
>          <para>
>              If
> -            <link
> linkend='var-BB_STRICT_CHECKSUM'><filename>BB_STRICT_CHECKSUM</filename></link>
> +            <link
> linkend='var-bb-BB_STRICT_CHECKSUM'><filename>BB_STRICT_CHECKSUM</filename></link>
> is set, any download without a checksum triggers an error message.
>              The
> -            <link
> linkend='var-BB_NO_NETWORK'><filename>BB_NO_NETWORK</filename></link>
> +            <link
> linkend='var-bb-BB_NO_NETWORK'><filename>BB_NO_NETWORK</filename></link>
> variable can be used to make any attempted network access a fatal
> error, which is useful for checking that mirrors are complete as well
> as other things. @@ -265,11 +265,11 @@
>                  The filename you specify within the URL can be
>                  either an absolute or relative path to a file.
>                  If the filename is relative, the contents of the
> -                <link
> linkend='var-FILESPATH'><filename>FILESPATH</filename></link>
> +                <link
> linkend='var-bb-FILESPATH'><filename>FILESPATH</filename></link>
> variable is used in the same way <filename>PATH</filename> is used to
> find executables. If the file cannot be found, it is assumed that it
> is available in
> -                <link
> linkend='var-DL_DIR'><filename>DL_DIR</filename></link>
> +                <link
> linkend='var-bb-DL_DIR'><filename>DL_DIR</filename></link> by the
> time the <filename>download()</filename> method is called. </para>
>  
> @@ -304,7 +304,7 @@
>                  allows the name of the downloaded file to be
> specified. Specifying the name of the downloaded file is useful
>                  for avoiding collisions in
> -                <link
> linkend='var-DL_DIR'><filename>DL_DIR</filename></link>
> +                <link
> linkend='var-bb-DL_DIR'><filename>DL_DIR</filename></link> when
> dealing with multiple files that have the same name. </para>
>  
> @@ -355,7 +355,7 @@
>                          A special value of "now" causes the checkout
> to be updated on every build.
>                          </para></listitem>
> -                    <listitem><para><emphasis><link
> linkend='var-CVSDIR'><filename>CVSDIR</filename></link>:</emphasis>
> +                    <listitem><para><emphasis><link
> linkend='var-bb-CVSDIR'><filename>CVSDIR</filename></link>:</emphasis>
> Specifies where a temporary checkout is saved. The location is often
> <filename>DL_DIR/cvs</filename>. </para></listitem>
> @@ -395,7 +395,7 @@
>                      <listitem><para><emphasis>"date":</emphasis>
>                          Specifies a date.
>                          If no "date" is specified, the
> -                        <link
> linkend='var-SRCDATE'><filename>SRCDATE</filename></link>
> +                        <link
> linkend='var-bb-SRCDATE'><filename>SRCDATE</filename></link> of the
> configuration is used to checkout a specific date. The special value
> of "now" causes the checkout to be updated on every build.
> @@ -406,7 +406,7 @@
>                          to which the module is unpacked.
>                          You are forcing the module into a special
>                          directory relative to
> -                        <link
> linkend='var-CVSDIR'><filename>CVSDIR</filename></link>.
> +                        <link
> linkend='var-bb-CVSDIR'><filename>CVSDIR</filename></link>.
> </para></listitem> <listitem><para><emphasis>"rsh"</emphasis>
>                          Used in conjunction with the "method"
> parameter. @@ -448,7 +448,7 @@
>                  <filename>FETCHCMD_svn</filename>, which defaults
>                  to "svn".
>                  The fetcher's temporary working directory is set by
> -                <link
> linkend='var-SVNDIR'><filename>SVNDIR</filename></link>,
> +                <link
> linkend='var-bb-SVNDIR'><filename>SVNDIR</filename></link>, which is
> usually <filename>DL_DIR/svn</filename>. </para>
>  
> @@ -509,7 +509,7 @@
>                  source control system.
>                  The fetcher works by creating a bare clone of the
>                  remote into
> -                <link
> linkend='var-GITDIR'><filename>GITDIR</filename></link>,
> +                <link
> linkend='var-bb-GITDIR'><filename>GITDIR</filename></link>, which is
> usually <filename>DL_DIR/git2</filename>. This bare clone is then
> cloned into the work directory during the unpack stage when a
> specific tree is checked out. @@ -588,6 +588,14 @@
>                          The name of the path in which to place the
> checkout. By default, the path is <filename>git/</filename>.
>                          </para></listitem>
> +                    <listitem><para><emphasis>"usehead":</emphasis>
> +                        Enables local <filename>git://</filename>
> URLs to use the
> +                        current branch HEAD as the revision for use
> with
> +                        <filename>AUTOREV</filename>.
> +                        The "usehead" parameter implies no branch
> and only works
> +                        when the transfer protocol is
> +                        <filename>file://</filename>.
> +                        </para></listitem>
>                  </itemizedlist>
>                  Here are some example URLs:
>                  <literallayout class='monospaced'>
> @@ -604,7 +612,7 @@
>                  This fetcher submodule inherits from the
>                  <link linkend='git-fetcher'>Git fetcher</link> and
> extends that fetcher's behavior by fetching a repository's submodules.
> -                <link
> linkend='var-SRC_URI'><filename>SRC_URI</filename></link>
> +                <link
> linkend='var-bb-SRC_URI'><filename>SRC_URI</filename></link> is
> passed to the Git fetcher as described in the "<link
> linkend='git-fetcher'>Git Fetcher
> (<filename>git://</filename>)</link>" section. @@ -639,9 +647,9 @@
>  
>              <para>
>                  To use this fetcher, make sure your recipe has proper
> -                <link
> linkend='var-SRC_URI'><filename>SRC_URI</filename></link>,
> -                <link
> linkend='var-SRCREV'><filename>SRCREV</filename></link>, and
> -                <link
> linkend='var-PV'><filename>PV</filename></link> settings.
> +                <link
> linkend='var-bb-SRC_URI'><filename>SRC_URI</filename></link>,
> +                <link
> linkend='var-bb-SRCREV'><filename>SRCREV</filename></link>, and
> +                <link
> linkend='var-bb-PV'><filename>PV</filename></link> settings. Here is
> an example: <literallayout class='monospaced'>
>       SRC_URI =
> "ccrc://cc.example.org/ccrc;vob=/example_vob;module=/example_module"
> @@ -726,15 +734,15 @@ <filename>FETCHCMD_p4</filename>, which defaults
>                  to "p4".
>                  The fetcher's temporary working directory is set by
> -                <link
> linkend='var-P4DIR'><filename>P4DIR</filename></link>,
> +                <link
> linkend='var-bb-P4DIR'><filename>P4DIR</filename></link>, which
> defaults to "DL_DIR/p4". </para>
>  
>              <para>
>                  To use this fetcher, make sure your recipe has proper
> -                <link
> linkend='var-SRC_URI'><filename>SRC_URI</filename></link>,
> -                <link
> linkend='var-SRCREV'><filename>SRCREV</filename></link>, and
> -                <link
> linkend='var-PV'><filename>PV</filename></link> values.
> +                <link
> linkend='var-bb-SRC_URI'><filename>SRC_URI</filename></link>,
> +                <link
> linkend='var-bb-SRCREV'><filename>SRCREV</filename></link>, and
> +                <link
> linkend='var-bb-PV'><filename>PV</filename></link> values. The p4
> executable is able to use the config file defined by your system's
> <filename>P4CONFIG</filename> environment variable in order to define
> the Perforce server URL and port, username, and @@ -785,9 +793,9 @@
>                  <filename>google-repo</filename> source control
> system. The fetcher works by initiating and syncing sources of the
>                  repository into
> -                <link
> linkend='var-REPODIR'><filename>REPODIR</filename></link>,
> +                <link
> linkend='var-bb-REPODIR'><filename>REPODIR</filename></link>, which
> is usually
> -                <link
> linkend='var-DL_DIR'><filename>DL_DIR</filename></link><filename>/repo</filename>.
> +                <link
> linkend='var-bb-DL_DIR'><filename>DL_DIR</filename></link><filename>/repo</filename>.
> </para> 
>              <para>
> @@ -824,19 +832,22 @@
>                          Bazaar (<filename>bzr://</filename>)
>                          </para></listitem>
>                      <listitem><para>
> -                        Trees using Git Annex
> (<filename>gitannex://</filename>)
> +                        Mercurial (<filename>hg://</filename>)
>                          </para></listitem>
>                      <listitem><para>
> -                        Secure FTP (<filename>sftp://</filename>)
> +                        npm (<filename>npm://</filename>)
>                          </para></listitem>
>                      <listitem><para>
> -                        Secure Shell (<filename>ssh://</filename>)
> +                        OSC (<filename>osc://</filename>)
>                          </para></listitem>
>                      <listitem><para>
> -                        OSC (<filename>osc://</filename>)
> +                        Secure FTP (<filename>sftp://</filename>)
>                          </para></listitem>
>                      <listitem><para>
> -                        Mercurial (<filename>hg://</filename>)
> +                        Secure Shell (<filename>ssh://</filename>)
> +                        </para></listitem>
> +                    <listitem><para>
> +                        Trees using Git Annex
> (<filename>gitannex://</filename>) </para></listitem>
>                  </itemizedlist>
>                  No documentation currently exists for these lesser
> used diff --git
> a/bitbake/doc/bitbake-user-manual/bitbake-user-manual-hello.xml
> b/bitbake/doc/bitbake-user-manual/bitbake-user-manual-hello.xml index
> 9076f0f..39066e4 100644 ---
> a/bitbake/doc/bitbake-user-manual/bitbake-user-manual-hello.xml +++
> b/bitbake/doc/bitbake-user-manual/bitbake-user-manual-hello.xml @@
> -194,7 +194,7 @@ <para> When you run BitBake, it begins looking for
> metadata files. The
> -                <link
> linkend='var-BBPATH'><filename>BBPATH</filename></link>
> +                <link
> linkend='var-bb-BBPATH'><filename>BBPATH</filename></link> variable
> is what tells BitBake where to look for those files.
> <filename>BBPATH</filename> is not set and you need to set it.
> Without <filename>BBPATH</filename>, Bitbake cannot @@ -273,14
> +273,14 @@ some editor to create the <filename>bitbake.conf</filename>
>                  so that it contains the following:
>                  <literallayout class='monospaced'>
> -     <link linkend='var-PN'>PN</link>  =
> "${@bb.parse.BBHandler.vars_from_file(d.getVar('FILE', False),d)[0]
> or 'defaultpkgname'}"
> +     <link linkend='var-bb-PN'>PN</link>  =
> "${@bb.parse.BBHandler.vars_from_file(d.getVar('FILE', False),d)[0]
> or 'defaultpkgname'}" </literallayout> <literallayout
> class='monospaced'>
> -     TMPDIR  = "${<link linkend='var-TOPDIR'>TOPDIR</link>}/tmp"
> -     <link linkend='var-CACHE'>CACHE</link>   = "${TMPDIR}/cache"
> -     <link linkend='var-STAMP'>STAMP</link>   =
> "${TMPDIR}/${PN}/stamps"
> -     <link linkend='var-T'>T</link>       = "${TMPDIR}/${PN}/work"
> -     <link linkend='var-B'>B</link>       = "${TMPDIR}/${PN}"
> +     TMPDIR  = "${<link linkend='var-bb-TOPDIR'>TOPDIR</link>}/tmp"
> +     <link linkend='var-bb-CACHE'>CACHE</link>   = "${TMPDIR}/cache"
> +     <link linkend='var-bb-STAMP'>STAMP</link>   =
> "${TMPDIR}/${PN}/stamps"
> +     <link linkend='var-bb-T'>T</link>       = "${TMPDIR}/${PN}/work"
> +     <link linkend='var-bb-B'>B</link>       = "${TMPDIR}/${PN}"
>                  </literallayout>
>                  <note>
>                      Without a value for <filename>PN</filename>, the
> @@ -402,12 +402,12 @@
>                  Move to the <filename>conf</filename> directory and
> create a <filename>layer.conf</filename> file that has the following:
>                  <literallayout class='monospaced'>
> -     BBPATH .= ":${<link linkend='var-LAYERDIR'>LAYERDIR</link>}"
> +     BBPATH .= ":${<link linkend='var-bb-LAYERDIR'>LAYERDIR</link>}"
>  
> -     <link linkend='var-BBFILES'>BBFILES</link> += "${LAYERDIR}/*.bb"
> +     <link linkend='var-bb-BBFILES'>BBFILES</link> +=
> "${LAYERDIR}/*.bb" 
> -     <link
> linkend='var-BBFILE_COLLECTIONS'>BBFILE_COLLECTIONS</link> +=
> "mylayer"
> -     <link
> linkend='var-BBFILE_PATTERN'>BBFILE_PATTERN_mylayer</link> :=
> "^${LAYERDIR_RE}/"
> +     <link
> linkend='var-bb-BBFILE_COLLECTIONS'>BBFILE_COLLECTIONS</link> +=
> "mylayer"
> +     <link
> linkend='var-bb-BBFILE_PATTERN'>BBFILE_PATTERN_mylayer</link> :=
> "^${LAYERDIR_RE}/" </literallayout> For information on these
> variables, click the links to go to the definitions in the
> glossary.</para> @@ -416,9 +416,9 @@
>                  a recipe file named
> <filename>printhello.bb</filename> that has the following:
>                  <literallayout class='monospaced'>
> -     <link linkend='var-DESCRIPTION'>DESCRIPTION</link> = "Prints
> Hello World"
> -     <link linkend='var-PN'>PN</link> = 'printhello'
> -     <link linkend='var-PV'>PV</link> = '1'
> +     <link linkend='var-bb-DESCRIPTION'>DESCRIPTION</link> = "Prints
> Hello World"
> +     <link linkend='var-bb-PN'>PN</link> = 'printhello'
> +     <link linkend='var-bb-PV'>PV</link> = '1'
>  
>       python do_build() {
>          bb.plain("********************");
> diff --git
> a/bitbake/doc/bitbake-user-manual/bitbake-user-manual-intro.xml
> b/bitbake/doc/bitbake-user-manual/bitbake-user-manual-intro.xml index
> f7d312a..8f2a960 100644 ---
> a/bitbake/doc/bitbake-user-manual/bitbake-user-manual-intro.xml +++
> b/bitbake/doc/bitbake-user-manual/bitbake-user-manual-intro.xml @@
> -781,7 +781,7 @@ target, you must also enable BitBake to perform
> multiple configuration builds. Enabling is accomplished by setting the
> -                    <link
> linkend='var-BBMULTICONFIG'><filename>BBMULTICONFIG</filename></link>
> +                    <link
> linkend='var-bb-BBMULTICONFIG'><filename>BBMULTICONFIG</filename></link>
> variable in the <filename>local.conf</filename> configuration file.
>                      As an example, suppose you had configuration
> files @@ -791,7 +791,7 @@
>                      The following statement in the
>                      <filename>local.conf</filename> file both enables
>                      BitBake to perform multiple configuration builds
> and
> -                    specifies the two multiconfigs:
> +                    specifies the two extra multiconfigs:
>                      <literallayout class='monospaced'>
>       BBMULTICONFIG = "target1 target2"
>                      </literallayout>
> @@ -803,13 +803,13 @@
>                      builds, use the following command form to start
> the builds:
>                      <literallayout class='monospaced'>
> -     $ bitbake
> [multiconfig:<replaceable>multiconfigname</replaceable>:]<replaceable>target</replaceable>
> [[[multiconfig:<replaceable>multiconfigname</replaceable>:]<replaceable>target</replaceable>] ... ]
> +     $ bitbake
> [mc:<replaceable>multiconfigname</replaceable>:]<replaceable>target</replaceable>
> [[[mc:<replaceable>multiconfigname</replaceable>:]<replaceable>target</replaceable>] ... ]
> </literallayout>
> -                    Here is an example for two multiconfigs:
> +                    Here is an example for two extra multiconfigs:
>                      <filename>target1</filename> and
>                      <filename>target2</filename>:
>                      <literallayout class='monospaced'>
> -     $ bitbake multiconfig:target1:<replaceable>target</replaceable>
> multiconfig:target2:<replaceable>target</replaceable>
> +     $ bitbake mc::<replaceable>target</replaceable>
> mc:target1:<replaceable>target</replaceable>
> mc:target2:<replaceable>target</replaceable> </literallayout> </para>
>              </section>
> @@ -837,13 +837,13 @@
>                      build, you must declare the dependencies in the
> recipe using the following statement form:
>                      <literallayout class='monospaced'>
> -     <replaceable>task_or_package</replaceable>[mcdepends] =
> "multiconfig:<replaceable>from_multiconfig</replaceable>:<replaceable>to_multiconfig</replaceable>:<replaceable>recipe_name</replaceable>:<replaceable>task_on_which_to_depend</replaceable>"
> +     <replaceable>task_or_package</replaceable>[mcdepends] =
> "mc:<replaceable>from_multiconfig</replaceable>:<replaceable>to_multiconfig</replaceable>:<replaceable>recipe_name</replaceable>:<replaceable>task_on_which_to_depend</replaceable>"
> </literallayout> To better show how to use this statement, consider an
>                      example with two multiconfigs:
> <filename>target1</filename> and <filename>target2</filename>:
>                      <literallayout class='monospaced'>
> -     <replaceable>image_task</replaceable>[mcdepends] =
> "multiconfig:target1:target2:<replaceable>image2</replaceable>:<replaceable>rootfs_task</replaceable>"
> +     <replaceable>image_task</replaceable>[mcdepends] =
> "mc:target1:target2:<replaceable>image2</replaceable>:<replaceable>rootfs_task</replaceable>"
> </literallayout> In this example, the
>                      <replaceable>from_multiconfig</replaceable> is
> "target1" and @@ -859,7 +859,7 @@
>                     Once you set up this dependency, you can build the
>                     "target1" multiconfig using a BitBake command as
> follows: <literallayout class='monospaced'>
> -     $ bitbake multiconfig:target1:<replaceable>image1</replaceable>
> +     $ bitbake mc:target1:<replaceable>image1</replaceable>
>                     </literallayout>
>                     This command executes all the tasks needed to
> create <replaceable>image1</replaceable> for the "target1"
> @@ -875,7 +875,7 @@
>                     Consider this change to the statement in the
>                     <replaceable>image1</replaceable> recipe:
>                     <literallayout class='monospaced'>
> -     <replaceable>image_task</replaceable>[mcdepends] =
> "multiconfig:target1:target2:<replaceable>image2</replaceable>:<replaceable>image_task</replaceable>"
> +     <replaceable>image_task</replaceable>[mcdepends] =
> "mc:target1:target2:<replaceable>image2</replaceable>:<replaceable>image_task</replaceable>"
> </literallayout> In this case, BitBake must create
>                     <replaceable>image2</replaceable> for the
> "target2" diff --git
> a/bitbake/doc/bitbake-user-manual/bitbake-user-manual-metadata.xml
> b/bitbake/doc/bitbake-user-manual/bitbake-user-manual-metadata.xml
> index 2490f6e..421364c 100644 ---
> a/bitbake/doc/bitbake-user-manual/bitbake-user-manual-metadata.xml
> +++
> b/bitbake/doc/bitbake-user-manual/bitbake-user-manual-metadata.xml @@
> -61,6 +61,78 @@ </para> </section> 
> +        <section id='modifying-existing-variables'>
> +            <title>Modifying Existing Variables</title>
> +
> +            <para>
> +                Sometimes you need to modify existing variables.
> +                Following are some cases where you might find you
> want to
> +                modify an existing variable:
> +                <itemizedlist>
> +                    <listitem><para>
> +                        Customize a recipe that uses the variable.
> +                        </para></listitem>
> +                    <listitem><para>
> +                        Change a variable's default value used in a
> +                        <filename>*.bbclass</filename> file.
> +                        </para></listitem>
> +                    <listitem><para>
> +                        Change the variable in a
> <filename>*.bbappend</filename>
> +                        file to override the variable in the
> original recipe.
> +                        </para></listitem>
> +                    <listitem><para>
> +                        Change the variable in a configuration file
> so that the
> +                        value overrides an existing configuration.
> +                        </para></listitem>
> +                </itemizedlist>
> +            </para>
> +
> +            <para>
> +                Changing a variable value can sometimes depend on
> how the
> +                value was originally assigned and also on the desired
> +                intent of the change.
> +                In particular, when you append a value to a variable
> that
> +                has a default value, the resulting value might not
> be what
> +                you expect.
> +                In this case, the value you provide might replace
> the value
> +                rather than append to the default value.
> +            </para>
> +
> +            <para>
> +                If after you have changed a variable's value and
> something
> +                unexplained occurs, you can use BitBake to check the
> actual
> +                value of the suspect variable.
> +                You can make these checks for both configuration and
> recipe
> +                level changes:
> +                <itemizedlist>
> +                    <listitem><para>
> +                        For configuration changes, use the following:
> +                        <literallayout class='monospaced'>
> +     $ bitbake -e
> +                        </literallayout>
> +                        This command displays variable values after
> the
> +                        configuration files (i.e.
> <filename>local.conf</filename>,
> +                        <filename>bblayers.conf</filename>,
> +                        <filename>bitbake.conf</filename> and so
> forth) have
> +                        been parsed.
> +                        <note>
> +                            Variables that are exported to the
> environment are
> +                            preceded by the string "export" in the
> command's
> +                            output.
> +                        </note>
> +                        </para></listitem>
> +                    <listitem><para>
> +                        For recipe changes, use the following:
> +                        <literallayout class='monospaced'>
> +     $ bitbake <replaceable>recipe</replaceable> -e | grep VARIABLE="
> +                        </literallayout>
> +                        This command checks to see if the variable
> actually
> +                        makes it into a specific recipe.
> +                        </para></listitem>
> +                </itemizedlist>
> +            </para>
> +        </section>
> +
>          <section id='line-joining'>
>              <title>Line Joining</title>
>  
> @@ -297,9 +369,8 @@
>  
>              <para>
>                  These operators differ from the ":=", ".=", "=.",
> "+=", and "=+"
> -                operators in that their effects are deferred
> -                until after parsing completes rather than being
> immediately
> -                applied.
> +                operators in that their effects are applied at
> variable
> +                expansion time rather than being immediately applied.
>                  Here are some examples:
>                  <literallayout class='monospaced'>
>       B = "bval"
> @@ -348,18 +419,22 @@
>       FOO = "123 456 789 123456 123 456 123 456"
>       FOO_remove = "123"
>       FOO_remove = "456"
> -     FOO2 = "abc def ghi abcdef abc def abc def"
> -     FOO2_remove = "abc def"
> +     FOO2 = " abc  def ghi abcdef abc def abc  def def"
> +     FOO2_remove = " \
> +         def \
> +         abc \
> +         ghi \
> +     "
>                  </literallayout>
>                  The variable <filename>FOO</filename> becomes
> -                "&nbsp;&nbsp;789 123456&nbsp;&nbsp;&nbsp;&nbsp;"
> +                "&nbsp;&nbsp;789&nbsp;123456&nbsp;&nbsp;&nbsp;&nbsp;"
>                  and <filename>FOO2</filename> becomes
> -                "&nbsp;&nbsp;ghi abcdef&nbsp;&nbsp;&nbsp;&nbsp;".
> +
> "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;jkl&nbsp;&nbsp;abcdef&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;".
> </para> 
>              <para>
>                  Like "_append" and "_prepend", "_remove"
> -                is deferred until after parsing completes.
> +                is applied at variable expansion time.
>              </para>
>          </section>
>  
> @@ -503,7 +578,7 @@
>          </section>
>  
>          <section id='unsetting-variables'>
> -            <title>Unseting variables</title>
> +            <title>Unsetting variables</title>
>  
>              <para>
>                  It is possible to completely remove a variable or a
> variable flag @@ -595,7 +670,7 @@
>  
>          <para>
>              BitBake uses
> -            <link
> linkend='var-OVERRIDES'><filename>OVERRIDES</filename></link>
> +            <link
> linkend='var-bb-OVERRIDES'><filename>OVERRIDES</filename></link> to
> control what variables are overridden after BitBake parses recipes
> and configuration files. This section describes how you can use
> @@ -705,7 +780,7 @@
>  
>                          <para>Internally, this is implemented by
> prepending the task (e.g. "task-compile:") to the value of
> -                        <link
> linkend='var-OVERRIDES'><filename>OVERRIDES</filename></link>
> +                        <link
> linkend='var-bb-OVERRIDES'><filename>OVERRIDES</filename></link> for
> the local datastore of the <filename>do_compile</filename>
> task.</para> 
> @@ -724,17 +799,15 @@
>              <title>Key Expansion</title>
>  
>              <para>
> -                Key expansion happens when the BitBake datastore is
> finalized
> -                just before BitBake expands overrides.
> +                Key expansion happens when the BitBake datastore is
> finalized. To better understand this, consider the following example:
>                  <literallayout class='monospaced'>
>       A${B} = "X"
>       B = "2"
>       A2 = "Y"
>                  </literallayout>
> -                In this case, after all the parsing is complete, and
> -                before any overrides are handled, BitBake expands
> -                <filename>${B}</filename> into "2".
> +                In this case, after all the parsing is complete,
> +                BitBake expands <filename>${B}</filename> into "2".
>                  This expansion causes <filename>A2</filename>, which
> was set to "Y" before the expansion, to become "X".
>              </para>
> @@ -868,7 +941,7 @@
>  
>              <para>
>                  BitBake uses the
> -                <link
> linkend='var-BBPATH'><filename>BBPATH</filename></link>
> +                <link
> linkend='var-bb-BBPATH'><filename>BBPATH</filename></link> variable
> to locate needed include and class files. Additionally, BitBake
> searches the current directory for <filename>include</filename> and
> <filename>require</filename> @@ -1086,7 +1159,7 @@
>              <para>
>                  When creating a configuration file
> (<filename>.conf</filename>), you can use the
> -                <link
> linkend='var-INHERIT'><filename>INHERIT</filename></link>
> +                <link
> linkend='var-bb-INHERIT'><filename>INHERIT</filename></link>
> configuration directive to inherit a class. BitBake only supports
> this directive when used within a configuration file.
> @@ -1370,7 +1443,7 @@
>                          </para></listitem>
>                      <listitem><para>
>                          BitBake-style Python functions generate a
> separate
> -                        <filename>${</filename><link
> linkend='var-T'><filename>T</filename></link><filename>}/run.</filename><replaceable>function-name</replaceable><filename>.</filename><replaceable>pid</replaceable>
> +                        <filename>${</filename><link
> linkend='var-bb-T'><filename>T</filename></link><filename>}/run.</filename><replaceable>function-name</replaceable><filename>.</filename><replaceable>pid</replaceable>
> script that is executed to run the function, and also generate a log
> file in
> <filename>${T}/log.</filename><replaceable>function-name</replaceable><filename>.</filename><replaceable>pid</replaceable>
> @@ -1773,7 +1846,7 @@ things exported or listed in its whitelist to
> ensure that the build environment is reproducible and consistent.
>                      You can prevent this "cleaning" by setting the
> -                    <link
> linkend='var-BB_PRESERVE_ENV'><filename>BB_PRESERVE_ENV</filename></link>
> +                    <link
> linkend='var-bb-BB_PRESERVE_ENV'><filename>BB_PRESERVE_ENV</filename></link>
> variable. </note>
>                  Consequently, if you do want something to get passed
> into the @@ -1783,9 +1856,9 @@
>                          Tell BitBake to load what you want from the
> environment into the datastore.
>                          You can do so through the
> -                        <link
> linkend='var-BB_ENV_WHITELIST'><filename>BB_ENV_WHITELIST</filename></link>
> +                        <link
> linkend='var-bb-BB_ENV_WHITELIST'><filename>BB_ENV_WHITELIST</filename></link>
> and
> -                        <link
> linkend='var-BB_ENV_EXTRAWHITE'><filename>BB_ENV_EXTRAWHITE</filename></link>
> +                        <link
> linkend='var-bb-BB_ENV_EXTRAWHITE'><filename>BB_ENV_EXTRAWHITE</filename></link>
> variables. For example, assume you want to prevent the build system
> from accessing your <filename>$HOME/.ccache</filename>
> @@ -1824,7 +1897,7 @@
>                  from the original execution environment.
>                  Bitbake saves a copy of the original environment into
>                  a special variable named
> -                <link
> linkend='var-BB_ORIGENV'><filename>BB_ORIGENV</filename></link>.
> +                <link
> linkend='var-bb-BB_ORIGENV'><filename>BB_ORIGENV</filename></link>.
> </para> 
>              <para>
> @@ -1883,7 +1956,7 @@
>                  <listitem><para><emphasis><filename>[depends]</filename>:</emphasis>
>                      Controls inter-task dependencies.
>                      See the
> -                    <link
> linkend='var-DEPENDS'><filename>DEPENDS</filename></link>
> +                    <link
> linkend='var-bb-DEPENDS'><filename>DEPENDS</filename></link> variable
> and the "<link linkend='inter-task-dependencies'>Inter-Task
> Dependencies</link>" section for more information.
> @@ -1891,7 +1964,7 @@
>                  <listitem><para><emphasis><filename>[deptask]</filename>:</emphasis>
>                      Controls task build-time dependencies.
>                      See the
> -                    <link
> linkend='var-DEPENDS'><filename>DEPENDS</filename></link>
> +                    <link
> linkend='var-bb-DEPENDS'><filename>DEPENDS</filename></link> variable
> and the "<link linkend='build-dependencies'>Build Dependencies</link>"
>                      section for more information.
> @@ -1937,7 +2010,7 @@
>                      of cores but certain tasks need to be
> rate-limited due to various kinds of resource constraints (e.g. to
> avoid network throttling). <filename>number_threads</filename> works
> similarly to the
> -                    <link
> linkend='var-BB_NUMBER_THREADS'><filename>BB_NUMBER_THREADS</filename></link>
> +                    <link
> linkend='var-bb-BB_NUMBER_THREADS'><filename>BB_NUMBER_THREADS</filename></link>
> variable but is task-specific.</para> 
>                      <para>Set the value globally.
> @@ -1971,9 +2044,9 @@
>                  <listitem><para><emphasis><filename>[rdepends]</filename>:</emphasis>
>                      Controls inter-task runtime dependencies.
>                      See the
> -                    <link
> linkend='var-RDEPENDS'><filename>RDEPENDS</filename></link>
> +                    <link
> linkend='var-bb-RDEPENDS'><filename>RDEPENDS</filename></link>
> variable, the
> -                    <link
> linkend='var-RRECOMMENDS'><filename>RRECOMMENDS</filename></link>
> +                    <link
> linkend='var-bb-RRECOMMENDS'><filename>RRECOMMENDS</filename></link>
> variable, and the "<link linkend='inter-task-dependencies'>Inter-Task
> Dependencies</link>" section for more information.
> @@ -1981,9 +2054,9 @@
>                  <listitem><para><emphasis><filename>[rdeptask]</filename>:</emphasis>
>                      Controls task runtime dependencies.
>                      See the
> -                    <link
> linkend='var-RDEPENDS'><filename>RDEPENDS</filename></link>
> +                    <link
> linkend='var-bb-RDEPENDS'><filename>RDEPENDS</filename></link>
> variable, the
> -                    <link
> linkend='var-RRECOMMENDS'><filename>RRECOMMENDS</filename></link>
> +                    <link
> linkend='var-bb-RRECOMMENDS'><filename>RRECOMMENDS</filename></link>
> variable, and the "<link linkend='runtime-dependencies'>Runtime
> Dependencies</link>" section for more information.
> @@ -1996,9 +2069,9 @@
>                  <listitem><para><emphasis><filename>[recrdeptask]</filename>:</emphasis>
>                      Controls task recursive runtime dependencies.
>                      See the
> -                    <link
> linkend='var-RDEPENDS'><filename>RDEPENDS</filename></link>
> +                    <link
> linkend='var-bb-RDEPENDS'><filename>RDEPENDS</filename></link>
> variable, the
> -                    <link
> linkend='var-RRECOMMENDS'><filename>RRECOMMENDS</filename></link>
> +                    <link
> linkend='var-bb-RRECOMMENDS'><filename>RRECOMMENDS</filename></link>
> variable, and the "<link linkend='recursive-dependencies'>Recursive
> Dependencies</link>" section for more information.
> @@ -2127,7 +2200,7 @@
>                      Any given datastore only has one such event
> executed against it, however.
>                      If
> -                    <link
> linkende='var-BB_INVALIDCONF'><filename>BB_INVALIDCONF</filename></link>
> +                    <link
> linkende='var-bb-BB_INVALIDCONF'><filename>BB_INVALIDCONF</filename></link>
> is set in the datastore by the event handler, the configuration is
> reparsed and a new event triggered, allowing the metadata to update
> configuration. @@ -2256,17 +2329,17 @@
>              from a single recipe file multiple incarnations of that
>              recipe file where all incarnations are buildable.
>              These features are enabled through the
> -            <link
> linkend='var-BBCLASSEXTEND'><filename>BBCLASSEXTEND</filename></link>
> +            <link
> linkend='var-bb-BBCLASSEXTEND'><filename>BBCLASSEXTEND</filename></link>
> and
> -            <link
> linkend='var-BBVERSIONS'><filename>BBVERSIONS</filename></link>
> +            <link
> linkend='var-bb-BBVERSIONS'><filename>BBVERSIONS</filename></link>
> variables. <note>
>                  The mechanism for this class extension is extremely
>                  specific to the implementation.
>                  Usually, the recipe's
> -                <link
> linkend='var-PROVIDES'><filename>PROVIDES</filename></link>,
> -                <link
> linkend='var-PN'><filename>PN</filename></link>, and
> -                <link
> linkend='var-DEPENDS'><filename>DEPENDS</filename></link>
> +                <link
> linkend='var-bb-PROVIDES'><filename>PROVIDES</filename></link>,
> +                <link
> linkend='var-bb-PN'><filename>PN</filename></link>, and
> +                <link
> linkend='var-bb-DEPENDS'><filename>DEPENDS</filename></link>
> variables would need to be modified by the extension class. For
> specific examples, see the OE-Core <filename>native</filename>,
> <filename>nativesdk</filename>, @@ -2287,7 +2360,7 @@
>                      project from a single recipe file.
>                      You can also specify conditional metadata
>                      (using the
> -                    <link
> linkend='var-OVERRIDES'><filename>OVERRIDES</filename></link>
> +                    <link
> linkend='var-bb-OVERRIDES'><filename>OVERRIDES</filename></link>
> mechanism) for a single version, or an optionally named range of
> versions. Here is an example: <literallayout class='monospaced'>
> @@ -2306,7 +2379,7 @@
>                      into overrides, but it is also made available
> for the metadata to use in the variable that defines the base recipe
> versions for use in <filename>file://</filename> search paths
> -                    (<link
> linkend='var-FILESPATH'><filename>FILESPATH</filename></link>).
> +                    (<link
> linkend='var-bb-FILESPATH'><filename>FILESPATH</filename></link>).
> </para></listitem> </itemizedlist>
>          </para>
> @@ -2408,7 +2481,7 @@
>  
>              <para>
>                  BitBake uses the
> -                <link
> linkend='var-DEPENDS'><filename>DEPENDS</filename></link>
> +                <link
> linkend='var-bb-DEPENDS'><filename>DEPENDS</filename></link> variable
> to manage build time dependencies. The <filename>[deptask]</filename>
> varflag for tasks signifies the task of each
> @@ -2429,9 +2502,9 @@
>  
>              <para>
>                  BitBake uses the
> -                <link
> linkend='var-PACKAGES'><filename>PACKAGES</filename></link>,
> -                <link
> linkend='var-RDEPENDS'><filename>RDEPENDS</filename></link>, and
> -                <link
> linkend='var-RRECOMMENDS'><filename>RRECOMMENDS</filename></link>
> +                <link
> linkend='var-bb-PACKAGES'><filename>PACKAGES</filename></link>,
> +                <link
> linkend='var-bb-RDEPENDS'><filename>RDEPENDS</filename></link>, and
> +                <link
> linkend='var-bb-RRECOMMENDS'><filename>RRECOMMENDS</filename></link>
> variables to manage runtime dependencies. </para>
>  
> @@ -2686,7 +2759,7 @@
>  
>          <para>
>              These checksums are stored in
> -            <link
> linkend='var-STAMP'><filename>STAMP</filename></link>.
> +            <link
> linkend='var-bb-STAMP'><filename>STAMP</filename></link>. You can
> examine the checksums using the following BitBake command:
> <literallayout class='monospaced'> $ bitbake-dumpsigs
> @@ -2708,44 +2781,44 @@
>              The following list describes related variables:
>              <itemizedlist>
>                  <listitem><para>
> -                    <link
> linkend='var-BB_HASHCHECK_FUNCTION'><filename>BB_HASHCHECK_FUNCTION</filename></link>:
> +                    <link
> linkend='var-bb-BB_HASHCHECK_FUNCTION'><filename>BB_HASHCHECK_FUNCTION</filename></link>:
> Specifies the name of the function to call during the "setscene" part
> of the task's execution in order to validate the list of task hashes.
>                      </para></listitem>
>                  <listitem><para>
> -                    <link
> linkend='var-BB_SETSCENE_DEPVALID'><filename>BB_SETSCENE_DEPVALID</filename></link>:
> +                    <link
> linkend='var-bb-BB_SETSCENE_DEPVALID'><filename>BB_SETSCENE_DEPVALID</filename></link>:
> Specifies a function BitBake calls that determines whether BitBake
> requires a setscene dependency to be met.
>                      </para></listitem>
>                  <listitem><para>
> -                    <link
> linkend='var-BB_SETSCENE_VERIFY_FUNCTION2'><filename>BB_SETSCENE_VERIFY_FUNCTION2</filename></link>:
> +                    <link
> linkend='var-bb-BB_SETSCENE_VERIFY_FUNCTION2'><filename>BB_SETSCENE_VERIFY_FUNCTION2</filename></link>:
> Specifies a function to call that verifies the list of planned task
> execution before the main task execution happens.
>                      </para></listitem>
>                  <listitem><para>
> -                    <link
> linkend='var-BB_STAMP_POLICY'><filename>BB_STAMP_POLICY</filename></link>:
> +                    <link
> linkend='var-bb-BB_STAMP_POLICY'><filename>BB_STAMP_POLICY</filename></link>:
> Defines the mode for comparing timestamps of stamp files.
> </para></listitem> <listitem><para>
> -                    <link
> linkend='var-BB_STAMP_WHITELIST'><filename>BB_STAMP_WHITELIST</filename></link>:
> +                    <link
> linkend='var-bb-BB_STAMP_WHITELIST'><filename>BB_STAMP_WHITELIST</filename></link>:
> Lists stamp files that are looked at when the stamp policy is
> "whitelist". </para></listitem>
>                  <listitem><para>
> -                    <link
> linkend='var-BB_TASKHASH'><filename>BB_TASKHASH</filename></link>:
> +                    <link
> linkend='var-bb-BB_TASKHASH'><filename>BB_TASKHASH</filename></link>:
> Within an executing task, this variable holds the hash of the task as
> returned by the currently enabled signature generator.
>                      </para></listitem>
>                  <listitem><para>
> -                    <link
> linkend='var-STAMP'><filename>STAMP</filename></link>:
> +                    <link
> linkend='var-bb-STAMP'><filename>STAMP</filename></link>: The base
> path to create stamp files. </para></listitem>
>                  <listitem><para>
> -                    <link
> linkend='var-STAMPCLEAN'><filename>STAMPCLEAN</filename></link>:
> +                    <link
> linkend='var-bb-STAMPCLEAN'><filename>STAMPCLEAN</filename></link>:
> Again, the base path to create stamp files but can use wildcards for
> matching a range of files for clean operations. </para></listitem>
> diff --git
> a/bitbake/doc/bitbake-user-manual/bitbake-user-manual-ref-variables.xml
> b/bitbake/doc/bitbake-user-manual/bitbake-user-manual-ref-variables.xml
> index a84b2bc..aca6741 100644 ---
> a/bitbake/doc/bitbake-user-manual/bitbake-user-manual-ref-variables.xml
> +++
> b/bitbake/doc/bitbake-user-manual/bitbake-user-manual-ref-variables.xml
> @@ -3,7 +3,7 @@ [<!ENTITY % poky SYSTEM "../poky.ent"> %poky; ] >
> <!-- Dummy chapter --> -<chapter id='ref-variables-glos'>
> +<chapter id='ref-bb-variables-glos'>
>  
>  <title>Variables Glossary</title>
>  
> @@ -34,29 +34,29 @@
>      </itemizedlist>
>  </note>
>  
> -<glossary id='ref-variables-glossary'>
> +<glossary id='ref-bb-variables-glossary'>
>  
>      <para>
> -       <link linkend='var-ASSUME_PROVIDED'>A</link>
> -       <link linkend='var-B'>B</link>
> -       <link linkend='var-CACHE'>C</link>
> -       <link linkend='var-DEFAULT_PREFERENCE'>D</link>
> -       <link linkend='var-EXCLUDE_FROM_WORLD'>E</link>
> -       <link linkend='var-FAKEROOT'>F</link>
> -       <link linkend='var-GITDIR'>G</link>
> -       <link linkend='var-HGDIR'>H</link>
> -<!--       <link linkend='var-ICECC_DISABLED'>I</link> -->
> +       <link linkend='var-bb-ASSUME_PROVIDED'>A</link>
> +       <link linkend='var-bb-B'>B</link>
> +       <link linkend='var-bb-CACHE'>C</link>
> +       <link linkend='var-bb-DEFAULT_PREFERENCE'>D</link>
> +       <link linkend='var-bb-EXCLUDE_FROM_WORLD'>E</link>
> +       <link linkend='var-bb-FAKEROOT'>F</link>
> +       <link linkend='var-bb-GITDIR'>G</link>
> +       <link linkend='var-bb-HGDIR'>H</link>
> +       <link linkend='var-bb-INHERIT'>I</link>
>  <!--               <link linkend='var-glossary-j'>J</link> -->
>  <!--       <link linkend='var-KARCH'>K</link> -->
> -       <link linkend='var-LAYERDEPENDS'>L</link>
> -       <link linkend='var-MIRRORS'>M</link>
> +       <link linkend='var-bb-LAYERDEPENDS'>L</link>
> +       <link linkend='var-bb-MIRRORS'>M</link>
>  <!--               <link linkend='var-glossary-n'>N</link> -->
> -       <link linkend='var-OVERRIDES'>O</link>
> -       <link linkend='var-P4DIR'>P</link>
> +       <link linkend='var-bb-OVERRIDES'>O</link>
> +       <link linkend='var-bb-P4DIR'>P</link>
>  <!--       <link linkend='var-QMAKE_PROFILES'>Q</link> -->
> -       <link linkend='var-RDEPENDS'>R</link>
> -       <link linkend='var-SECTION'>S</link>
> -       <link linkend='var-T'>T</link>
> +       <link linkend='var-bb-RDEPENDS'>R</link>
> +       <link linkend='var-bb-SECTION'>S</link>
> +       <link linkend='var-bb-T'>T</link>
>  <!--       <link linkend='var-UBOOT_CONFIG'>U</link> -->
>  <!--               <link linkend='var-glossary-v'>V</link> -->
>  <!--       <link linkend='var-WARN_QA'>W</link> -->
> @@ -65,13 +65,13 @@
>  <!--               <link linkend='var-glossary-z'>Z</link>-->
>      </para>
>  
> -    <glossdiv id='var-glossary-a'><title>A</title>
> +    <glossdiv id='var-bb-glossary-a'><title>A</title>
>  
> -        <glossentry
> id='var-ASSUME_PROVIDED'><glossterm>ASSUME_PROVIDED</glossterm>
> +        <glossentry
> id='var-bb-ASSUME_PROVIDED'><glossterm>ASSUME_PROVIDED</glossterm>
> <glossdef> <para>
>                      Lists recipe names
> -                    (<link
> linkend='var-PN'><filename>PN</filename></link>
> +                    (<link
> linkend='var-bb-PN'><filename>PN</filename></link> values) BitBake
> does not attempt to build. Instead, BitBake assumes these recipes
> have already been built.
> @@ -91,9 +91,9 @@
>      </glossdiv>
>  
>  
> -    <glossdiv id='var-glossary-b'><title>B</title>
> +    <glossdiv id='var-bb-glossary-b'><title>B</title>
>  
> -        <glossentry id='var-B'><glossterm>B</glossterm>
> +        <glossentry id='var-bb-B'><glossterm>B</glossterm>
>              <glossdef>
>                  <para>
>                      The directory in which BitBake executes functions
> @@ -102,7 +102,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_ALLOWED_NETWORKS'><glossterm>BB_ALLOWED_NETWORKS</glossterm>
> +        <glossentry
> id='var-bb-BB_ALLOWED_NETWORKS'><glossterm>BB_ALLOWED_NETWORKS</glossterm>
> <glossdef> <para>
>                      Specifies a space-delimited list of hosts that
> the fetcher @@ -111,7 +111,7 @@
>                      <itemizedlist>
>                          <listitem><para>
>                              This host list is only used if
> -                            <link
> linkend='var-BB_NO_NETWORK'><filename>BB_NO_NETWORK</filename></link>
> +                            <link
> linkend='var-bb-BB_NO_NETWORK'><filename>BB_NO_NETWORK</filename></link>
> is either not set or set to "0". </para></listitem>
>                          <listitem><para>
> @@ -151,13 +151,13 @@
>                      </itemizedlist>
>                      Using <filename>BB_ALLOWED_NETWORKS</filename> in
>                      conjunction with
> -                    <link
> linkend='var-PREMIRRORS'><filename>PREMIRRORS</filename></link>
> +                    <link
> linkend='var-bb-PREMIRRORS'><filename>PREMIRRORS</filename></link> is
> very useful. Adding the host you want to use to
>                      <filename>PREMIRRORS</filename> results in the
> source code being fetched from an allowed location and avoids raising
>                      an error when a host that is not allowed is in a
> -                    <link
> linkend='var-SRC_URI'><filename>SRC_URI</filename></link>
> +                    <link
> linkend='var-bb-SRC_URI'><filename>SRC_URI</filename></link>
> statement. This is because the fetcher does not attempt to use the
>                      host listed in <filename>SRC_URI</filename>
> after a @@ -167,7 +167,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_CONSOLELOG'><glossterm>BB_CONSOLELOG</glossterm>
> +        <glossentry
> id='var-bb-BB_CONSOLELOG'><glossterm>BB_CONSOLELOG</glossterm>
> <glossdef> <para>
>                      Specifies the path to a log file into which
> BitBake's user @@ -176,7 +176,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_CURRENTTASK'><glossterm>BB_CURRENTTASK</glossterm>
> +        <glossentry
> id='var-bb-BB_CURRENTTASK'><glossterm>BB_CURRENTTASK</glossterm>
> <glossdef> <para>
>                      Contains the name of the currently running task.
> @@ -186,7 +186,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_DANGLINGAPPENDS_WARNONLY'><glossterm>BB_DANGLINGAPPENDS_WARNONLY</glossterm>
> +        <glossentry
> id='var-bb-BB_DANGLINGAPPENDS_WARNONLY'><glossterm>BB_DANGLINGAPPENDS_WARNONLY</glossterm>
> <glossdef> <para>
>                      Defines how BitBake handles situations where an
> append @@ -208,7 +208,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_DEFAULT_TASK'><glossterm>BB_DEFAULT_TASK</glossterm>
> +        <glossentry
> id='var-bb-BB_DEFAULT_TASK'><glossterm>BB_DEFAULT_TASK</glossterm>
> <glossdef> <para>
>                      The default task to use when none is specified
> (e.g. @@ -219,7 +219,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_DISKMON_DIRS'><glossterm>BB_DISKMON_DIRS</glossterm>
> +        <glossentry
> id='var-bb-BB_DISKMON_DIRS'><glossterm>BB_DISKMON_DIRS</glossterm>
> <glossdef> <para>
>                      Monitors disk space and available inodes during
> the build @@ -245,7 +245,7 @@
>                        build when a threshold is broken.
>                        Subsequent warnings are issued as
>                        defined by the
> -                      <link
> linkend='var-BB_DISKMON_WARNINTERVAL'>BB_DISKMON_WARNINTERVAL</link>
> variable,
> +                      <link
> linkend='var-bb-BB_DISKMON_WARNINTERVAL'>BB_DISKMON_WARNINTERVAL</link>
> variable, which must be defined. 
>          &lt;dir&gt; is:
> @@ -275,7 +275,7 @@
>       BB_DISKMON_DIRS = "ABORT,${TMPDIR},,100K"
>                      </literallayout>
>                      The first example works only if you also set
> -                    the <link
> linkend='var-BB_DISKMON_WARNINTERVAL'><filename>BB_DISKMON_WARNINTERVAL</filename></link>
> variable.
> +                    the <link
> linkend='var-bb-BB_DISKMON_WARNINTERVAL'><filename>BB_DISKMON_WARNINTERVAL</filename></link>
> variable. This example causes the build system to immediately abort
> when either the disk space in <filename>${TMPDIR}</filename> drops
> below 1 Gbyte or the available free inodes drops below @@ -309,7
> +309,7 @@ </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_DISKMON_WARNINTERVAL'><glossterm>BB_DISKMON_WARNINTERVAL</glossterm>
> +        <glossentry
> id='var-bb-BB_DISKMON_WARNINTERVAL'><glossterm>BB_DISKMON_WARNINTERVAL</glossterm>
> <glossdef> <para>
>                      Defines the disk space and free inode warning
> intervals. @@ -319,7 +319,7 @@
>                      If you are going to use the
>                      <filename>BB_DISKMON_WARNINTERVAL</filename>
> variable, you must also use the
> -                    <link
> linkend='var-BB_DISKMON_DIRS'><filename>BB_DISKMON_DIRS</filename></link>
> variable
> +                    <link
> linkend='var-bb-BB_DISKMON_DIRS'><filename>BB_DISKMON_DIRS</filename></link>
> variable and define its action as "WARN". During the build,
> subsequent warnings are issued each time disk space or number of free
> inodes further reduces by @@ -374,7 +374,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_ENV_WHITELIST'><glossterm>BB_ENV_WHITELIST</glossterm>
> +        <glossentry
> id='var-bb-BB_ENV_WHITELIST'><glossterm>BB_ENV_WHITELIST</glossterm>
> <glossdef> <para>
>                      Specifies the internal whitelist of variables to
> allow @@ -382,11 +382,11 @@
>                      datastore.
>                      If the value of this variable is not specified
>                      (which is the default), the following list is
> used:
> -                    <link
> linkend='var-BBPATH'><filename>BBPATH</filename></link>,
> -                    <link
> linkend='var-BB_PRESERVE_ENV'><filename>BB_PRESERVE_ENV</filename></link>,
> -                    <link
> linkend='var-BB_ENV_WHITELIST'><filename>BB_ENV_WHITELIST</filename></link>,
> +                    <link
> linkend='var-bb-BBPATH'><filename>BBPATH</filename></link>,
> +                    <link
> linkend='var-bb-BB_PRESERVE_ENV'><filename>BB_PRESERVE_ENV</filename></link>,
> +                    <link
> linkend='var-bb-BB_ENV_WHITELIST'><filename>BB_ENV_WHITELIST</filename></link>,
> and
> -                    <link
> linkend='var-BB_ENV_EXTRAWHITE'><filename>BB_ENV_EXTRAWHITE</filename></link>.
> +                    <link
> linkend='var-bb-BB_ENV_EXTRAWHITE'><filename>BB_ENV_EXTRAWHITE</filename></link>.
> <note> You must set this variable in the external environment
>                          in order for it to work.
> @@ -395,7 +395,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_ENV_EXTRAWHITE'><glossterm>BB_ENV_EXTRAWHITE</glossterm>
> +        <glossentry
> id='var-bb-BB_ENV_EXTRAWHITE'><glossterm>BB_ENV_EXTRAWHITE</glossterm>
> <glossdef> <para>
>                      Specifies an additional set of variables to
> allow through @@ -403,7 +403,7 @@
>                      datastore.
>                      This list of variables are on top of the
> internal list set in
> -                    <link
> linkend='var-BB_ENV_WHITELIST'><filename>BB_ENV_WHITELIST</filename></link>.
> +                    <link
> linkend='var-bb-BB_ENV_WHITELIST'><filename>BB_ENV_WHITELIST</filename></link>.
> <note> You must set this variable in the external
>                          environment in order for it to work.
> @@ -412,22 +412,22 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_FETCH_PREMIRRORONLY'><glossterm>BB_FETCH_PREMIRRORONLY</glossterm>
> +        <glossentry
> id='var-bb-BB_FETCH_PREMIRRORONLY'><glossterm>BB_FETCH_PREMIRRORONLY</glossterm>
> <glossdef> <para>
>                      When set to "1", causes BitBake's fetcher module
> to only search
> -                    <link
> linkend='var-PREMIRRORS'><filename>PREMIRRORS</filename></link>
> +                    <link
> linkend='var-bb-PREMIRRORS'><filename>PREMIRRORS</filename></link>
> for files. BitBake will not search the main
> -                    <link
> linkend='var-SRC_URI'><filename>SRC_URI</filename></link>
> +                    <link
> linkend='var-bb-SRC_URI'><filename>SRC_URI</filename></link> or
> -                    <link
> linkend='var-MIRRORS'><filename>MIRRORS</filename></link>.
> +                    <link
> linkend='var-bb-MIRRORS'><filename>MIRRORS</filename></link>. </para>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_FILENAME'><glossterm>BB_FILENAME</glossterm>
> +        <glossentry
> id='var-bb-BB_FILENAME'><glossterm>BB_FILENAME</glossterm> <glossdef>
>                  <para>
>                      Contains the filename of the recipe that owns
> the currently @@ -440,12 +440,12 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_GENERATE_MIRROR_TARBALLS'><glossterm>BB_GENERATE_MIRROR_TARBALLS</glossterm>
> +        <glossentry
> id='var-bb-BB_GENERATE_MIRROR_TARBALLS'><glossterm>BB_GENERATE_MIRROR_TARBALLS</glossterm>
> <glossdef> <para>
>                      Causes tarballs of the Git repositories,
> including the Git metadata, to be placed in the
> -                    <link
> linkend='var-DL_DIR'><filename>DL_DIR</filename></link>
> +                    <link
> linkend='var-bb-DL_DIR'><filename>DL_DIR</filename></link> directory.
>                      Anyone wishing to create a source mirror would
> want to enable this variable.
> @@ -461,7 +461,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_HASHCONFIG_WHITELIST'><glossterm>BB_HASHCONFIG_WHITELIST</glossterm>
> +        <glossentry
> id='var-bb-BB_HASHCONFIG_WHITELIST'><glossterm>BB_HASHCONFIG_WHITELIST</glossterm>
> <glossdef> <para>
>                      Lists variables that are excluded from base
> configuration @@ -485,7 +485,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_HASHBASE_WHITELIST'><glossterm>BB_HASHBASE_WHITELIST</glossterm>
> +        <glossentry
> id='var-bb-BB_HASHBASE_WHITELIST'><glossterm>BB_HASHBASE_WHITELIST</glossterm>
> <glossdef> <para>
>                      Lists variables that are excluded from checksum
> and @@ -500,7 +500,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_HASHCHECK_FUNCTION'><glossterm>BB_HASHCHECK_FUNCTION</glossterm>
> +        <glossentry
> id='var-bb-BB_HASHCHECK_FUNCTION'><glossterm>BB_HASHCHECK_FUNCTION</glossterm>
> <glossdef> <para>
>                      Specifies the name of the function to call
> during the @@ -524,7 +524,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_INVALIDCONF'><glossterm>BB_INVALIDCONF</glossterm>
> +        <glossentry
> id='var-bb-BB_INVALIDCONF'><glossterm>BB_INVALIDCONF</glossterm>
> <glossdef> <para>
>                      Used in combination with the
> @@ -539,11 +539,11 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_LOGFMT'><glossterm>BB_LOGFMT</glossterm>
> +        <glossentry
> id='var-bb-BB_LOGFMT'><glossterm>BB_LOGFMT</glossterm> <glossdef>
>                  <para>
>                      Specifies the name of the log files saved into
> -                    <filename>${</filename><link
> linkend='var-T'><filename>T</filename></link><filename>}</filename>.
> +                    <filename>${</filename><link
> linkend='var-bb-T'><filename>T</filename></link><filename>}</filename>.
> By default, the <filename>BB_LOGFMT</filename> variable is undefined
> and the log file names get created using the following form:
> @@ -556,7 +556,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_NICE_LEVEL'><glossterm>BB_NICE_LEVEL</glossterm>
> +        <glossentry
> id='var-bb-BB_NICE_LEVEL'><glossterm>BB_NICE_LEVEL</glossterm>
> <glossdef> <para>
>                      Allows BitBake to run at a specific priority
> @@ -564,13 +564,13 @@
>                      System permissions usually mean that BitBake can
> reduce its priority but not raise it again.
>                      See
> -                    <link
> linkend='var-BB_TASK_NICE_LEVEL'><filename>BB_TASK_NICE_LEVEL</filename></link>
> +                    <link
> linkend='var-bb-BB_TASK_NICE_LEVEL'><filename>BB_TASK_NICE_LEVEL</filename></link>
> for additional information. </para>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_NO_NETWORK'><glossterm>BB_NO_NETWORK</glossterm>
> +        <glossentry
> id='var-bb-BB_NO_NETWORK'><glossterm>BB_NO_NETWORK</glossterm>
> <glossdef> <para>
>                      Disables network access in the BitBake fetcher
> modules. @@ -587,7 +587,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_NUMBER_THREADS'><glossterm>BB_NUMBER_THREADS</glossterm>
> +        <glossentry
> id='var-bb-BB_NUMBER_THREADS'><glossterm>BB_NUMBER_THREADS</glossterm>
> <glossdef> <para>
>                      The maximum number of tasks BitBake should run
> in parallel @@ -599,7 +599,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_NUMBER_PARSE_THREADS'><glossterm>BB_NUMBER_PARSE_THREADS</glossterm>
> +        <glossentry
> id='var-bb-BB_NUMBER_PARSE_THREADS'><glossterm>BB_NUMBER_PARSE_THREADS</glossterm>
> <glossdef> <para>
>                      Sets the number of threads BitBake uses when
> parsing. @@ -609,7 +609,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_ORIGENV'><glossterm>BB_ORIGENV</glossterm>
> +        <glossentry
> id='var-bb-BB_ORIGENV'><glossterm>BB_ORIGENV</glossterm> <glossdef>
>                  <para>
>                      Contains a copy of the original external
> environment in @@ -625,7 +625,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_PRESERVE_ENV'><glossterm>BB_PRESERVE_ENV</glossterm>
> +        <glossentry
> id='var-bb-BB_PRESERVE_ENV'><glossterm>BB_PRESERVE_ENV</glossterm>
> <glossdef> <para>
>                      Disables whitelisting and instead allows all
> variables @@ -639,12 +639,12 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_RUNFMT'><glossterm>BB_RUNFMT</glossterm>
> +        <glossentry
> id='var-bb-BB_RUNFMT'><glossterm>BB_RUNFMT</glossterm> <glossdef>
>                  <para>
>                      Specifies the name of the executable script files
>                      (i.e. run files) saved into
> -                    <filename>${</filename><link
> linkend='var-T'><filename>T</filename></link><filename>}</filename>.
> +                    <filename>${</filename><link
> linkend='var-bb-T'><filename>T</filename></link><filename>}</filename>.
> By default, the <filename>BB_RUNFMT</filename> variable is undefined
> and the run file names get created using the following form:
> @@ -657,7 +657,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_RUNTASK'><glossterm>BB_RUNTASK</glossterm>
> +        <glossentry
> id='var-bb-BB_RUNTASK'><glossterm>BB_RUNTASK</glossterm> <glossdef>
>                  <para>
>                      Contains the name of the currently executing
> task. @@ -669,7 +669,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_SCHEDULER'><glossterm>BB_SCHEDULER</glossterm>
> +        <glossentry
> id='var-bb-BB_SCHEDULER'><glossterm>BB_SCHEDULER</glossterm>
> <glossdef> <para>
>                      Selects the name of the scheduler to use for the
> @@ -695,7 +695,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_SCHEDULERS'><glossterm>BB_SCHEDULERS</glossterm>
> +        <glossentry
> id='var-bb-BB_SCHEDULERS'><glossterm>BB_SCHEDULERS</glossterm>
> <glossdef> <para>
>                      Defines custom schedulers to import.
> @@ -705,13 +705,13 @@
>  
>                  <para>
>                      For information how to select a scheduler, see
> the
> -                    <link
> linkend='var-BB_SCHEDULER'><filename>BB_SCHEDULER</filename></link>
> +                    <link
> linkend='var-bb-BB_SCHEDULER'><filename>BB_SCHEDULER</filename></link>
> variable. </para>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_SETSCENE_DEPVALID'><glossterm>BB_SETSCENE_DEPVALID</glossterm>
> +        <glossentry
> id='var-bb-BB_SETSCENE_DEPVALID'><glossterm>BB_SETSCENE_DEPVALID</glossterm>
> <glossdef> <para>
>                      Specifies a function BitBake calls that
> determines @@ -731,7 +731,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_SETSCENE_VERIFY_FUNCTION2'><glossterm>BB_SETSCENE_VERIFY_FUNCTION2</glossterm>
> +        <glossentry
> id='var-bb-BB_SETSCENE_VERIFY_FUNCTION2'><glossterm>BB_SETSCENE_VERIFY_FUNCTION2</glossterm>
> <glossdef> <para>
>                      Specifies a function to call that verifies the
> list of @@ -752,7 +752,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_SIGNATURE_EXCLUDE_FLAGS'><glossterm>BB_SIGNATURE_EXCLUDE_FLAGS</glossterm>
> +        <glossentry
> id='var-bb-BB_SIGNATURE_EXCLUDE_FLAGS'><glossterm>BB_SIGNATURE_EXCLUDE_FLAGS</glossterm>
> <glossdef> <para>
>                      Lists variable flags (varflags)
> @@ -771,7 +771,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_SIGNATURE_HANDLER'><glossterm>BB_SIGNATURE_HANDLER</glossterm>
> +        <glossentry
> id='var-bb-BB_SIGNATURE_HANDLER'><glossterm>BB_SIGNATURE_HANDLER</glossterm>
> <glossdef> <para>
>                      Defines the name of the signature handler
> BitBake uses. @@ -790,7 +790,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_SRCREV_POLICY'><glossterm>BB_SRCREV_POLICY</glossterm>
> +        <glossentry
> id='var-bb-BB_SRCREV_POLICY'><glossterm>BB_SRCREV_POLICY</glossterm>
> <glossdef> <para>
>                      Defines the behavior of the fetcher when it
> interacts with @@ -817,7 +817,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_STAMP_POLICY'><glossterm>BB_STAMP_POLICY</glossterm>
> +        <glossentry
> id='var-bb-BB_STAMP_POLICY'><glossterm>BB_STAMP_POLICY</glossterm>
> <glossdef> <para>
>                      Defines the mode used for how timestamps of
> stamp files @@ -836,7 +836,7 @@
>                          <listitem><para><emphasis>whitelist</emphasis>
> - Identical to "full" mode except timestamp
>                              comparisons are made for recipes listed
> in the
> -                            <link
> linkend='var-BB_STAMP_WHITELIST'><filename>BB_STAMP_WHITELIST</filename></link>
> +                            <link
> linkend='var-bb-BB_STAMP_WHITELIST'><filename>BB_STAMP_WHITELIST</filename></link>
> variable. </para></listitem>
>                      </itemizedlist>
> @@ -848,19 +848,19 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_STAMP_WHITELIST'><glossterm>BB_STAMP_WHITELIST</glossterm>
> +        <glossentry
> id='var-bb-BB_STAMP_WHITELIST'><glossterm>BB_STAMP_WHITELIST</glossterm>
> <glossdef> <para>
>                      Lists files whose stamp file timestamps are
> compared when the stamp policy mode is set to "whitelist".
>                      For information on stamp policies, see the
> -                    <link
> linkend='var-BB_STAMP_POLICY'><filename>BB_STAMP_POLICY</filename></link>
> +                    <link
> linkend='var-bb-BB_STAMP_POLICY'><filename>BB_STAMP_POLICY</filename></link>
> variable. </para>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_STRICT_CHECKSUM'><glossterm>BB_STRICT_CHECKSUM</glossterm>
> +        <glossentry
> id='var-bb-BB_STRICT_CHECKSUM'><glossterm>BB_STRICT_CHECKSUM</glossterm>
> <glossdef> <para>
>                      Sets a more strict checksum mechanism for
> non-local URLs. @@ -871,7 +871,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_TASK_IONICE_LEVEL'><glossterm>BB_TASK_IONICE_LEVEL</glossterm>
> +        <glossentry
> id='var-bb-BB_TASK_IONICE_LEVEL'><glossterm>BB_TASK_IONICE_LEVEL</glossterm>
> <glossdef> <para>
>                      Allows adjustment of a task's Input/Output
> priority. @@ -882,7 +882,7 @@
>                      variable to adjust the I/O priority of these
> tasks. <note>
>                          This variable works similarly to the
> -                        <link
> linkend='var-BB_TASK_NICE_LEVEL'><filename>BB_TASK_NICE_LEVEL</filename></link>
> +                        <link
> linkend='var-bb-BB_TASK_NICE_LEVEL'><filename>BB_TASK_NICE_LEVEL</filename></link>
> variable except with a task's I/O priorities. </note>
>                  </para>
> @@ -921,7 +921,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_TASK_NICE_LEVEL'><glossterm>BB_TASK_NICE_LEVEL</glossterm>
> +        <glossentry
> id='var-bb-BB_TASK_NICE_LEVEL'><glossterm>BB_TASK_NICE_LEVEL</glossterm>
> <glossdef> <para>
>                      Allows specific tasks to change their priority
> @@ -940,7 +940,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_TASKHASH'><glossterm>BB_TASKHASH</glossterm>
> +        <glossentry
> id='var-bb-BB_TASKHASH'><glossterm>BB_TASKHASH</glossterm> <glossdef>
>                  <para>
>                      Within an executing task, this variable holds
> the hash @@ -950,7 +950,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_VERBOSE_LOGS'><glossterm>BB_VERBOSE_LOGS</glossterm>
> +        <glossentry
> id='var-bb-BB_VERBOSE_LOGS'><glossterm>BB_VERBOSE_LOGS</glossterm>
> <glossdef> <para>
>                      Controls how verbose BitBake is during builds.
> @@ -960,7 +960,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BB_WORKERCONTEXT'><glossterm>BB_WORKERCONTEXT</glossterm>
> +        <glossentry
> id='var-bb-BB_WORKERCONTEXT'><glossterm>BB_WORKERCONTEXT</glossterm>
> <glossdef> <para>
>                      Specifies if the current context is executing a
> task. @@ -973,7 +973,7 @@
>          </glossentry>
>  
>  
> -        <glossentry
> id='var-BBCLASSEXTEND'><glossterm>BBCLASSEXTEND</glossterm>
> +        <glossentry
> id='var-bb-BBCLASSEXTEND'><glossterm>BBCLASSEXTEND</glossterm>
> <glossdef> <para>
>                      Allows you to extend a recipe so that it builds
> variants @@ -1009,7 +1009,7 @@
>                          <filename>_class-native</filename>.
>                          For example, to generate a native version of
> a recipe, a
> -                        <link
> linkend='var-DEPENDS'><filename>DEPENDS</filename></link>
> +                        <link
> linkend='var-bb-DEPENDS'><filename>DEPENDS</filename></link> on "foo"
> is rewritten to a <filename>DEPENDS</filename> on "foo-native".
>                          </para>
> @@ -1028,7 +1028,7 @@
>               </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-BBDEBUG'><glossterm>BBDEBUG</glossterm>
> +        <glossentry
> id='var-bb-BBDEBUG'><glossterm>BBDEBUG</glossterm> <glossdef>
>                  <para>
>                      Sets the BitBake debug output level to a
> specific value @@ -1042,7 +1042,7 @@
>               </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BBFILE_COLLECTIONS'><glossterm>BBFILE_COLLECTIONS</glossterm>
> +        <glossentry
> id='var-bb-BBFILE_COLLECTIONS'><glossterm>BBFILE_COLLECTIONS</glossterm>
> <glossdef> <para>Lists the names of configured layers.
>                      These names are used to find the other
> <filename>BBFILE_*</filename> @@ -1053,10 +1053,10 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BBFILE_PATTERN'><glossterm>BBFILE_PATTERN</glossterm>
> +        <glossentry
> id='var-bb-BBFILE_PATTERN'><glossterm>BBFILE_PATTERN</glossterm>
> <glossdef> <para>Variable that expands to match files from
> -                    <link
> linkend='var-BBFILES'><filename>BBFILES</filename></link>
> +                    <link
> linkend='var-bb-BBFILES'><filename>BBFILES</filename></link> in a
> particular layer. This variable is used in the
> <filename>conf/layer.conf</filename> file and must be suffixed with
> the name of the specific layer (e.g. @@ -1064,7 +1064,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BBFILE_PRIORITY'><glossterm>BBFILE_PRIORITY</glossterm>
> +        <glossentry
> id='var-bb-BBFILE_PRIORITY'><glossterm>BBFILE_PRIORITY</glossterm>
> <glossdef> <para>Assigns the priority for recipe files in each
> layer.</para> <para>This variable is useful in situations where the
> same recipe appears in @@ -1074,7 +1074,7 @@
>                      letting you control the precedence for the
> multiple layers. The precedence established through this variable
> stands regardless of a recipe's version
> -                    (<link
> linkend='var-PV'><filename>PV</filename></link> variable).
> +                    (<link
> linkend='var-bb-PV'><filename>PV</filename></link> variable). For
> example, a layer that has a recipe with a higher
> <filename>PV</filename> value but for which the
> <filename>BBFILE_PRIORITY</filename> is set to have a lower
> precedence still has a lower precedence.</para> @@ -1083,7 +1083,7 @@
> For example, the value 6 has a higher precedence than the value 5. If
> not specified, the <filename>BBFILE_PRIORITY</filename> variable is
> set based on layer dependencies (see the
> -                    <filename><link
> linkend='var-LAYERDEPENDS'>LAYERDEPENDS</link></filename> variable for
> +                    <filename><link
> linkend='var-bb-LAYERDEPENDS'>LAYERDEPENDS</link></filename> variable
> for more information. The default priority, if unspecified
>                      for a layer with no dependencies, is the lowest
> defined priority + 1 @@ -1095,7 +1095,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-BBFILES'><glossterm>BBFILES</glossterm>
> +        <glossentry
> id='var-bb-BBFILES'><glossterm>BBFILES</glossterm> <glossdef>
>                  <para>
>                      A space-separated list of recipe files BitBake
> uses to @@ -1113,7 +1113,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BBINCLUDED'><glossterm>BBINCLUDED</glossterm>
> +        <glossentry
> id='var-bb-BBINCLUDED'><glossterm>BBINCLUDED</glossterm> <glossdef>
>                  <para>
>                      Contains a space-separated list of all of all
> files that @@ -1123,7 +1123,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BBINCLUDELOGS'><glossterm>BBINCLUDELOGS</glossterm>
> +        <glossentry
> id='var-bb-BBINCLUDELOGS'><glossterm>BBINCLUDELOGS</glossterm>
> <glossdef> <para>
>                      If set to a value, enables printing the task log
> when @@ -1132,11 +1132,11 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BBINCLUDELOGS_LINES'><glossterm>BBINCLUDELOGS_LINES</glossterm>
> +        <glossentry
> id='var-bb-BBINCLUDELOGS_LINES'><glossterm>BBINCLUDELOGS_LINES</glossterm>
> <glossdef> <para>
>                      If
> -                    <link
> linkend='var-BBINCLUDELOGS'><filename>BBINCLUDELOGS</filename></link>
> +                    <link
> linkend='var-bb-BBINCLUDELOGS'><filename>BBINCLUDELOGS</filename></link>
> is set, specifies the maximum number of lines from the task log file
> to print when reporting a failed task. If you do not set
> <filename>BBINCLUDELOGS_LINES</filename>, @@ -1145,7 +1145,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-BBLAYERS'><glossterm>BBLAYERS</glossterm>
> +        <glossentry
> id='var-bb-BBLAYERS'><glossterm>BBLAYERS</glossterm> <glossdef>
>                  <para>Lists the layers to enable during the build.
>                      This variable is defined in the
> <filename>bblayers.conf</filename> configuration @@ -1166,7 +1166,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BBLAYERS_FETCH_DIR'><glossterm>BBLAYERS_FETCH_DIR</glossterm>
> +        <glossentry
> id='var-bb-BBLAYERS_FETCH_DIR'><glossterm>BBLAYERS_FETCH_DIR</glossterm>
> <glossdef> <para>
>                      Sets the base location where layers are stored.
> @@ -1178,7 +1178,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-BBMASK'><glossterm>BBMASK</glossterm>
> +        <glossentry id='var-bb-BBMASK'><glossterm>BBMASK</glossterm>
>              <glossdef>
>                  <para>
>                      Prevents BitBake from processing recipes and
> recipe @@ -1236,7 +1236,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BBMULTICONFIG'><glossterm>BBMULTICONFIG</glossterm>
> +        <glossentry
> id='var-bb-BBMULTICONFIG'><glossterm>BBMULTICONFIG</glossterm> <info>
>                  BBMULTICONFIG[doc] = "Enables BitBake to perform
> multiple configuration builds and lists each separate configuration
> (multiconfig)." </info> @@ -1275,7 +1275,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-BBPATH'><glossterm>BBPATH</glossterm>
> +        <glossentry id='var-bb-BBPATH'><glossterm>BBPATH</glossterm>
>              <glossdef>
>                  <para>
>                      Used by BitBake to locate class
> @@ -1302,7 +1302,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-BBSERVER'><glossterm>BBSERVER</glossterm>
> +        <glossentry
> id='var-bb-BBSERVER'><glossterm>BBSERVER</glossterm> <glossdef>
>                  <para>
>                      Points to the server that runs memory-resident
> BitBake. @@ -1312,7 +1312,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BBTARGETS'><glossterm>BBTARGETS</glossterm>
> +        <glossentry
> id='var-bb-BBTARGETS'><glossterm>BBTARGETS</glossterm> <glossdef>
>                  <para>
>                      Allows you to use a configuration file to add to
> the list @@ -1321,14 +1321,14 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BBVERSIONS'><glossterm>BBVERSIONS</glossterm>
> +        <glossentry
> id='var-bb-BBVERSIONS'><glossterm>BBVERSIONS</glossterm> <glossdef>
>                  <para>
>                      Allows a single recipe to build multiple
> versions of a project from a single recipe file.
>                      You also able to specify conditional metadata
>                      using the
> -                    <link
> linkend='var-OVERRIDES'><filename>OVERRIDES</filename></link>
> +                    <link
> linkend='var-bb-OVERRIDES'><filename>OVERRIDES</filename></link>
> mechanism for a single version or for an optionally named range of
> versions. </para>
> @@ -1342,7 +1342,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BITBAKE_UI'><glossterm>BITBAKE_UI</glossterm>
> +        <glossentry
> id='var-bb-BITBAKE_UI'><glossterm>BITBAKE_UI</glossterm> <glossdef>
>                  <para>
>                      Used to specify the UI module to use when
> running BitBake. @@ -1356,7 +1356,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-BUILDNAME'><glossterm>BUILDNAME</glossterm>
> +        <glossentry
> id='var-bb-BUILDNAME'><glossterm>BUILDNAME</glossterm> <glossdef>
>                  <para>
>                      A name assigned to the build.
> @@ -1366,7 +1366,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-BZRDIR'><glossterm>BZRDIR</glossterm>
> +        <glossentry id='var-bb-BZRDIR'><glossterm>BZRDIR</glossterm>
>              <glossdef>
>                  <para>
>                      The directory in which files checked out of a
> Bazaar @@ -1377,9 +1377,9 @@
>  
>      </glossdiv>
>  
> -    <glossdiv id='var-glossary-c'><title>C</title>
> +    <glossdiv id='var-bb-glossary-c'><title>C</title>
>  
> -        <glossentry id='var-CACHE'><glossterm>CACHE</glossterm>
> +        <glossentry id='var-bb-CACHE'><glossterm>CACHE</glossterm>
>              <glossdef>
>                  <para>
>                      Specifies the directory BitBake uses to store a
> cache @@ -1389,7 +1389,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-CVSDIR'><glossterm>CVSDIR</glossterm>
> +        <glossentry id='var-bb-CVSDIR'><glossterm>CVSDIR</glossterm>
>              <glossdef>
>                  <para>
>                      The directory in which files checked out under
> the @@ -1400,9 +1400,9 @@
>  
>      </glossdiv>
>  
> -    <glossdiv id='var-glossary-d'><title>D</title>
> +    <glossdiv id='var-bb-glossary-d'><title>D</title>
>  
> -        <glossentry
> id='var-DEFAULT_PREFERENCE'><glossterm>DEFAULT_PREFERENCE</glossterm>
> +        <glossentry
> id='var-bb-DEFAULT_PREFERENCE'><glossterm>DEFAULT_PREFERENCE</glossterm>
> <glossdef> <para>
>                      Specifies a weak bias for recipe selection
> priority. @@ -1413,20 +1413,20 @@
>                      piece of software.
>                      Using the variable in this way causes the stable
> version of the recipe to build by default in the absence of
> -                    <filename><link
> linkend='var-PREFERRED_VERSION'>PREFERRED_VERSION</link></filename>
> +                    <filename><link
> linkend='var-bb-PREFERRED_VERSION'>PREFERRED_VERSION</link></filename>
> being used to build the development version. </para>
>                  <note>
>                      The bias provided by
> <filename>DEFAULT_PREFERENCE</filename> is weak and is overridden by
> -                    <filename><link
> linkend='var-BBFILE_PRIORITY'>BBFILE_PRIORITY</link></filename>
> +                    <filename><link
> linkend='var-bb-BBFILE_PRIORITY'>BBFILE_PRIORITY</link></filename> if
> that variable is different between two layers that contain different
> versions of the same recipe. </note>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-DEPENDS'><glossterm>DEPENDS</glossterm>
> +        <glossentry
> id='var-bb-DEPENDS'><glossterm>DEPENDS</glossterm> <glossdef>
>                  <para>
>                      Lists a recipe's build-time dependencies
> @@ -1451,13 +1451,13 @@
>  
>                  <para>
>                      For information on runtime dependencies, see the
> -                    <link
> linkend='var-RDEPENDS'><filename>RDEPENDS</filename></link>
> +                    <link
> linkend='var-bb-RDEPENDS'><filename>RDEPENDS</filename></link>
> variable. </para>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-DESCRIPTION'><glossterm>DESCRIPTION</glossterm>
> +        <glossentry
> id='var-bb-DESCRIPTION'><glossterm>DESCRIPTION</glossterm> <glossdef>
>                  <para>
>                      A long description for the recipe.
> @@ -1465,7 +1465,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-DL_DIR'><glossterm>DL_DIR</glossterm>
> +        <glossentry id='var-bb-DL_DIR'><glossterm>DL_DIR</glossterm>
>              <glossdef>
>                  <para>
>                      The central download directory used by the build
> process to @@ -1474,7 +1474,7 @@
>                      suitable for mirroring for everything except Git
>                      repositories.
>                      If you want tarballs of Git repositories, use the
> -                    <link
> linkend='var-BB_GENERATE_MIRROR_TARBALLS'><filename>BB_GENERATE_MIRROR_TARBALLS</filename></link>
> +                    <link
> linkend='var-bb-BB_GENERATE_MIRROR_TARBALLS'><filename>BB_GENERATE_MIRROR_TARBALLS</filename></link>
> variable. </para>
>              </glossdef>
> @@ -1482,9 +1482,9 @@
>          </glossentry>
>      </glossdiv>
>  
> -    <glossdiv id='var-glossary-e'><title>E</title>
> +    <glossdiv id='var-bb-glossary-e'><title>E</title>
>  
> -        <glossentry
> id='var-EXCLUDE_FROM_WORLD'><glossterm>EXCLUDE_FROM_WORLD</glossterm>
> +        <glossentry
> id='var-bb-EXCLUDE_FROM_WORLD'><glossterm>EXCLUDE_FROM_WORLD</glossterm>
> <glossdef> <para>
>                      Directs BitBake to exclude a recipe from world
> builds (i.e. @@ -1512,9 +1512,9 @@
>  
>      </glossdiv>
>  
> -    <glossdiv id='var-glossary-f'><title>F</title>
> +    <glossdiv id='var-bb-glossary-f'><title>F</title>
>  
> -        <glossentry id='var-FAKEROOT'><glossterm>FAKEROOT</glossterm>
> +        <glossentry
> id='var-bb-FAKEROOT'><glossterm>FAKEROOT</glossterm> <glossdef>
>                  <para>
>                       Contains the command to use when running a
> shell script @@ -1527,19 +1527,19 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-FAKEROOTBASEENV'><glossterm>FAKEROOTBASEENV</glossterm>
> +        <glossentry
> id='var-bb-FAKEROOTBASEENV'><glossterm>FAKEROOTBASEENV</glossterm>
> <glossdef> <para>
>                       Lists environment variables to set when
> executing the command defined by
> -                     <link
> linkend='var-FAKEROOTCMD'><filename>FAKEROOTCMD</filename></link>
> +                     <link
> linkend='var-bb-FAKEROOTCMD'><filename>FAKEROOTCMD</filename></link>
> that starts the bitbake-worker process in the fakeroot environment.
>                  </para>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-FAKEROOTCMD'><glossterm>FAKEROOTCMD</glossterm>
> +        <glossentry
> id='var-bb-FAKEROOTCMD'><glossterm>FAKEROOTCMD</glossterm> <glossdef>
>                  <para>
>                       Contains the command that starts the
> bitbake-worker @@ -1548,7 +1548,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-FAKEROOTDIRS'><glossterm>FAKEROOTDIRS</glossterm>
> +        <glossentry
> id='var-bb-FAKEROOTDIRS'><glossterm>FAKEROOTDIRS</glossterm>
> <glossdef> <para>
>                       Lists directories to create before running a
> task in @@ -1557,33 +1557,33 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-FAKEROOTENV'><glossterm>FAKEROOTENV</glossterm>
> +        <glossentry
> id='var-bb-FAKEROOTENV'><glossterm>FAKEROOTENV</glossterm> <glossdef>
>                  <para>
>                       Lists environment variables to set when running
> a task in the fakeroot environment.
>                       For additional information on environment
> variables and the fakeroot environment, see the
> -                     <link
> linkend='var-FAKEROOTBASEENV'><filename>FAKEROOTBASEENV</filename></link>
> +                     <link
> linkend='var-bb-FAKEROOTBASEENV'><filename>FAKEROOTBASEENV</filename></link>
> variable. </para>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-FAKEROOTNOENV'><glossterm>FAKEROOTNOENV</glossterm>
> +        <glossentry
> id='var-bb-FAKEROOTNOENV'><glossterm>FAKEROOTNOENV</glossterm>
> <glossdef> <para>
>                       Lists environment variables to set when running
> a task that is not in the fakeroot environment.
>                       For additional information on environment
> variables and the fakeroot environment, see the
> -                     <link
> linkend='var-FAKEROOTENV'><filename>FAKEROOTENV</filename></link>
> +                     <link
> linkend='var-bb-FAKEROOTENV'><filename>FAKEROOTENV</filename></link>
> variable. </para>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-FETCHCMD'><glossterm>FETCHCMD</glossterm>
> +        <glossentry
> id='var-bb-FETCHCMD'><glossterm>FETCHCMD</glossterm> <glossdef>
>                  <para>
>                      Defines the command the BitBake fetcher module
> @@ -1595,7 +1595,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-FILE'><glossterm>FILE</glossterm>
> +        <glossentry id='var-bb-FILE'><glossterm>FILE</glossterm>
>              <glossdef>
>                  <para>
>                      Points at the current file.
> @@ -1607,7 +1607,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-FILESPATH'><glossterm>FILESPATH</glossterm>
> +        <glossentry
> id='var-bb-FILESPATH'><glossterm>FILESPATH</glossterm> <glossdef>
>                  <para>
>                      Specifies directories BitBake uses when
> searching for @@ -1625,9 +1625,9 @@
>      </glossdiv>
>  
>  
> -    <glossdiv id='var-glossary-g'><title>G</title>
> +    <glossdiv id='var-bb-glossary-g'><title>G</title>
>  
> -        <glossentry id='var-GITDIR'><glossterm>GITDIR</glossterm>
> +        <glossentry id='var-bb-GITDIR'><glossterm>GITDIR</glossterm>
>              <glossdef>
>                  <para>
>                      The directory in which a local copy of a Git
> repository @@ -1639,9 +1639,9 @@
>      </glossdiv>
>  
>  
> -    <glossdiv id='var-glossary-h'><title>H</title>
> +    <glossdiv id='var-bb-glossary-h'><title>H</title>
>  
> -        <glossentry id='var-HGDIR'><glossterm>HGDIR</glossterm>
> +        <glossentry id='var-bb-HGDIR'><glossterm>HGDIR</glossterm>
>              <glossdef>
>                  <para>
>                      The directory in which files checked out of a
> Mercurial @@ -1650,7 +1650,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-HOMEPAGE'><glossterm>HOMEPAGE</glossterm>
> +        <glossentry
> id='var-bb-HOMEPAGE'><glossterm>HOMEPAGE</glossterm> <glossdef>
>                  <para>Website where more information about the
> software the recipe is building can be found.</para>
> @@ -1659,9 +1659,9 @@
>  
>      </glossdiv>
>  
> -    <glossdiv id='var-glossary-i'><title>I</title>
> +    <glossdiv id='var-bb-glossary-i'><title>I</title>
>  
> -        <glossentry id='var-INHERIT'><glossterm>INHERIT</glossterm>
> +        <glossentry
> id='var-bb-INHERIT'><glossterm>INHERIT</glossterm> <glossdef>
>                  <para>
>                      Causes the named class or classes to be
> inherited globally. @@ -1691,15 +1691,15 @@
>      </glossdiv>
>  -->  
>  
> -    <glossdiv id='var-glossary-l'><title>L</title>
> +    <glossdiv id='var-bb-glossary-l'><title>L</title>
>  
> -        <glossentry
> id='var-LAYERDEPENDS'><glossterm>LAYERDEPENDS</glossterm>
> +        <glossentry
> id='var-bb-LAYERDEPENDS'><glossterm>LAYERDEPENDS</glossterm>
> <glossdef> <para>Lists the layers, separated by spaces, upon which
> this recipe depends. Optionally, you can specify a specific layer
> version for a dependency by adding it to the end of the layer name
> with a colon, (e.g. "anotherlayer:3" to be compared against
> -                    <link
> linkend='var-LAYERVERSION'><filename>LAYERVERSION</filename></link><filename>_anotherlayer</filename>
> +                    <link
> linkend='var-bb-LAYERVERSION'><filename>LAYERVERSION</filename></link><filename>_anotherlayer</filename>
> in this case). BitBake produces an error if any dependency is missing
> or the version numbers do not match exactly (if specified).</para>
> @@ -1710,7 +1710,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-LAYERDIR'><glossterm>LAYERDIR</glossterm>
> +        <glossentry
> id='var-bb-LAYERDIR'><glossterm>LAYERDIR</glossterm> <glossdef>
>                  <para>When used inside the
> <filename>layer.conf</filename> configuration file, this variable
> provides the path of the current layer. @@ -1719,22 +1719,22 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-LAYERDIR_RE'><glossterm>LAYERDIR_RE</glossterm>
> +        <glossentry
> id='var-bb-LAYERDIR_RE'><glossterm>LAYERDIR_RE</glossterm> <glossdef>
>                  <para>When used inside the
> <filename>layer.conf</filename> configuration file, this variable
> provides the path of the current layer, escaped for use in a regular
> expression
> -                    (<link
> linkend='var-BBFILE_PATTERN'><filename>BBFILE_PATTERN</filename></link>).
> +                    (<link
> linkend='var-bb-BBFILE_PATTERN'><filename>BBFILE_PATTERN</filename></link>).
> This variable is not available outside of
> <filename>layer.conf</filename> and references are expanded
> immediately when parsing of the file completes.</para> </glossdef>
> </glossentry> 
> -        <glossentry
> id='var-LAYERVERSION'><glossterm>LAYERVERSION</glossterm>
> +        <glossentry
> id='var-bb-LAYERVERSION'><glossterm>LAYERVERSION</glossterm>
> <glossdef> <para>Optionally specifies the version of a layer as a
> single number. You can use this variable within
> -                    <link
> linkend='var-LAYERDEPENDS'><filename>LAYERDEPENDS</filename></link>
> +                    <link
> linkend='var-bb-LAYERDEPENDS'><filename>LAYERDEPENDS</filename></link>
> for another layer in order to depend on a specific version of the
> layer.</para> <para>
> @@ -1744,7 +1744,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-LICENSE'><glossterm>LICENSE</glossterm>
> +        <glossentry
> id='var-bb-LICENSE'><glossterm>LICENSE</glossterm> <glossdef>
>                  <para>
>                      The list of source licenses for the recipe.
> @@ -1754,9 +1754,9 @@
>  
>      </glossdiv>
>  
> -    <glossdiv id='var-glossary-m'><title>M</title>
> +    <glossdiv id='var-bb-glossary-m'><title>M</title>
>  
> -        <glossentry id='var-MIRRORS'><glossterm>MIRRORS</glossterm>
> +        <glossentry
> id='var-bb-MIRRORS'><glossterm>MIRRORS</glossterm> <glossdef>
>                  <para>
>                      Specifies additional paths from which BitBake
> gets source code. @@ -1764,14 +1764,14 @@
>                      tries the local download directory.
>                      If that location fails, the build system tries
> locations defined by
> -                    <link
> linkend='var-PREMIRRORS'><filename>PREMIRRORS</filename></link>,
> +                    <link
> linkend='var-bb-PREMIRRORS'><filename>PREMIRRORS</filename></link>,
> the upstream source, and then locations specified by
> <filename>MIRRORS</filename> in that order. </para>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-MULTI_PROVIDER_WHITELIST'><glossterm>MULTI_PROVIDER_WHITELIST</glossterm>
> +        <glossentry
> id='var-bb-MULTI_PROVIDER_WHITELIST'><glossterm>MULTI_PROVIDER_WHITELIST</glossterm>
> <glossdef> <para>
>                      Allows you to suppress BitBake warnings caused
> when @@ -1804,9 +1804,9 @@
>      </glossdiv>
>  -->  
>  
> -    <glossdiv id='var-glossary-o'><title>O</title>
> +    <glossdiv id='var-bb-glossary-o'><title>O</title>
>  
> -        <glossentry
> id='var-OVERRIDES'><glossterm>OVERRIDES</glossterm>
> +        <glossentry
> id='var-bb-OVERRIDES'><glossterm>OVERRIDES</glossterm> <glossdef>
>                  <para>
>                      BitBake uses <filename>OVERRIDES</filename> to
> control @@ -1829,9 +1829,9 @@
>          </glossentry>
>      </glossdiv>
>  
> -    <glossdiv id='var-glossary-p'><title>P</title>
> +    <glossdiv id='var-bb-glossary-p'><title>P</title>
>  
> -        <glossentry id='var-P4DIR'><glossterm>P4DIR</glossterm>
> +        <glossentry id='var-bb-P4DIR'><glossterm>P4DIR</glossterm>
>              <glossdef>
>                  <para>
>                      The directory in which a local copy of a
> Perforce depot @@ -1840,14 +1840,14 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-PACKAGES'><glossterm>PACKAGES</glossterm>
> +        <glossentry
> id='var-bb-PACKAGES'><glossterm>PACKAGES</glossterm> <glossdef>
>                  <para>The list of packages the recipe creates.
>                  </para>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-PACKAGES_DYNAMIC'><glossterm>PACKAGES_DYNAMIC</glossterm>
> +        <glossentry
> id='var-bb-PACKAGES_DYNAMIC'><glossterm>PACKAGES_DYNAMIC</glossterm>
> <glossdef> <para>
>                      A promise that your recipe satisfies runtime
> dependencies @@ -1856,7 +1856,7 @@
>                      does not actually satisfy the dependencies, it
> only states that they should be satisfied.
>                      For example, if a hard, runtime dependency
> -                    (<link
> linkend='var-RDEPENDS'><filename>RDEPENDS</filename></link>)
> +                    (<link
> linkend='var-bb-RDEPENDS'><filename>RDEPENDS</filename></link>) of
> another package is satisfied during the build through the
> <filename>PACKAGES_DYNAMIC</filename> variable, but a package with
> the module name is never actually @@ -1865,7 +1865,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-PE'><glossterm>PE</glossterm>
> +        <glossentry id='var-bb-PE'><glossterm>PE</glossterm>
>              <glossdef>
>                  <para>
>                      The epoch of the recipe.
> @@ -1877,7 +1877,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-PERSISTENT_DIR'><glossterm>PERSISTENT_DIR</glossterm>
> +        <glossentry
> id='var-bb-PERSISTENT_DIR'><glossterm>PERSISTENT_DIR</glossterm>
> <glossdef> <para>
>                      Specifies the directory BitBake uses to store
> data that @@ -1889,7 +1889,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-PF'><glossterm>PF</glossterm>
> +        <glossentry id='var-bb-PF'><glossterm>PF</glossterm>
>              <glossdef>
>                  <para>
>                      Specifies the recipe or package name and
> includes all version and revision @@ -1899,27 +1899,27 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-PN'><glossterm>PN</glossterm>
> +        <glossentry id='var-bb-PN'><glossterm>PN</glossterm>
>              <glossdef>
>                  <para>The recipe name.</para>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-PR'><glossterm>PR</glossterm>
> +        <glossentry id='var-bb-PR'><glossterm>PR</glossterm>
>              <glossdef>
>                  <para>The revision of the recipe.
>                      </para>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-PREFERRED_PROVIDER'><glossterm>PREFERRED_PROVIDER</glossterm>
> +        <glossentry
> id='var-bb-PREFERRED_PROVIDER'><glossterm>PREFERRED_PROVIDER</glossterm>
> <glossdef> <para>
>                      Determines which recipe should be given
> preference when multiple recipes provide the same item.
>                      You should always suffix the variable with the
> name of the provided item, and you should set it to the
> -                    <link
> linkend='var-PN'><filename>PN</filename></link>
> +                    <link
> linkend='var-bb-PN'><filename>PN</filename></link> of the recipe to
> which you want to give precedence. Some examples:
>                      <literallayout class='monospaced'>
> @@ -1931,14 +1931,14 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-PREFERRED_PROVIDERS'><glossterm>PREFERRED_PROVIDERS</glossterm>
> +        <glossentry
> id='var-bb-PREFERRED_PROVIDERS'><glossterm>PREFERRED_PROVIDERS</glossterm>
> <glossdef> <para>
>                      Determines which recipe should be given
> preference for cases where multiple recipes provide the same item.
>                      Functionally,
>                      <filename>PREFERRED_PROVIDERS</filename> is
> identical to
> -                    <link
> linkend='var-PREFERRED_PROVIDER'><filename>PREFERRED_PROVIDER</filename></link>.
> +                    <link
> linkend='var-bb-PREFERRED_PROVIDER'><filename>PREFERRED_PROVIDER</filename></link>.
> However, the <filename>PREFERRED_PROVIDERS</filename> variable lets
> you define preferences for multiple situations using the following
> form: @@ -1954,15 +1954,15 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-PREFERRED_VERSION'><glossterm>PREFERRED_VERSION</glossterm>
> +        <glossentry
> id='var-bb-PREFERRED_VERSION'><glossterm>PREFERRED_VERSION</glossterm>
> <glossdef> <para>
>                      If there are multiple versions of recipes
> available, this variable determines which recipe should be given
> preference. You must always suffix the variable with the
> -                    <link
> linkend='var-PN'><filename>PN</filename></link>
> +                    <link
> linkend='var-bb-PN'><filename>PN</filename></link> you want to
> select, and you should set
> -                    <link
> linkend='var-PV'><filename>PV</filename></link>
> +                    <link
> linkend='var-bb-PV'><filename>PV</filename></link> accordingly for
> precedence. </para>
>  
> @@ -1989,7 +1989,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-PREMIRRORS'><glossterm>PREMIRRORS</glossterm>
> +        <glossentry
> id='var-bb-PREMIRRORS'><glossterm>PREMIRRORS</glossterm> <glossdef>
>                  <para>
>                      Specifies additional paths from which BitBake
> gets source code. @@ -1998,7 +1998,7 @@
>                      If that location fails, the build system tries
> locations defined by <filename>PREMIRRORS</filename>, the upstream
>                      source, and then locations specified by
> -                    <link
> linkend='var-MIRRORS'><filename>MIRRORS</filename></link>
> +                    <link
> linkend='var-bb-MIRRORS'><filename>MIRRORS</filename></link> in that
> order. </para>
>  
> @@ -2022,20 +2022,20 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-PROVIDES'><glossterm>PROVIDES</glossterm>
> +        <glossentry
> id='var-bb-PROVIDES'><glossterm>PROVIDES</glossterm> <glossdef>
>                  <para>
>                      A list of aliases by which a particular recipe
> can be known.
>                      By default, a recipe's own
> -                    <filename><link
> linkend='var-PN'>PN</link></filename>
> +                    <filename><link
> linkend='var-bb-PN'>PN</link></filename> is implicitly already in its
> <filename>PROVIDES</filename> list.
>                      If a recipe uses <filename>PROVIDES</filename>,
> the additional aliases are synonyms for the recipe and can
>                      be useful satisfying dependencies of other
> recipes during the build as specified by
> -                    <filename><link
> linkend='var-DEPENDS'>DEPENDS</link></filename>.
> +                    <filename><link
> linkend='var-bb-DEPENDS'>DEPENDS</link></filename>. </para>
>  
>                  <para>
> @@ -2059,7 +2059,7 @@
>                      virtual target in <filename>PROVIDES</filename>.
>                      Recipes that depend on the functionality in
> question can include the virtual target in
> -                    <link
> linkend='var-DEPENDS'><filename>DEPENDS</filename></link>
> +                    <link
> linkend='var-bb-DEPENDS'><filename>DEPENDS</filename></link> to leave
> the choice of provider open. </para>
>  
> @@ -2072,11 +2072,11 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-PRSERV_HOST'><glossterm>PRSERV_HOST</glossterm>
> +        <glossentry
> id='var-bb-PRSERV_HOST'><glossterm>PRSERV_HOST</glossterm> <glossdef>
>                  <para>
>                      The network based
> -                    <link
> linkend='var-PR'><filename>PR</filename></link>
> +                    <link
> linkend='var-bb-PR'><filename>PR</filename></link> service host and
> port. </para>
>  
> @@ -2094,7 +2094,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-PV'><glossterm>PV</glossterm>
> +        <glossentry id='var-bb-PV'><glossterm>PV</glossterm>
>              <glossdef>
>                  <para>The version of the recipe.
>                   </para>
> @@ -2108,9 +2108,9 @@
>      </glossdiv>
>  -->  
>  
> -    <glossdiv id='var-glossary-r'><title>R</title>
> +    <glossdiv id='var-bb-glossary-r'><title>R</title>
>  
> -        <glossentry id='var-RDEPENDS'><glossterm>RDEPENDS</glossterm>
> +        <glossentry
> id='var-bb-RDEPENDS'><glossterm>RDEPENDS</glossterm> <glossdef>
>                  <para>
>                      Lists a package's runtime dependencies (i.e.
> other packages) @@ -2165,13 +2165,13 @@
>  
>                  <para>
>                      For information on build-time dependencies, see
> the
> -                    <link
> linkend='var-DEPENDS'><filename>DEPENDS</filename></link>
> +                    <link
> linkend='var-bb-DEPENDS'><filename>DEPENDS</filename></link> variable.
>                  </para>
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-REPODIR'><glossterm>REPODIR</glossterm>
> +        <glossentry
> id='var-bb-REPODIR'><glossterm>REPODIR</glossterm> <glossdef>
>                  <para>
>                      The directory in which a local copy of a
> @@ -2181,14 +2181,14 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-RPROVIDES'><glossterm>RPROVIDES</glossterm>
> +        <glossentry
> id='var-bb-RPROVIDES'><glossterm>RPROVIDES</glossterm> <glossdef>
>                  <para>
>                      A list of package name aliases that a package
> also provides. These aliases are useful for satisfying runtime
> dependencies of other packages both during the build and on the target
>                      (as specified by
> -                    <filename><link
> linkend='var-RDEPENDS'>RDEPENDS</link></filename>).
> +                    <filename><link
> linkend='var-bb-RDEPENDS'>RDEPENDS</link></filename>). </para>
>                  <para>
>                     As with all package-controlling variables, you
> must always @@ -2201,7 +2201,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-RRECOMMENDS'><glossterm>RRECOMMENDS</glossterm>
> +        <glossentry
> id='var-bb-RRECOMMENDS'><glossterm>RRECOMMENDS</glossterm> <glossdef>
>                  <para>
>                      A list of packages that extends the usability of
> a package @@ -2210,7 +2210,7 @@
>                      packages in order to successfully build, but
> needs them for the extended usability.
>                      To specify runtime dependencies for packages,
> see the
> -                    <filename><link
> linkend='var-RDEPENDS'>RDEPENDS</link></filename>
> +                    <filename><link
> linkend='var-bb-RDEPENDS'>RDEPENDS</link></filename> variable.
>                  </para>
>  
> @@ -2243,15 +2243,15 @@
>  
>      </glossdiv>
>  
> -    <glossdiv id='var-glossary-s'><title>S</title>
> +    <glossdiv id='var-bb-glossary-s'><title>S</title>
>  
> -        <glossentry id='var-SECTION'><glossterm>SECTION</glossterm>
> +        <glossentry
> id='var-bb-SECTION'><glossterm>SECTION</glossterm> <glossdef>
>                  <para>The section in which packages should be
> categorized.</para> </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-SRC_URI'><glossterm>SRC_URI</glossterm>
> +        <glossentry
> id='var-bb-SRC_URI'><glossterm>SRC_URI</glossterm> <glossdef>
>                  <para>
>                      The list of source files - local or remote.
> @@ -2272,7 +2272,7 @@
>                              the metadata,
>                              from the local machine.
>                              The path is relative to the
> -                            <link
> linkend='var-FILESPATH'><filename>FILESPATH</filename></link>
> +                            <link
> linkend='var-bb-FILESPATH'><filename>FILESPATH</filename></link>
> variable.</para></listitem>
> <listitem><para><emphasis><filename>bzr://</filename> -</emphasis>
> Fetches files from a Bazaar revision control
> repository.</para></listitem> @@ -2322,7 +2322,7 @@ </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-SRCDATE'><glossterm>SRCDATE</glossterm>
> +        <glossentry
> id='var-bb-SRCDATE'><glossterm>SRCDATE</glossterm> <glossdef>
>                  <para>
>                      The date of the source code used to build the
> package. @@ -2331,7 +2331,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-SRCREV'><glossterm>SRCREV</glossterm>
> +        <glossentry id='var-bb-SRCREV'><glossterm>SRCREV</glossterm>
>              <glossdef>
>                  <para>
>                      The revision of the source code used to build
> the package. @@ -2344,13 +2344,13 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-SRCREV_FORMAT'><glossterm>SRCREV_FORMAT</glossterm>
> +        <glossentry
> id='var-bb-SRCREV_FORMAT'><glossterm>SRCREV_FORMAT</glossterm>
> <glossdef> <para>
>                      Helps construct valid
> -                    <link
> linkend='var-SRCREV'><filename>SRCREV</filename></link>
> +                    <link
> linkend='var-bb-SRCREV'><filename>SRCREV</filename></link> values
> when multiple source controlled URLs are used in
> -                    <link
> linkend='var-SRC_URI'><filename>SRC_URI</filename></link>.
> +                    <link
> linkend='var-bb-SRC_URI'><filename>SRC_URI</filename></link>. </para>
>  
>                  <para>
> @@ -2371,7 +2371,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-STAMP'><glossterm>STAMP</glossterm>
> +        <glossentry id='var-bb-STAMP'><glossterm>STAMP</glossterm>
>              <glossdef>
>                  <para>
>                      Specifies the base path used to create recipe
> stamp files. @@ -2381,12 +2381,12 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry
> id='var-STAMPCLEAN'><glossterm>STAMPCLEAN</glossterm>
> +        <glossentry
> id='var-bb-STAMPCLEAN'><glossterm>STAMPCLEAN</glossterm> <glossdef>
>                  <para>
>                      Specifies the base path used to create recipe
> stamp files. Unlike the
> -                    <link
> linkend='var-STAMP'><filename>STAMP</filename></link>
> +                    <link
> linkend='var-bb-STAMP'><filename>STAMP</filename></link> variable,
> <filename>STAMPCLEAN</filename> can contain wildcards to match the
> range of files a clean operation should remove.
> @@ -2396,7 +2396,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-SUMMARY'><glossterm>SUMMARY</glossterm>
> +        <glossentry
> id='var-bb-SUMMARY'><glossterm>SUMMARY</glossterm> <glossdef>
>                  <para>
>                      A short summary for the recipe, which is 72
> characters or less. @@ -2404,7 +2404,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-SVNDIR'><glossterm>SVNDIR</glossterm>
> +        <glossentry id='var-bb-SVNDIR'><glossterm>SVNDIR</glossterm>
>              <glossdef>
>                  <para>
>                      The directory in which files checked out of a
> Subversion @@ -2415,9 +2415,9 @@
>  
>      </glossdiv>
>  
> -    <glossdiv id='var-glossary-t'><title>T</title>
> +    <glossdiv id='var-bb-glossary-t'><title>T</title>
>  
> -        <glossentry id='var-T'><glossterm>T</glossterm>
> +        <glossentry id='var-bb-T'><glossterm>T</glossterm>
>              <glossdef>
>                  <para>Points to a directory were BitBake places
>                      temporary files, which consist mostly of task
> logs and @@ -2426,7 +2426,7 @@
>              </glossdef>
>          </glossentry>
>  
> -        <glossentry id='var-TOPDIR'><glossterm>TOPDIR</glossterm>
> +        <glossentry id='var-bb-TOPDIR'><glossterm>TOPDIR</glossterm>
>              <glossdef>
>                  <para>
>                      Points to the build directory.
> diff --git a/bitbake/doc/poky.ent b/bitbake/doc/poky.ent
> index c032e14..85d9c83 100644
> --- a/bitbake/doc/poky.ent
> +++ b/bitbake/doc/poky.ent
> @@ -17,13 +17,6 @@
>  <!ENTITY OE_DOCS_URL "http://docs.openembedded.org">
>  <!ENTITY OH_HOME_URL "http://o-hand.com">
>  <!ENTITY BITBAKE_HOME_URL
> "http://developer.berlios.de/projects/bitbake/"> -<!ENTITY
> ECLIPSE_MAIN_URL "http://www.eclipse.org/downloads"> -<!ENTITY
> ECLIPSE_DL_URL "http://download.eclipse.org"> -<!ENTITY
> ECLIPSE_DL_PLUGIN_URL
> "&YOCTO_DL_URL;/releases/eclipse-plugin/&DISTRO;"> -<!ENTITY
> ECLIPSE_UPDATES_URL "&ECLIPSE_DL_URL;/tm/updates/3.3"> -<!ENTITY
> ECLIPSE_INDIGO_URL "&ECLIPSE_DL_URL;/releases/indigo"> -<!ENTITY
> ECLIPSE_JUNO_URL "&ECLIPSE_DL_URL;/releases/juno"> -<!ENTITY
> ECLIPSE_INDIGO_CDT_URL "&ECLIPSE_DL_URL;tools/cdt/releases/indigo">
> <!ENTITY YOCTO_DOCS_URL "&YOCTO_HOME_URL;/docs"> <!ENTITY
> YOCTO_SOURCES_URL "&YOCTO_HOME_URL;/sources/"> <!ENTITY
> YOCTO_AB_PORT_URL "&YOCTO_AB_URL;:8010"> @@ -31,7 +24,6 @@ <!ENTITY
> YOCTO_POKY_URL "&YOCTO_DL_URL;/releases/poky/"> <!ENTITY
> YOCTO_RELEASE_DL_URL "&YOCTO_DL_URL;/releases/yocto/yocto-&DISTRO;">
> <!ENTITY YOCTO_TOOLCHAIN_DL_URL "&YOCTO_RELEASE_DL_URL;/toolchain/">
> -<!ENTITY YOCTO_ECLIPSE_DL_URL
> "&YOCTO_RELEASE_DL_URL;/eclipse-plugin/indigo;"> <!ENTITY
> YOCTO_ADTINSTALLER_DL_URL "&YOCTO_RELEASE_DL_URL;/adt_installer">
> <!ENTITY YOCTO_POKY_DL_URL
> "&YOCTO_RELEASE_DL_URL;/&YOCTO_POKY;.tar.bz2"> <!ENTITY
> YOCTO_MACHINES_DL_URL "&YOCTO_RELEASE_DL_URL;/machines"> diff --git
> a/bitbake/lib/bb/COW.py b/bitbake/lib/bb/COW.py index
> 7817473..d26e981 100644 --- a/bitbake/lib/bb/COW.py +++
> b/bitbake/lib/bb/COW.py @@ -1,23 +1,8 @@ -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # This is a copy on write dictionary and set which abuses classes to
> try and be nice and fast. #
>  # Copyright (C) 2006 Tim Ansell
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -#
>  #Please Note:
>  # Be careful when using mutable types (ie Dict and Lists) -
> operations involving these are SLOW. # Assign a file to __warn__ to
> get warnings about slow operations. diff --git
> a/bitbake/lib/bb/__init__.py b/bitbake/lib/bb/__init__.py index
> 4bc47c8..c144311 100644 --- a/bitbake/lib/bb/__init__.py
> +++ b/bitbake/lib/bb/__init__.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # BitBake Build System Python Library
>  #
> @@ -8,20 +6,10 @@
>  #
>  # Based on Gentoo's portage.py.
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
> -__version__ = "1.40.0"
> +__version__ = "1.44.0"
>  
>  import sys
>  if sys.version_info < (3, 4, 0):
> diff --git a/bitbake/lib/bb/build.py b/bitbake/lib/bb/build.py
> index 3e2a94e..30a2ba2 100644
> --- a/bitbake/lib/bb/build.py
> +++ b/bitbake/lib/bb/build.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # BitBake 'Build' implementation
>  #
> @@ -10,18 +8,7 @@
>  #
>  # Based on Gentoo's portage.py.
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # Based on functions from the base bb module, Copyright 2003 Holger
> Schurig 
> @@ -67,23 +54,6 @@ else:
>  builtins['bb'] = bb
>  builtins['os'] = os
>  
> -class FuncFailed(Exception):
> -    def __init__(self, name = None, logfile = None):
> -        self.logfile = logfile
> -        self.name = name
> -        if name:
> -            self.msg = 'Function failed: %s' % name
> -        else:
> -            self.msg = "Function failed"
> -
> -    def __str__(self):
> -        if self.logfile and os.path.exists(self.logfile):
> -            msg = ("%s (log file is located at %s)" %
> -                   (self.msg, self.logfile))
> -        else:
> -            msg = self.msg
> -        return msg
> -
>  class TaskBase(event.Event):
>      """Base class for task events"""
>  
> @@ -176,15 +146,33 @@ class LogTee(object):
>  
>      def __repr__(self):
>          return '<LogTee {0}>'.format(self.name)
> +
>      def flush(self):
>          self.outfile.flush()
>  
> -#
> -# pythonexception allows the python exceptions generated to be raised
> -# as the real exceptions (not FuncFailed) and without a backtrace at
> the -# origin of the failure.
> -#
> -def exec_func(func, d, dirs = None, pythonexception=False):
> +
> +class StdoutNoopContextManager:
> +    """
> +    This class acts like sys.stdout, but adds noop __enter__ and
> __exit__ methods.
> +    """
> +    def __enter__(self):
> +        return sys.stdout
> +
> +    def __exit__(self, *exc_info):
> +        pass
> +
> +    def write(self, string):
> +        return sys.stdout.write(string)
> +
> +    def flush(self):
> +        sys.stdout.flush()
> +
> +    @property
> +    def name(self):
> +        return sys.stdout.name
> +
> +
> +def exec_func(func, d, dirs = None):
>      """Execute a BB 'function'"""
>  
>      try:
> @@ -256,7 +244,7 @@ def exec_func(func, d, dirs = None,
> pythonexception=False): 
>      with bb.utils.fileslocked(lockfiles):
>          if ispython:
> -            exec_func_python(func, d, runfile, cwd=adir,
> pythonexception=pythonexception)
> +            exec_func_python(func, d, runfile, cwd=adir)
>          else:
>              exec_func_shell(func, d, runfile, cwd=adir)
>  
> @@ -276,7 +264,7 @@ _functionfmt = """
>  {function}(d)
>  """
>  logformatter = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
> -def exec_func_python(func, d, runfile, cwd=None,
> pythonexception=False): +def exec_func_python(func, d, runfile,
> cwd=None): """Execute a python BB 'function'"""
>  
>      code = _functionfmt.format(function=func)
> @@ -301,13 +289,7 @@ def exec_func_python(func, d, runfile, cwd=None,
> pythonexception=False): bb.methodpool.insert_method(func, text, fn,
> lineno - 1) 
>          comp = utils.better_compile(code, func, "exec_python_func()
> autogenerated")
> -        utils.better_exec(comp, {"d": d}, code, "exec_python_func()
> autogenerated", pythonexception=pythonexception)
> -    except (bb.parse.SkipRecipe, bb.build.FuncFailed):
> -        raise
> -    except:
> -        if pythonexception:
> -            raise
> -        raise FuncFailed(func, None)
> +        utils.better_exec(comp, {"d": d}, code, "exec_python_func()
> autogenerated") finally:
>          bb.debug(2, "Python function %s finished" % func)
>  
> @@ -335,6 +317,42 @@ trap 'bb_exit_handler' 0
>  set -e
>  '''
>  
> +def create_progress_handler(func, progress, logfile, d):
> +    if progress == 'percent':
> +        # Use default regex
> +        return bb.progress.BasicProgressHandler(d, outfile=logfile)
> +    elif progress.startswith('percent:'):
> +        # Use specified regex
> +        return bb.progress.BasicProgressHandler(d,
> regex=progress.split(':', 1)[1], outfile=logfile)
> +    elif progress.startswith('outof:'):
> +        # Use specified regex
> +        return bb.progress.OutOfProgressHandler(d,
> regex=progress.split(':', 1)[1], outfile=logfile)
> +    elif progress.startswith("custom:"):
> +        # Use a custom progress handler that was injected via
> OE_EXTRA_IMPORTS or __builtins__
> +        import functools
> +        from types import ModuleType
> +
> +        parts = progress.split(":", 2)
> +        _, cls, otherargs = parts[0], parts[1], (parts[2] or None)
> if parts[2:] else None
> +        if cls:
> +            def resolve(x, y):
> +                if not x:
> +                    return None
> +                if isinstance(x, ModuleType):
> +                    return getattr(x, y, None)
> +                return x.get(y)
> +            cls_obj = functools.reduce(resolve, cls.split("."),
> bb.utils._context)
> +            if not cls_obj:
> +                # Fall-back on __builtins__
> +                cls_obj = functools.reduce(lambda x, y: x.get(y),
> cls.split("."), __builtins__)
> +            if cls_obj:
> +                return cls_obj(d, outfile=logfile,
> otherargs=otherargs)
> +            bb.warn('%s: unknown custom progress handler in task
> progress varflag value "%s", ignoring' % (func, cls))
> +    else:
> +        bb.warn('%s: invalid task progress varflag value "%s",
> ignoring' % (func, progress)) +
> +    return logfile
> +
>  def exec_func_shell(func, d, runfile, cwd=None):
>      """Execute a shell function from the metadata
>  
> @@ -372,23 +390,13 @@ exit $ret
>              cmd = [fakerootcmd, runfile]
>  
>      if bb.msg.loggerDefaultVerbose:
> -        logfile = LogTee(logger, sys.stdout)
> +        logfile = LogTee(logger, StdoutNoopContextManager())
>      else:
> -        logfile = sys.stdout
> +        logfile = StdoutNoopContextManager()
>  
>      progress = d.getVarFlag(func, 'progress')
>      if progress:
> -        if progress == 'percent':
> -            # Use default regex
> -            logfile = bb.progress.BasicProgressHandler(d,
> outfile=logfile)
> -        elif progress.startswith('percent:'):
> -            # Use specified regex
> -            logfile = bb.progress.BasicProgressHandler(d,
> regex=progress.split(':', 1)[1], outfile=logfile)
> -        elif progress.startswith('outof:'):
> -            # Use specified regex
> -            logfile = bb.progress.OutOfProgressHandler(d,
> regex=progress.split(':', 1)[1], outfile=logfile)
> -        else:
> -            bb.warn('%s: invalid task progress varflag value "%s",
> ignoring' % (func, progress))
> +        logfile = create_progress_handler(func, progress, logfile, d)
>  
>      fifobuffer = bytearray()
>      def readfifo(data):
> @@ -407,6 +415,8 @@ exit $ret
>                      bb.plain(value)
>                  elif cmd == 'bbnote':
>                      bb.note(value)
> +                elif cmd == 'bbverbnote':
> +                    bb.verbnote(value)
>                  elif cmd == 'bbwarn':
>                      bb.warn(value)
>                  elif cmd == 'bberror':
> @@ -436,13 +446,8 @@ exit $ret
>      with open(fifopath, 'r+b', buffering=0) as fifo:
>          try:
>              bb.debug(2, "Executing shell function %s" % func)
> -
> -            try:
> -                with open(os.devnull, 'r+') as stdin:
> -                    bb.process.run(cmd, shell=False, stdin=stdin,
> log=logfile, extrafiles=[(fifo,readfifo)])
> -            except bb.process.CmdError:
> -                logfn = d.getVar('BB_LOGFILE')
> -                raise FuncFailed(func, logfn)
> +            with open(os.devnull, 'r+') as stdin, logfile:
> +                bb.process.run(cmd, shell=False, stdin=stdin,
> log=logfile, extrafiles=[(fifo,readfifo)]) finally:
>              os.unlink(fifopath)
>  
> @@ -570,9 +575,6 @@ def _exec_task(fn, task, d, quieterr):
>              event.fire(TaskStarted(task, logfn, flags, localdata),
> localdata) except (bb.BBHandledException, SystemExit):
>              return 1
> -        except FuncFailed as exc:
> -            logger.error(str(exc))
> -            return 1
>  
>          try:
>              for func in (prefuncs or '').split():
> @@ -580,7 +582,10 @@ def _exec_task(fn, task, d, quieterr):
>              exec_func(task, localdata)
>              for func in (postfuncs or '').split():
>                  exec_func(func, localdata)
> -        except FuncFailed as exc:
> +        except bb.BBHandledException:
> +            event.fire(TaskFailed(task, logfn, localdata, True),
> localdata)
> +            return 1
> +        except Exception as exc:
>              if quieterr:
>                  event.fire(TaskFailedSilent(task, logfn, localdata),
> localdata) else:
> @@ -588,9 +593,6 @@ def _exec_task(fn, task, d, quieterr):
>                  logger.error(str(exc))
>                  event.fire(TaskFailed(task, logfn, localdata,
> errprinted), localdata) return 1
> -        except bb.BBHandledException:
> -            event.fire(TaskFailed(task, logfn, localdata, True),
> localdata)
> -            return 1
>      finally:
>          sys.stdout.flush()
>          sys.stderr.flush()
> @@ -814,6 +816,9 @@ def add_tasks(tasklist, d):
>          task_deps['parents'][task] = []
>          if 'deps' in flags:
>              for dep in flags['deps']:
> +                # Check and warn for "addtask task after foo" while
> foo does not exist
> +                #if not dep in tasklist:
> +                #    bb.warn('%s: dependent task %s for %s does not
> exist' % (d.getVar('PN'), dep, task)) dep = d.expand(dep)
>                  task_deps['parents'][task].append(dep)
>  
> diff --git a/bitbake/lib/bb/cache.py b/bitbake/lib/bb/cache.py
> index 258d679..b6f7da5 100644
> --- a/bitbake/lib/bb/cache.py
> +++ b/bitbake/lib/bb/cache.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # BitBake Cache implementation
>  #
> @@ -15,18 +13,8 @@
>  # Copyright (C) 2005        Holger Hans Peter Freyther
>  # Copyright (C) 2005        ROAD GmbH
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  import sys
> @@ -95,21 +83,21 @@ class CoreRecipeInfo(RecipeInfoCommon):
>          self.appends = self.listvar('__BBAPPEND', metadata)
>          self.nocache = self.getvar('BB_DONT_CACHE', metadata)
>  
> +        self.provides  = self.depvar('PROVIDES', metadata)
> +        self.rprovides = self.depvar('RPROVIDES', metadata)
> +        self.pn = self.getvar('PN', metadata) or
> bb.parse.vars_from_file(filename,metadata)[0]
> +        self.packages = self.listvar('PACKAGES', metadata)
> +        if not self.packages:
> +            self.packages.append(self.pn)
> +        self.packages_dynamic = self.listvar('PACKAGES_DYNAMIC',
> metadata) +
>          self.skipreason = self.getvar('__SKIPPED', metadata)
>          if self.skipreason:
> -            self.pn = self.getvar('PN', metadata) or
> bb.parse.BBHandler.vars_from_file(filename,metadata)[0] self.skipped
> = True
> -            self.provides  = self.depvar('PROVIDES', metadata)
> -            self.rprovides = self.depvar('RPROVIDES', metadata)
>              return
>  
>          self.tasks = metadata.getVar('__BBTASKS', False)
>  
> -        self.pn = self.getvar('PN', metadata)
> -        self.packages = self.listvar('PACKAGES', metadata)
> -        if not self.packages:
> -            self.packages.append(self.pn)
> -
>          self.basetaskhashes = self.taskvar('BB_BASEHASH',
> self.tasks, metadata) self.hashfilename =
> self.getvar('BB_HASHFILENAME', metadata) 
> @@ -125,11 +113,8 @@ class CoreRecipeInfo(RecipeInfoCommon):
>          self.stampclean = self.getvar('STAMPCLEAN', metadata)
>          self.stamp_extrainfo = self.flaglist('stamp-extra-info',
> self.tasks, metadata) self.file_checksums =
> self.flaglist('file-checksums', self.tasks, metadata, True)
> -        self.packages_dynamic = self.listvar('PACKAGES_DYNAMIC',
> metadata) self.depends          = self.depvar('DEPENDS', metadata)
> -        self.provides         = self.depvar('PROVIDES', metadata)
>          self.rdepends         = self.depvar('RDEPENDS', metadata)
> -        self.rprovides        = self.depvar('RPROVIDES', metadata)
>          self.rrecommends      = self.depvar('RRECOMMENDS', metadata)
>          self.rprovides_pkg    = self.pkgvar('RPROVIDES',
> self.packages, metadata) self.rdepends_pkg     =
> self.pkgvar('RDEPENDS', self.packages, metadata) @@ -235,7 +220,7 @@
> class CoreRecipeInfo(RecipeInfoCommon): 
>          cachedata.hashfn[fn] = self.hashfilename
>          for task, taskhash in self.basetaskhashes.items():
> -            identifier = '%s.%s' % (fn, task)
> +            identifier = '%s:%s' % (fn, task)
>              cachedata.basetaskhash[identifier] = taskhash
>  
>          cachedata.inherits[fn] = self.inherits
> @@ -249,7 +234,7 @@ def virtualfn2realfn(virtualfn):
>      Convert a virtual file name to a real one + the associated
> subclass keyword """
>      mc = ""
> -    if virtualfn.startswith('multiconfig:'):
> +    if virtualfn.startswith('mc:'):
>          elems = virtualfn.split(':')
>          mc = elems[1]
>          virtualfn = ":".join(elems[2:])
> @@ -270,7 +255,7 @@ def realfn2virtual(realfn, cls, mc):
>      if cls:
>          realfn = "virtual:" + cls + ":" + realfn
>      if mc:
> -        realfn = "multiconfig:" + mc + ":" + realfn
> +        realfn = "mc:" + mc + ":" + realfn
>      return realfn
>  
>  def variant2virtual(realfn, variant):
> @@ -279,11 +264,11 @@ def variant2virtual(realfn, variant):
>      """
>      if variant == "":
>          return realfn
> -    if variant.startswith("multiconfig:"):
> +    if variant.startswith("mc:"):
>          elems = variant.split(":")
>          if elems[2]:
> -            return "multiconfig:" + elems[1] + ":virtual:" +
> ":".join(elems[2:]) + ":" + realfn
> -        return "multiconfig:" + elems[1] + ":" + realfn
> +            return "mc:" + elems[1] + ":virtual:" +
> ":".join(elems[2:]) + ":" + realfn
> +        return "mc:" + elems[1] + ":" + realfn
>      return "virtual:" + variant + ":" + realfn
>  
>  def parse_recipe(bb_data, bbfile, appends, mc=''):
> @@ -361,7 +346,7 @@ class NoCache(object):
>              bb_data = self.databuilder.mcdata[mc].createCopy()
>              newstores = parse_recipe(bb_data, bbfile, appends, mc)
>              for ns in newstores:
> -                datastores["multiconfig:%s:%s" % (mc, ns)] =
> newstores[ns]
> +                datastores["mc:%s:%s" % (mc, ns)] = newstores[ns]
>  
>          return datastores
>  
> @@ -411,6 +396,15 @@ class Cache(NoCache):
>          else:
>              logger.debug(1, "Cache file %s not found, building..." %
> self.cachefile) 
> +        # We don't use the symlink, its just for debugging
> convinience
> +        symlink = os.path.join(self.cachedir, "bb_cache.dat")
> +        if os.path.exists(symlink):
> +            bb.utils.remove(symlink)
> +        try:
> +            os.symlink(os.path.basename(self.cachefile), symlink)
> +        except OSError:
> +            pass
> +
>      def load_cachefile(self):
>          cachesize = 0
>          previous_progress = 0
> @@ -889,3 +883,56 @@ class MultiProcessCache(object):
>              p.dump([data, self.__class__.CACHE_VERSION])
>  
>          bb.utils.unlockfile(glf)
> +
> +
> +class SimpleCache(object):
> +    """
> +    BitBake multi-process cache implementation
> +
> +    Used by the codeparser & file checksum caches
> +    """
> +
> +    def __init__(self, version):
> +        self.cachefile = None
> +        self.cachedata = None
> +        self.cacheversion = version
> +
> +    def init_cache(self, d, cache_file_name=None, defaultdata=None):
> +        cachedir = (d.getVar("PERSISTENT_DIR") or
> +                    d.getVar("CACHE"))
> +        if not cachedir:
> +            return defaultdata
> +
> +        bb.utils.mkdirhier(cachedir)
> +        self.cachefile = os.path.join(cachedir,
> +                                      cache_file_name or
> self.__class__.cache_file_name)
> +        logger.debug(1, "Using cache in '%s'", self.cachefile)
> +
> +        glf = bb.utils.lockfile(self.cachefile + ".lock")
> +
> +        try:
> +            with open(self.cachefile, "rb") as f:
> +                p = pickle.Unpickler(f)
> +                data, version = p.load()
> +        except:
> +            bb.utils.unlockfile(glf)
> +            return defaultdata
> +
> +        bb.utils.unlockfile(glf)
> +
> +        if version != self.cacheversion:
> +            return defaultdata
> +
> +        return data
> +
> +    def save(self, data):
> +        if not self.cachefile:
> +            return
> +
> +        glf = bb.utils.lockfile(self.cachefile + ".lock")
> +
> +        with open(self.cachefile, "wb") as f:
> +            p = pickle.Pickler(f, -1)
> +            p.dump([data, self.cacheversion])
> +
> +        bb.utils.unlockfile(glf)
> diff --git a/bitbake/lib/bb/cache_extra.py
> b/bitbake/lib/bb/cache_extra.py index 83f4959..bf4226d 100644
> --- a/bitbake/lib/bb/cache_extra.py
> +++ b/bitbake/lib/bb/cache_extra.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # Extra RecipeInfo will be all defined in this file. Currently,
>  # Only Hob (Image Creator) Requests some extra fields. So
> @@ -12,18 +10,8 @@
>  
>  # Copyright (C) 2011, Intel Corporation. All rights reserved.
>  
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from bb.cache import RecipeInfoCommon
>  
> diff --git a/bitbake/lib/bb/checksum.py b/bitbake/lib/bb/checksum.py
> index 4e1598f..5bc8a8f 100644
> --- a/bitbake/lib/bb/checksum.py
> +++ b/bitbake/lib/bb/checksum.py
> @@ -2,18 +2,8 @@
>  #
>  # Copyright (C) 2012 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import glob
>  import operator
> diff --git a/bitbake/lib/bb/codeparser.py
> b/bitbake/lib/bb/codeparser.py index ddd1b97..fd2c473 100644
> --- a/bitbake/lib/bb/codeparser.py
> +++ b/bitbake/lib/bb/codeparser.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  """
>  BitBake code parser
>  
> @@ -33,7 +37,7 @@ from bb.cache import MultiProcessCache
>  logger = logging.getLogger('BitBake.CodeParser')
>  
>  def bbhash(s):
> -    return hashlib.md5(s.encode("utf-8")).hexdigest()
> +    return hashlib.sha256(s.encode("utf-8")).hexdigest()
>  
>  def check_indent(codestr):
>      """If the code is indented, add a top level piece of code to
> 'remove' the indentation""" @@ -140,7 +144,7 @@ class
> CodeParserCache(MultiProcessCache): # so that an existing cache gets
> invalidated. Additionally you'll need # to increment
> __cache_version__ in cache.py in order to ensure that old # recipe
> caches don't trigger "Taskhash mismatch" errors.
> -    CACHE_VERSION = 10
> +    CACHE_VERSION = 11
>  
>      def __init__(self):
>          MultiProcessCache.__init__(self)
> @@ -368,8 +372,9 @@ class ShellParser():
>      def _parse_shell(self, value):
>          try:
>              tokens, _ = pyshyacc.parse(value, eof=True, debug=False)
> -        except pyshlex.NeedMore:
> -            raise sherrors.ShellSyntaxError("Unexpected EOF")
> +        except Exception:
> +            bb.error('Error during parse shell code, the last 5
> lines are:\n%s' % '\n'.join(value.split('\n')[-5:]))
> +            raise
>  
>          self.process_tokens(tokens)
>  
> diff --git a/bitbake/lib/bb/command.py b/bitbake/lib/bb/command.py
> index 6c966e3..378f389 100644
> --- a/bitbake/lib/bb/command.py
> +++ b/bitbake/lib/bb/command.py
> @@ -6,18 +6,8 @@ Provide an interface to interact with the bitbake
> server through 'commands' 
>  # Copyright (C) 2006-2007  Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  """
>  The bitbake server takes 'commands' from its UI/commandline.
> diff --git a/bitbake/lib/bb/compat.py b/bitbake/lib/bb/compat.py
> index de1923d..4935668 100644
> --- a/bitbake/lib/bb/compat.py
> +++ b/bitbake/lib/bb/compat.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  """Code pulled from future python versions, here for compatibility"""
>  
>  from collections import MutableMapping, KeysView, ValuesView,
> ItemsView, OrderedDict diff --git a/bitbake/lib/bb/cooker.py
> b/bitbake/lib/bb/cooker.py index 16681ba..20ef04d 100644
> --- a/bitbake/lib/bb/cooker.py
> +++ b/bitbake/lib/bb/cooker.py
> @@ -1,6 +1,3 @@
> -#!/usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # Copyright (C) 2003, 2004  Chris Larson
>  # Copyright (C) 2003, 2004  Phil Blundell
> @@ -9,19 +6,8 @@
>  # Copyright (C) 2005        ROAD GmbH
>  # Copyright (C) 2006 - 2007 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -
>  
>  import sys, os, glob, os.path, re, time
>  import atexit
> @@ -45,6 +31,7 @@ import pyinotify
>  import json
>  import pickle
>  import codecs
> +import hashserv
>  
>  logger      = logging.getLogger("BitBake")
>  collectlog  = logging.getLogger("BitBake.Collection")
> @@ -175,27 +162,45 @@ class BBCooker:
>  
>          self.configuration = configuration
>  
> +        bb.debug(1, "BBCooker starting %s" % time.time())
> +        sys.stdout.flush()
> +
>          self.configwatcher = pyinotify.WatchManager()
> +        bb.debug(1, "BBCooker pyinotify1 %s" % time.time())
> +        sys.stdout.flush()
> +
>          self.configwatcher.bbseen = []
>          self.configwatcher.bbwatchedfiles = []
>          self.confignotifier = pyinotify.Notifier(self.configwatcher,
> self.config_notifications)
> +        bb.debug(1, "BBCooker pyinotify2 %s" % time.time())
> +        sys.stdout.flush()
>          self.watchmask = pyinotify.IN_CLOSE_WRITE |
> pyinotify.IN_CREATE | pyinotify.IN_DELETE | \
> pyinotify.IN_DELETE_SELF | pyinotify.IN_MODIFY |
> pyinotify.IN_MOVE_SELF | \ pyinotify.IN_MOVED_FROM |
> pyinotify.IN_MOVED_TO self.watcher = pyinotify.WatchManager()
> +        bb.debug(1, "BBCooker pyinotify3 %s" % time.time())
> +        sys.stdout.flush()
>          self.watcher.bbseen = []
>          self.watcher.bbwatchedfiles = []
>          self.notifier = pyinotify.Notifier(self.watcher,
> self.notifications) 
> +        bb.debug(1, "BBCooker pyinotify complete %s" % time.time())
> +        sys.stdout.flush()
> +
>          # If being called by something like tinfoil, we need to
> clean cached data # which may now be invalid
>          bb.parse.clear_cache()
>          bb.parse.BBHandler.cached_statements = {}
>  
>          self.ui_cmdline = None
> +        self.hashserv = None
> +        self.hashservaddr = None
>  
>          self.initConfigurationData()
>  
> +        bb.debug(1, "BBCooker parsed base configuration %s" %
> time.time())
> +        sys.stdout.flush()
> +
>          # we log all events to a file if so directed
>          if self.configuration.writeeventlog:
>              # register the log file writer as UI Handler
> @@ -233,6 +238,9 @@ class BBCooker:
>          # Let SIGHUP exit as SIGTERM
>          signal.signal(signal.SIGHUP, self.sigterm_exception)
>  
> +        bb.debug(1, "BBCooker startup complete %s" % time.time())
> +        sys.stdout.flush()
> +
>      def process_inotify_updates(self):
>          for n in [self.confignotifier, self.notifier]:
>              if n.check_events(timeout=0):
> @@ -367,13 +375,12 @@ class BBCooker:
>          # Copy of the data store which has been expanded.
>          # Used for firing events and accessing variables where
> expansion needs to be accounted for #
> -        bb.parse.init_parser(self.data)
> -
>          if CookerFeatures.BASEDATASTORE_TRACKING in self.featureset:
>              self.disableDataTracking()
>  
> -        self.data.renameVar("__depends", "__base_depends")
> -        self.add_filewatch(self.data.getVar("__base_depends",
> False), self.configwatcher)
> +        for mc in self.databuilder.mcdata.values():
> +            mc.renameVar("__depends", "__base_depends")
> +            self.add_filewatch(mc.getVar("__base_depends", False),
> self.configwatcher) 
>          self.baseconfig_valid = True
>          self.parsecache_valid = False
> @@ -385,6 +392,22 @@ class BBCooker:
>          except prserv.serv.PRServiceConfigError as e:
>              bb.fatal("Unable to start PR Server, exitting")
>  
> +        if self.data.getVar("BB_HASHSERVE") == "auto":
> +            # Create a new hash server bound to a unix domain socket
> +            if not self.hashserv:
> +                dbfile = (self.data.getVar("PERSISTENT_DIR") or
> self.data.getVar("CACHE")) + "/hashserv.db"
> +                self.hashservaddr = "unix://%s/hashserve.sock" %
> self.data.getVar("TOPDIR")
> +                self.hashserv =
> hashserv.create_server(self.hashservaddr, dbfile, sync=False)
> +                self.hashserv.process =
> multiprocessing.Process(target=self.hashserv.serve_forever)
> +                self.hashserv.process.start()
> +            self.data.setVar("BB_HASHSERVE", self.hashservaddr)
> +            self.databuilder.origdata.setVar("BB_HASHSERVE",
> self.hashservaddr)
> +            self.databuilder.data.setVar("BB_HASHSERVE",
> self.hashservaddr)
> +            for mc in self.databuilder.mcdata:
> +                self.databuilder.mcdata[mc].setVar("BB_HASHSERVE",
> self.hashservaddr) +
> +        bb.parse.init_parser(self.data)
> +
>      def enableDataTracking(self):
>          self.configuration.tracking = True
>          if hasattr(self, "data"):
> @@ -488,6 +511,7 @@ class BBCooker:
>          """
>          fn = None
>          envdata = None
> +        mc = ''
>          if not pkgs_to_build:
>              pkgs_to_build = []
>  
> @@ -496,6 +520,12 @@ class BBCooker:
>              self.enableDataTracking()
>              self.reset()
>  
> +        def mc_base(p):
> +            if p.startswith('mc:'):
> +                s = p.split(':')
> +                if len(s) == 2:
> +                    return s[1]
> +            return None
>  
>          if buildfile:
>              # Parse the configuration here. We need to do it
> explicitly here since @@ -506,18 +536,16 @@ class BBCooker:
>              fn = self.matchFile(fn)
>              fn = bb.cache.realfn2virtual(fn, cls, mc)
>          elif len(pkgs_to_build) == 1:
> -            ignore = self.data.getVar("ASSUME_PROVIDED") or ""
> -            if pkgs_to_build[0] in set(ignore.split()):
> -                bb.fatal("%s is in ASSUME_PROVIDED" %
> pkgs_to_build[0])
> +            mc = mc_base(pkgs_to_build[0])
> +            if not mc:
> +                ignore = self.data.getVar("ASSUME_PROVIDED") or ""
> +                if pkgs_to_build[0] in set(ignore.split()):
> +                    bb.fatal("%s is in ASSUME_PROVIDED" %
> pkgs_to_build[0]) 
> -            taskdata, runlist = self.buildTaskData(pkgs_to_build,
> None, self.configuration.abort, allowincomplete=True)
> +                taskdata, runlist =
> self.buildTaskData(pkgs_to_build, None, self.configuration.abort,
> allowincomplete=True) 
> -            mc = runlist[0][0]
> -            fn = runlist[0][3]
> -        else:
> -            envdata = self.data
> -            data.expandKeys(envdata)
> -            parse.ast.runAnonFuncs(envdata)
> +                mc = runlist[0][0]
> +                fn = runlist[0][3]
>  
>          if fn:
>              try:
> @@ -526,6 +554,12 @@ class BBCooker:
>              except Exception as e:
>                  parselog.exception("Unable to read %s", fn)
>                  raise
> +        else:
> +            if not mc in self.databuilder.mcdata:
> +                bb.fatal('Not multiconfig named "%s" found' % mc)
> +            envdata = self.databuilder.mcdata[mc]
> +            data.expandKeys(envdata)
> +            parse.ast.runAnonFuncs(envdata)
>  
>          # Display history
>          with closing(StringIO()) as env:
> @@ -565,10 +599,10 @@ class BBCooker:
>          wildcard = False
>  
>          # Wild card expansion:
> -        # Replace string such as "multiconfig:*:bash"
> -        # into "multiconfig:A:bash multiconfig:B:bash bash"
> +        # Replace string such as "mc:*:bash"
> +        # into "mc:A:bash mc:B:bash bash"
>          for k in targetlist:
> -            if k.startswith("multiconfig:"):
> +            if k.startswith("mc:"):
>                  if wildcard:
>                      bb.fatal('multiconfig conflict')
>                  if k.split(":")[1] == "*":
> @@ -601,7 +635,7 @@ class BBCooker:
>          runlist = []
>          for k in fulltargetlist:
>              mc = ""
> -            if k.startswith("multiconfig:"):
> +            if k.startswith("mc:"):
>                  mc = k.split(":")[1]
>                  k = ":".join(k.split(":")[2:])
>              ktask = task
> @@ -620,13 +654,22 @@ class BBCooker:
>              runlist.append([mc, k, ktask, fn])
>              bb.event.fire(bb.event.TreeDataPreparationProgress(current,
> len(fulltargetlist)), self.data) 
> -        mcdeps = taskdata[mc].get_mcdepends()
> +        havemc = False
> +        for mc in self.multiconfigs:
> +            if taskdata[mc].get_mcdepends():
> +                havemc = True
> +
>          # No need to do check providers if there are no mcdeps or
> not an mc build
> -        if mcdeps and mc:
> -            # Make sure we can provide the multiconfig dependency
> +        if havemc or len(self.multiconfigs) > 1:
>              seen = set()
>              new = True
> +            # Make sure we can provide the multiconfig dependency
>              while new:
> +                mcdeps = set()
> +                # Add unresolved first, so we can get multiconfig
> indirect dependencies on time
> +                for mc in self.multiconfigs:
> +                    taskdata[mc].add_unresolved(localdata[mc],
> self.recipecaches[mc])
> +                    mcdeps |= set(taskdata[mc].get_mcdepends())
>                  new = False
>                  for mc in self.multiconfigs:
>                      for k in mcdeps:
> @@ -641,6 +684,7 @@ class BBCooker:
>                              taskdata[depmc].add_provider(localdata[depmc],
> self.recipecaches[depmc], l[3]) seen.add(k)
>                              new = True
> +
>          for mc in self.multiconfigs:
>              taskdata[mc].add_unresolved(localdata[mc],
> self.recipecaches[mc]) 
> @@ -676,7 +720,7 @@ class BBCooker:
>      @staticmethod
>      def add_mc_prefix(mc, pn):
>          if mc:
> -            return "multiconfig:%s:%s" % (mc, pn)
> +            return "mc:%s:%s" % (mc, pn)
>          return pn
>  
>      def buildDependTree(self, rq, taskdata):
> @@ -875,6 +919,10 @@ class BBCooker:
>              os.unlink('package-depends.dot')
>          except FileNotFoundError:
>              pass
> +        try:
> +            os.unlink('recipe-depends.dot')
> +        except FileNotFoundError:
> +            pass
>  
>          with open('task-depends.dot', 'w') as f:
>              f.write("digraph depends {\n")
> @@ -888,27 +936,6 @@ class BBCooker:
>              f.write("}\n")
>          logger.info("Task dependencies saved to 'task-depends.dot'")
>  
> -        with open('recipe-depends.dot', 'w') as f:
> -            f.write("digraph depends {\n")
> -            pndeps = {}
> -            for task in sorted(depgraph["tdepends"]):
> -                (pn, taskname) = task.rsplit(".", 1)
> -                if pn not in pndeps:
> -                    pndeps[pn] = set()
> -                for dep in sorted(depgraph["tdepends"][task]):
> -                    (deppn, deptaskname) = dep.rsplit(".", 1)
> -                    pndeps[pn].add(deppn)
> -            for pn in sorted(pndeps):
> -                fn = depgraph["pn"][pn]["filename"]
> -                version = depgraph["pn"][pn]["version"]
> -                f.write('"%s" [label="%s\\n%s\\n%s"]\n' % (pn, pn,
> version, fn))
> -                for dep in sorted(pndeps[pn]):
> -                    if dep == pn:
> -                        continue
> -                    f.write('"%s" -> "%s"\n' % (pn, dep))
> -            f.write("}\n")
> -        logger.info("Flattened recipe dependencies saved to
> 'recipe-depends.dot'") -
>      def show_appends_with_no_recipes(self):
>          # Determine which bbappends haven't been applied
>  
> @@ -1191,8 +1218,8 @@ class BBCooker:
>                      continue
>                  elif regex == "":
>                      parselog.debug(1, "BBFILE_PATTERN_%s is empty" %
> c)
> +                    cre = re.compile('^NULL$')
>                      errors = False
> -                    continue
>                  else:
>                      try:
>                          cre = re.compile(regex)
> @@ -1453,7 +1480,7 @@ class BBCooker:
>          ntargets = []
>          for target in runlist:
>              if target[0]:
> -                ntargets.append("multiconfig:%s:%s:%s" % (target[0],
> target[1], target[2]))
> +                ntargets.append("mc:%s:%s:%s" % (target[0],
> target[1], target[2])) ntargets.append("%s:%s" % (target[1],
> target[2])) 
>          for mc in self.multiconfigs:
> @@ -1576,6 +1603,9 @@ class BBCooker:
>          for pkg in pkgs_to_build:
>              if pkg in ignore:
>                  parselog.warning("Explicit target \"%s\" is in
> ASSUME_PROVIDED, ignoring" % pkg)
> +            if pkg.startswith("multiconfig:"):
> +                pkgs_to_build.remove(pkg)
> +                pkgs_to_build.append(pkg.replace("multiconfig:",
> "mc:")) 
>          if 'world' in pkgs_to_build:
>              pkgs_to_build.remove('world')
> @@ -1583,7 +1613,7 @@ class BBCooker:
>                  bb.providers.buildWorldTargetList(self.recipecaches[mc],
> task) for t in self.recipecaches[mc].world_target:
>                      if mc:
> -                        t = "multiconfig:" + mc + ":" + t
> +                        t = "mc:" + mc + ":" + t
>                      pkgs_to_build.append(t)
>  
>          if 'universe' in pkgs_to_build:
> @@ -1602,7 +1632,7 @@ class BBCooker:
>                              bb.debug(1, "Skipping %s for universe
> tasks as task %s doesn't exist" % (t, task)) continue
>                      if mc:
> -                        t = "multiconfig:" + mc + ":" + t
> +                        t = "mc:" + mc + ":" + t
>                      pkgs_to_build.append(t)
>  
>          return pkgs_to_build
> @@ -1615,9 +1645,11 @@ class BBCooker:
>  
>      def post_serve(self):
>          prserv.serv.auto_shutdown()
> +        if self.hashserv:
> +            self.hashserv.process.terminate()
> +            self.hashserv.process.join()
>          bb.event.fire(CookerExit(), self.data)
>  
> -
>      def shutdown(self, force = False):
>          if force:
>              self.state = state.forceshutdown
> @@ -1632,6 +1664,7 @@ class BBCooker:
>  
>      def reset(self):
>          self.initConfigurationData()
> +        self.handlePRServ()
>  
>      def clientComplete(self):
>          """Called when the client is done using the server"""
> @@ -1865,35 +1898,6 @@ class ParsingFailure(Exception):
>          self.recipe = recipe
>          Exception.__init__(self, realexception, recipe)
>  
> -class Feeder(multiprocessing.Process):
> -    def __init__(self, jobs, to_parsers, quit):
> -        self.quit = quit
> -        self.jobs = jobs
> -        self.to_parsers = to_parsers
> -        multiprocessing.Process.__init__(self)
> -
> -    def run(self):
> -        while True:
> -            try:
> -                quit = self.quit.get_nowait()
> -            except queue.Empty:
> -                pass
> -            else:
> -                if quit == 'cancel':
> -                    self.to_parsers.cancel_join_thread()
> -                break
> -
> -            try:
> -                job = self.jobs.pop()
> -            except IndexError:
> -                break
> -
> -            try:
> -                self.to_parsers.put(job, timeout=0.5)
> -            except queue.Full:
> -                self.jobs.insert(0, job)
> -                continue
> -
>  class Parser(multiprocessing.Process):
>      def __init__(self, jobs, results, quit, init, profile):
>          self.jobs = jobs
> @@ -1940,11 +1944,8 @@ class Parser(multiprocessing.Process):
>                  result = pending.pop()
>              else:
>                  try:
> -                    job = self.jobs.get(timeout=0.25)
> -                except queue.Empty:
> -                    continue
> -
> -                if job is None:
> +                    job = self.jobs.pop()
> +                except IndexError:
>                      break
>                  result = self.parse(*job)
>  
> @@ -2028,14 +2029,15 @@ class CookerParser(object):
>                  multiprocessing.util.Finalize(None,
> bb.codeparser.parser_cache_save, exitpriority=1)
> multiprocessing.util.Finalize(None, bb.fetch.fetcher_parse_save,
> exitpriority=1) 
> -            self.feeder_quit = multiprocessing.Queue(maxsize=1)
>              self.parser_quit =
> multiprocessing.Queue(maxsize=self.num_processes)
> -            self.jobs =
> multiprocessing.Queue(maxsize=self.num_processes) self.result_queue =
> multiprocessing.Queue()
> -            self.feeder = Feeder(self.willparse, self.jobs,
> self.feeder_quit)
> -            self.feeder.start()
> +
> +            def chunkify(lst,n):
> +                return [lst[i::n] for i in range(n)]
> +            self.jobs = chunkify(self.willparse, self.num_processes)
> +
>              for i in range(0, self.num_processes):
> -                parser = Parser(self.jobs, self.result_queue,
> self.parser_quit, init, self.cooker.configuration.profile)
> +                parser = Parser(self.jobs[i], self.result_queue,
> self.parser_quit, init, self.cooker.configuration.profile)
> parser.start() self.process_names.append(parser.name)
>                  self.processes.append(parser)
> @@ -2056,17 +2058,20 @@ class CookerParser(object):
>                                              self.total)
>  
>              bb.event.fire(event, self.cfgdata)
> -            self.feeder_quit.put(None)
>              for process in self.processes:
>                  self.parser_quit.put(None)
>          else:
> -            self.feeder_quit.put('cancel')
> -
>              self.parser_quit.cancel_join_thread()
>              for process in self.processes:
>                  self.parser_quit.put(None)
>  
> -            self.jobs.cancel_join_thread()
> +        # Cleanup the queue before call process.join(), otherwise
> there might be
> +        # deadlocks.
> +        while True:
> +            try:
> +               self.result_queue.get(timeout=0.25)
> +            except queue.Empty:
> +                break
>  
>          for process in self.processes:
>              if force:
> @@ -2074,7 +2079,6 @@ class CookerParser(object):
>                  process.terminate()
>              else:
>                  process.join()
> -        self.feeder.join()
>  
>          sync = threading.Thread(target=self.bb_cache.sync)
>          sync.start()
> diff --git a/bitbake/lib/bb/cookerdata.py
> b/bitbake/lib/bb/cookerdata.py index 5df66e6..472423f 100644
> --- a/bitbake/lib/bb/cookerdata.py
> +++ b/bitbake/lib/bb/cookerdata.py
> @@ -1,6 +1,3 @@
> -#!/usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # Copyright (C) 2003, 2004  Chris Larson
>  # Copyright (C) 2003, 2004  Phil Blundell
> @@ -9,23 +6,14 @@
>  # Copyright (C) 2005        ROAD GmbH
>  # Copyright (C) 2006        Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import logging
>  import os
>  import re
>  import sys
> +import hashlib
>  from functools import wraps
>  import bb
>  from bb import data
> @@ -134,6 +122,7 @@ class CookerConfiguration(object):
>          self.profile = False
>          self.nosetscene = False
>          self.setsceneonly = False
> +        self.skipsetscene = False
>          self.invalidate_stamp = False
>          self.dump_signatures = []
>          self.dry_run = False
> @@ -279,12 +268,13 @@ class CookerDataBuilder(object):
>          self.mcdata = {}
>  
>      def parseBaseConfiguration(self):
> +        data_hash = hashlib.sha256()
>          try:
> -            bb.parse.init_parser(self.basedata)
>              self.data = self.parseConfigurationFiles(self.prefiles,
> self.postfiles) 
>              if self.data.getVar("BB_WORKERCONTEXT", False) is None:
>                  bb.fetch.fetcher_init(self.data)
> +            bb.parse.init_parser(self.data)
>              bb.codeparser.parser_cache_init(self.data)
>  
>              bb.event.fire(bb.event.ConfigParsed(), self.data)
> @@ -302,7 +292,7 @@ class CookerDataBuilder(object):
>                  bb.event.fire(bb.event.ConfigParsed(), self.data)
>  
>              bb.parse.init_parser(self.data)
> -            self.data_hash = self.data.get_hash()
> +            data_hash.update(self.data.get_hash().encode('utf-8'))
>              self.mcdata[''] = self.data
>  
>              multiconfig = (self.data.getVar("BBMULTICONFIG") or
> "").split() @@ -310,9 +300,11 @@ class CookerDataBuilder(object):
>                  mcdata = self.parseConfigurationFiles(self.prefiles,
> self.postfiles, config) bb.event.fire(bb.event.ConfigParsed(), mcdata)
>                  self.mcdata[config] = mcdata
> +                data_hash.update(mcdata.get_hash().encode('utf-8'))
>              if multiconfig:
>                  bb.event.fire(bb.event.MultiConfigParsed(self.mcdata),
> self.data) 
> +            self.data_hash = data_hash.hexdigest()
>          except (SyntaxError, bb.BBHandledException):
>              raise bb.BBHandledException
>          except bb.data_smart.ExpansionError as e:
> @@ -354,14 +346,24 @@ class CookerDataBuilder(object):
>              data = parse_config_file(layerconf, data)
>  
>              layers = (data.getVar('BBLAYERS') or "").split()
> +            broken_layers = []
>  
>              data = bb.data.createCopy(data)
>              approved = bb.utils.approved_variables()
> +
> +            # Check whether present layer directories exist
>              for layer in layers:
>                  if not os.path.isdir(layer):
> -                    parselog.critical("Layer directory '%s' does not
> exist! "
> -                                      "Please check BBLAYERS in %s"
> % (layer, layerconf))
> -                    sys.exit(1)
> +                    broken_layers.append(layer)
> +
> +            if broken_layers:
> +                parselog.critical("The following layer directories
> do not exist:")
> +                for layer in broken_layers:
> +                    parselog.critical("   %s", layer)
> +                parselog.critical("Please check BBLAYERS in %s" %
> (layerconf))
> +                sys.exit(1)
> +
> +            for layer in layers:
>                  parselog.debug(2, "Adding layer %s", layer)
>                  if 'HOME' in approved and '~' in layer:
>                      layer = os.path.expanduser(layer)
> @@ -391,7 +393,11 @@ class CookerDataBuilder(object):
>                  bb.fatal("BBFILES_DYNAMIC entries must be of the
> form <collection name>:<filename pattern>, not:\n    %s" % "\n
> ".join(invalid)) layerseries =
> set((data.getVar("LAYERSERIES_CORENAMES") or "").split())
> +            collections_tmp = collections[:]
>              for c in collections:
> +                collections_tmp.remove(c)
> +                if c in collections_tmp:
> +                    bb.fatal("Found duplicated BBFILE_COLLECTIONS
> '%s', check bblayers.conf or layer.conf to fix it." % c) compat =
> set((data.getVar("LAYERSERIES_COMPAT_%s" % c) or "").split()) if
> compat and not (compat & layerseries): bb.fatal("Layer %s is not
> compatible with the core layer which only supports these series: %s
> (layer is compatible with %s)" diff --git
> a/bitbake/lib/bb/daemonize.py b/bitbake/lib/bb/daemonize.py index
> c937675..f01e6ec 100644 --- a/bitbake/lib/bb/daemonize.py +++
> b/bitbake/lib/bb/daemonize.py @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  """
>  Python Daemonizing helper
>  
> diff --git a/bitbake/lib/bb/data.py b/bitbake/lib/bb/data.py
> index d66d98c..0d75d0c 100644
> --- a/bitbake/lib/bb/data.py
> +++ b/bitbake/lib/bb/data.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Data' implementations
>  
> @@ -22,18 +20,7 @@ the speed is more critical here.
>  # Copyright (C) 2003, 2004  Chris Larson
>  # Copyright (C) 2005        Holger Hans Peter Freyther
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # Based on functions from the base bb module, Copyright 2003 Holger
> Schurig 
> @@ -143,7 +130,7 @@ def emit_var(var, o=sys.__stdout__, d = init(),
> all=False): if all:
>              oval = d.getVar(var, False)
>          val = d.getVar(var)
> -    except (KeyboardInterrupt, bb.build.FuncFailed):
> +    except (KeyboardInterrupt):
>          raise
>      except Exception as exc:
>          o.write('# expansion of %s threw %s: %s\n' % (var,
> exc.__class__.__name__, str(exc))) @@ -322,8 +309,6 @@ def
> build_dependencies(key, keys, shelldeps, varflagsexcl, d): if
> varflags.get("python"): value = d.getVarFlag(key, "_content", False)
>                  parser = bb.codeparser.PythonParser(key, logger)
> -                if value and "\t" in value:
> -                    logger.warning("Variable %s contains tabs,
> please remove these (%s)" % (key, d.getVar("FILE")))
> parser.parse_python(value, filename=varflags.get("filename"),
> lineno=varflags.get("lineno")) deps = deps | parser.references deps =
> deps | (keys & parser.execs) @@ -437,8 +422,8 @@ def
> generate_dependency_hash(tasklist, gendeps, lookupcache, whitelist,
> fn): var = lookupcache[dep] if var is not None:
>                  data = data + str(var)
> -        k = fn + "." + task
> -        basehash[k] = hashlib.md5(data.encode("utf-8")).hexdigest()
> +        k = fn + ":" + task
> +        basehash[k] =
> hashlib.sha256(data.encode("utf-8")).hexdigest() taskdeps[task] =
> alldeps 
>      return taskdeps, basehash
> diff --git a/bitbake/lib/bb/data_smart.py
> b/bitbake/lib/bb/data_smart.py index 67af380..dd5c618 100644
> --- a/bitbake/lib/bb/data_smart.py
> +++ b/bitbake/lib/bb/data_smart.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake Smart Dictionary Implementation
>  
> @@ -14,18 +12,8 @@ BitBake build tools.
>  # Copyright (C) 2005        Uli Luckas
>  # Copyright (C) 2005        ROAD GmbH
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. # Based on functions from the base bb module,
> Copyright 2003 Holger Schurig 
>  import copy, re, sys, traceback
> @@ -39,10 +27,11 @@ from bb.COW  import COWDictBase
>  logger = logging.getLogger("BitBake.Data")
>  
>  __setvar_keyword__ = ["_append", "_prepend", "_remove"]
> -__setvar_regexp__ =
> re.compile('(?P<base>.*?)(?P<keyword>_append|_prepend|_remove)(_(?P<add>[^A-Z]*))?$')
> -__expand_var_regexp__ = re.compile(r"\${[^{}@\n\t :]+}")
> +__setvar_regexp__ =
> re.compile(r'(?P<base>.*?)(?P<keyword>_append|_prepend|_remove)(_(?P<add>[^A-Z]*))?$')
> +__expand_var_regexp__ = re.compile(r"\${[a-zA-Z0-9\-_+./~]+?}")
> __expand_python_regexp__ = re.compile(r"\${@.+?}")
> -__whitespace_split__ = re.compile('(\s)') +__whitespace_split__ =
> re.compile(r'(\s)') +__override_regexp__ = re.compile(r'[a-z0-9]+') 
>  def infer_caller_details(loginfo, parent = False, varval = True):
>      """Save the caller the trouble of specifying everything."""
> @@ -597,7 +586,7 @@ class DataSmart(MutableMapping):
>          # aka pay the cookie monster
>          override = var[var.rfind('_')+1:]
>          shortvar = var[:var.rfind('_')]
> -        while override and override.islower():
> +        while override and __override_regexp__.match(override):
>              if shortvar not in self.overridedata:
>                  self.overridedata[shortvar] = []
>              if [var, override] not in self.overridedata[shortvar]:
> @@ -1073,4 +1062,4 @@ class DataSmart(MutableMapping):
>                      data.update({i:value})
>  
>          data_str = str([(k, data[k]) for k in sorted(data.keys())])
> -        return hashlib.md5(data_str.encode("utf-8")).hexdigest()
> +        return hashlib.sha256(data_str.encode("utf-8")).hexdigest()
> diff --git a/bitbake/lib/bb/event.py b/bitbake/lib/bb/event.py
> index 5b1b094..d44621e 100644
> --- a/bitbake/lib/bb/event.py
> +++ b/bitbake/lib/bb/event.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Event' implementation
>  
> @@ -9,18 +7,8 @@ BitBake build tools.
>  
>  # Copyright (C) 2003, 2004  Chris Larson
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os, sys
>  import warnings
> @@ -136,6 +124,7 @@ def fire_class_handlers(event, d):
>  ui_queue = []
>  @atexit.register
>  def print_ui_queue():
> +    global ui_queue
>      """If we're exiting before a UI has been spawned, display any
> queued LogRecords to the console."""
>      logger = logging.getLogger("BitBake")
> @@ -180,6 +169,7 @@ def print_ui_queue():
>              logger.removeHandler(stderr)
>          else:
>              logger.removeHandler(stdout)
> +        ui_queue = []
>  
>  def fire_ui_handlers(event, d):
>      global _thread_lock
> @@ -414,23 +404,6 @@ class RecipeTaskPreProcess(RecipeEvent):
>  class RecipeParsed(RecipeEvent):
>      """ Recipe Parsing Complete """
>  
> -class StampUpdate(Event):
> -    """Trigger for any adjustment of the stamp files to happen"""
> -
> -    def __init__(self, targets, stampfns):
> -        self._targets = targets
> -        self._stampfns = stampfns
> -        Event.__init__(self)
> -
> -    def getStampPrefix(self):
> -        return self._stampfns
> -
> -    def getTargets(self):
> -        return self._targets
> -
> -    stampPrefix = property(getStampPrefix)
> -    targets = property(getTargets)
> -
>  class BuildBase(Event):
>      """Base class for bitbake build events"""
>  
> diff --git a/bitbake/lib/bb/exceptions.py
> b/bitbake/lib/bb/exceptions.py index cd71343..ecbad59 100644
> --- a/bitbake/lib/bb/exceptions.py
> +++ b/bitbake/lib/bb/exceptions.py
> @@ -1,3 +1,6 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
>  
>  import inspect
>  import traceback
> diff --git a/bitbake/lib/bb/fetch2/__init__.py
> b/bitbake/lib/bb/fetch2/__init__.py index 572b71a..1f5f8f1 100644
> --- a/bitbake/lib/bb/fetch2/__init__.py
> +++ b/bitbake/lib/bb/fetch2/__init__.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Fetch' implementations
>  
> @@ -10,18 +8,7 @@ BitBake build tools.
>  # Copyright (C) 2003, 2004  Chris Larson
>  # Copyright (C) 2012  Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # Based on functions from the base bb module, Copyright 2003 Holger
> Schurig 
> @@ -256,7 +243,7 @@ class URI(object):
>  
>          # Identify if the URI is relative or not
>          if urlp.scheme in self._relative_schemes and \
> -           re.compile("^\w+:(?!//)").match(uri):
> +           re.compile(r"^\w+:(?!//)").match(uri):
>              self.relative = True
>  
>          if not self.relative:
> @@ -524,7 +511,7 @@ def fetcher_parse_save():
>  def fetcher_parse_done():
>      _checksum_cache.save_merge()
>  
> -def fetcher_compare_revisions():
> +def fetcher_compare_revisions(d):
>      """
>      Compare the revisions in the persistant cache with current
> values and return true/false on whether they've changed.
> @@ -777,7 +764,8 @@ def get_srcrev(d,
> method_name='sortable_revision'): #
>      format = d.getVar('SRCREV_FORMAT')
>      if not format:
> -        raise FetchError("The SRCREV_FORMAT variable must be set
> when multiple SCMs are used.")
> +        raise FetchError("The SRCREV_FORMAT variable must be set
> when multiple SCMs are used.\n"\
> +                         "The SCMs are:\n%s" % '\n'.join(scms))
>  
>      name_to_rev = {}
>      seenautoinc = False
> @@ -855,10 +843,18 @@ def runfetchcmd(cmd, d, quiet=False,
> cleanup=None, log=None, workdir=None): if val:
>              cmd = 'export ' + var + '=\"%s\"; %s' % (val, cmd)
>  
> +    # Ensure that a _PYTHON_SYSCONFIGDATA_NAME value set by a recipe
> +    # (for example via python3native.bbclass since warrior) is not
> set for
> +    # host Python (otherwise tools like git-make-shallow will fail)
> +    cmd = 'unset _PYTHON_SYSCONFIGDATA_NAME; ' + cmd
> +
>      # Disable pseudo as it may affect ssh, potentially causing it to
> hang. cmd = 'export PSEUDO_DISABLED=1; ' + cmd
>  
> -    logger.debug(1, "Running %s", cmd)
> +    if workdir:
> +        logger.debug(1, "Running '%s' in %s" % (cmd, workdir))
> +    else:
> +        logger.debug(1, "Running %s", cmd)
>  
>      success = False
>      error_message = ""
> @@ -894,7 +890,7 @@ def check_network_access(d, info, url):
>      log remote network access, and error if BB_NO_NETWORK is set or
> the given URI is untrusted
>      """
> -    if d.getVar("BB_NO_NETWORK") == "1":
> +    if bb.utils.to_boolean(d.getVar("BB_NO_NETWORK")):
>          raise NetworkAccess(url, info)
>      elif not trusted_network(d, url):
>          raise UntrustedUrl(url, info)
> @@ -966,7 +962,8 @@ def rename_bad_checksum(ud, suffix):
>  
>      new_localpath = "%s_bad-checksum_%s" % (ud.localpath, suffix)
>      bb.warn("Renaming %s to %s" % (ud.localpath, new_localpath))
> -    bb.utils.movefile(ud.localpath, new_localpath)
> +    if not bb.utils.movefile(ud.localpath, new_localpath):
> +        bb.warn("Renaming %s to %s failed, grep movefile in
> log.do_fetch to see why" % (ud.localpath, new_localpath)) 
>  
>  def try_mirror_url(fetch, origud, ud, ld, check = False):
> @@ -1027,7 +1024,7 @@ def try_mirror_url(fetch, origud, ud, ld, check
> = False): raise
>  
>      except IOError as e:
> -        if e.errno in [os.errno.ESTALE]:
> +        if e.errno in [errno.ESTALE]:
>              logger.warning("Stale Error Observed %s." % ud.url)
>              return False
>          raise
> @@ -1094,7 +1091,7 @@ def trusted_network(d, url):
>      BB_ALLOWED_NETWORKS is set globally or for a specific recipe.
>      Note: modifies SRC_URI & mirrors.
>      """
> -    if d.getVar('BB_NO_NETWORK') == "1":
> +    if bb.utils.to_boolean(d.getVar("BB_NO_NETWORK")):
>          return True
>  
>      pkgname = d.expand(d.getVar('PN', False))
> @@ -1403,7 +1400,7 @@ class FetchMethod(object):
>          Fetch urls
>          Assumes localpath was called first
>          """
> -        raise NoMethodError(url)
> +        raise NoMethodError(urldata.url)
>  
>      def unpack(self, urldata, rootdir, data):
>          iterate = False
> @@ -1469,7 +1466,7 @@ class FetchMethod(object):
>                  else:
>                      cmd = 'rpm2cpio.sh %s | cpio -id' % (file)
>              elif file.endswith('.deb') or file.endswith('.ipk'):
> -                output = subprocess.check_output('ar -t %s' % file,
> preexec_fn=subprocess_setup, shell=True)
> +                output = subprocess.check_output(['ar', '-t', file],
> preexec_fn=subprocess_setup) datafile = None
>                  if output:
>                      for line in output.decode().splitlines():
> @@ -1547,7 +1544,7 @@ class FetchMethod(object):
>          Check the status of a URL
>          Assumes localpath was called first
>          """
> -        logger.info("URL %s could not be checked for status since no
> method exists.", url)
> +        logger.info("URL %s could not be checked for status since no
> method exists.", urldata.url) return True
>  
>      def latest_revision(self, ud, d, name):
> @@ -1555,7 +1552,7 @@ class FetchMethod(object):
>          Look in the cache for the latest revision, if not present
> ask the SCM. """
>          if not hasattr(self, "_latest_revision"):
> -            raise ParameterError("The fetcher for this URL does not
> support _latest_revision", url)
> +            raise ParameterError("The fetcher for this URL does not
> support _latest_revision", ud.url) 
>          revs = bb.persist_data.persist('BB_URI_HEADREVS', d)
>          key = self.generate_revision_key(ud, d, name)
> @@ -1638,7 +1635,7 @@ class Fetch(object):
>              urls = self.urls
>  
>          network = self.d.getVar("BB_NO_NETWORK")
> -        premirroronly = (self.d.getVar("BB_FETCH_PREMIRRORONLY") ==
> "1")
> +        premirroronly =
> bb.utils.to_boolean(self.d.getVar("BB_FETCH_PREMIRRORONLY")) 
>          for u in urls:
>              ud = self.ud[u]
> @@ -1716,7 +1713,7 @@ class Fetch(object):
>                  update_stamp(ud, self.d)
>  
>              except IOError as e:
> -                if e.errno in [os.errno.ESTALE]:
> +                if e.errno in [errno.ESTALE]:
>                      logger.error("Stale Error Observed %s." % u)
>                      raise ChecksumError("Stale Error Detected")
>  
> @@ -1786,7 +1783,7 @@ class Fetch(object):
>  
>          for url in urls:
>              if url not in self.ud:
> -                self.ud[url] = FetchData(url, d)
> +                self.ud[url] = FetchData(url, self.d)
>              ud = self.ud[url]
>              ud.setup_localpath(self.d)
>  
> diff --git a/bitbake/lib/bb/fetch2/bzr.py
> b/bitbake/lib/bb/fetch2/bzr.py index 658502f..c56d875 100644
> --- a/bitbake/lib/bb/fetch2/bzr.py
> +++ b/bitbake/lib/bb/fetch2/bzr.py
> @@ -10,18 +10,8 @@ BitBake 'Fetch' implementation for bzr.
>  #   BitBake build tools.
>  #   Copyright (C) 2003, 2004  Chris Larson
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  import sys
> diff --git a/bitbake/lib/bb/fetch2/clearcase.py
> b/bitbake/lib/bb/fetch2/clearcase.py index 3a6573d..3dd93ad 100644
> --- a/bitbake/lib/bb/fetch2/clearcase.py
> +++ b/bitbake/lib/bb/fetch2/clearcase.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Fetch' clearcase implementation
>  
> @@ -47,18 +45,7 @@ User credentials:
>  """
>  # Copyright (C) 2014 Siemens AG
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  
>  import os
> @@ -67,6 +54,8 @@ import shutil
>  import bb
>  from   bb.fetch2 import FetchMethod
>  from   bb.fetch2 import FetchError
> +from   bb.fetch2 import MissingParameterError
> +from   bb.fetch2 import ParameterError
>  from   bb.fetch2 import runfetchcmd
>  from   bb.fetch2 import logger
>  
> @@ -92,7 +81,7 @@ class ClearCase(FetchMethod):
>          if 'protocol' in ud.parm:
>              ud.proto = ud.parm['protocol']
>          if not ud.proto in ('http', 'https'):
> -            raise fetch2.ParameterError("Invalid protocol type",
> ud.url)
> +            raise ParameterError("Invalid protocol type", ud.url)
>  
>          ud.vob = ''
>          if 'vob' in ud.parm:
> diff --git a/bitbake/lib/bb/fetch2/cvs.py
> b/bitbake/lib/bb/fetch2/cvs.py index 0e0a319..1b35ba4 100644
> --- a/bitbake/lib/bb/fetch2/cvs.py
> +++ b/bitbake/lib/bb/fetch2/cvs.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Fetch' implementations
>  
> @@ -10,20 +8,9 @@ BitBake build tools.
>  
>  # Copyright (C) 2003, 2004  Chris Larson
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -#
> -#Based on functions from the base bb module, Copyright 2003 Holger
> Schurig +# Based on functions from the base bb module, Copyright 2003
> Holger Schurig #
>  
>  import os
> diff --git a/bitbake/lib/bb/fetch2/git.py
> b/bitbake/lib/bb/fetch2/git.py index 59a2ee8..2d1d2ca 100644
> --- a/bitbake/lib/bb/fetch2/git.py
> +++ b/bitbake/lib/bb/fetch2/git.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Fetch' git implementation
>  
> @@ -55,20 +53,10 @@ Supported SRC_URI options are:
>  
>  """
>  
> -#Copyright (C) 2005 Richard Purdie
> +# Copyright (C) 2005 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import collections
>  import errno
> @@ -199,7 +187,7 @@ class Git(FetchMethod):
>              depth_default = 1
>          ud.shallow_depths = collections.defaultdict(lambda:
> depth_default) 
> -        revs_default = d.getVar("BB_GIT_SHALLOW_REVS", True)
> +        revs_default = d.getVar("BB_GIT_SHALLOW_REVS")
>          ud.shallow_revs = []
>          ud.branches = {}
>          for pos, name in enumerate(ud.names):
> @@ -318,7 +306,7 @@ class Git(FetchMethod):
>      def try_premirror(self, ud, d):
>          # If we don't do this, updating an existing checkout with
> only premirrors # is not possible
> -        if d.getVar("BB_FETCH_PREMIRRORONLY") is not None:
> +        if bb.utils.to_boolean(d.getVar("BB_FETCH_PREMIRRORONLY")):
>              return True
>          if os.path.exists(ud.clonedir):
>              return False
> @@ -476,6 +464,8 @@ class Git(FetchMethod):
>          if os.path.exists(destdir):
>              bb.utils.prunedir(destdir)
>  
> +        need_lfs = ud.parm.get("lfs", "1") == "1"
> +
>          source_found = False
>          source_error = []
>  
> @@ -503,6 +493,13 @@ class Git(FetchMethod):
>  
>          repourl = self._get_repo_url(ud)
>          runfetchcmd("%s remote set-url origin %s" % (ud.basecmd,
> repourl), d, workdir=destdir) +
> +        if self._contains_lfs(ud, d, destdir):
> +            if need_lfs and not self._find_git_lfs(d):
> +                raise bb.fetch2.FetchError("Repository %s has LFS
> content, install git-lfs on host to download (or set lfs=0 to ignore
> it)" % (repourl))
> +            else:
> +                bb.note("Repository %s has LFS content but it is not
> being fetched" % (repourl)) +
>          if not ud.nocheckout:
>              if subdir != "":
>                  runfetchcmd("%s read-tree %s%s" % (ud.basecmd,
> ud.revisions[ud.names[0]], readpathspec), d, @@ -522,9 +519,17 @@
> class Git(FetchMethod): def clean(self, ud, d):
>          """ clean the git directory """
>  
> -        bb.utils.remove(ud.localpath, True)
> -        bb.utils.remove(ud.fullmirror)
> -        bb.utils.remove(ud.fullmirror + ".done")
> +        to_remove = [ud.localpath, ud.fullmirror, ud.fullmirror +
> ".done"]
> +        # The localpath is a symlink to clonedir when it is cloned
> from a
> +        # mirror, so remove both of them.
> +        if os.path.islink(ud.localpath):
> +            clonedir = os.path.realpath(ud.localpath)
> +            to_remove.append(clonedir)
> +
> +        for r in to_remove:
> +            if os.path.exists(r):
> +                bb.note('Removing %s' % r)
> +                bb.utils.remove(r, True)
>  
>      def supports_srcrev(self):
>          return True
> @@ -545,6 +550,27 @@ class Git(FetchMethod):
>              raise bb.fetch2.FetchError("The command '%s' gave output
> with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
> return output.split()[0] != "0" 
> +    def _contains_lfs(self, ud, d, wd):
> +        """
> +        Check if the repository has 'lfs' (large file) content
> +        """
> +        cmd = "%s grep lfs HEAD:.gitattributes | wc -l" % (
> +                ud.basecmd)
> +        try:
> +            output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
> +            if int(output) > 0:
> +                return True
> +        except (bb.fetch2.FetchError,ValueError):
> +            pass
> +        return False
> +
> +    def _find_git_lfs(self, d):
> +        """
> +        Return True if git-lfs can be found, False otherwise.
> +        """
> +        import shutil
> +        return shutil.which("git-lfs", path=d.getVar('PATH')) is not
> None +
>      def _get_repo_url(self, ud):
>          """
>          Return the repository URL
> @@ -615,7 +641,7 @@ class Git(FetchMethod):
>          """
>          pupver = ('', '')
>  
> -        tagregex = re.compile(d.getVar('UPSTREAM_CHECK_GITTAGREGEX')
> or "(?P<pver>([0-9][\.|_]?)+)")
> +        tagregex = re.compile(d.getVar('UPSTREAM_CHECK_GITTAGREGEX')
> or r"(?P<pver>([0-9][\.|_]?)+)") try:
>              output = self._lsremote(ud, d, "refs/tags/*")
>          except (bb.fetch2.FetchError, bb.fetch2.NetworkAccess) as e:
> @@ -630,7 +656,7 @@ class Git(FetchMethod):
>  
>              tag_head = line.split("/")[-1]
>              # Ignore non-released branches
> -            m = re.search("(alpha|beta|rc|final)+", tag_head)
> +            m = re.search(r"(alpha|beta|rc|final)+", tag_head)
>              if m:
>                  continue
>  
> diff --git a/bitbake/lib/bb/fetch2/gitannex.py
> b/bitbake/lib/bb/fetch2/gitannex.py index a9b69ca..1d497dc 100644
> --- a/bitbake/lib/bb/fetch2/gitannex.py
> +++ b/bitbake/lib/bb/fetch2/gitannex.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Fetch' git annex implementation
>  """
> @@ -7,18 +5,8 @@ BitBake 'Fetch' git annex implementation
>  # Copyright (C) 2014 Otavio Salvador
>  # Copyright (C) 2014 O.S. Systems Software LTDA.
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  import bb
> diff --git a/bitbake/lib/bb/fetch2/gitsm.py
> b/bitbake/lib/bb/fetch2/gitsm.py index 35729db..c622771 100644
> --- a/bitbake/lib/bb/fetch2/gitsm.py
> +++ b/bitbake/lib/bb/fetch2/gitsm.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Fetch' git submodules implementation
>  
> @@ -16,18 +14,8 @@ NOTE: Switching a SRC_URI from "git://" to
> "gitsm://" requires a clean of your r 
>  # Copyright (C) 2013 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  import bb
> @@ -45,60 +33,97 @@ class GitSM(Git):
>          """
>          return ud.type in ['gitsm']
>  
> -    @staticmethod
> -    def parse_gitmodules(gitmodules):
> -        modules = {}
> -        module = ""
> -        for line in gitmodules.splitlines():
> -            if line.startswith('[submodule'):
> -                module = line.split('"')[1]
> -                modules[module] = {}
> -            elif module and line.strip().startswith('path'):
> -                path = line.split('=')[1].strip()
> -                modules[module]['path'] = path
> -            elif module and line.strip().startswith('url'):
> -                url = line.split('=')[1].strip()
> -                modules[module]['url'] = url
> -        return modules
> -
> -    def update_submodules(self, ud, d):
> +    def process_submodules(self, ud, workdir, function, d):
> +        """
> +        Iterate over all of the submodules in this repository and
> execute
> +        the 'function' for each of them.
> +        """
> +
>          submodules = []
>          paths = {}
> +        revision = {}
>          uris = {}
> -        local_paths = {}
> -
> +        subrevision = {}
> +
> +        def parse_gitmodules(gitmodules):
> +            modules = {}
> +            module = ""
> +            for line in gitmodules.splitlines():
> +                if line.startswith('[submodule'):
> +                    module = line.split('"')[1]
> +                    modules[module] = {}
> +                elif module and line.strip().startswith('path'):
> +                    path = line.split('=')[1].strip()
> +                    modules[module]['path'] = path
> +                elif module and line.strip().startswith('url'):
> +                    url = line.split('=')[1].strip()
> +                    modules[module]['url'] = url
> +            return modules
> +
> +        # Collect the defined submodules, and their attributes
>          for name in ud.names:
>              try:
> -                gitmodules = runfetchcmd("%s show %s:.gitmodules" %
> (ud.basecmd, ud.revisions[name]), d, quiet=True, workdir=ud.clonedir)
> +                gitmodules = runfetchcmd("%s show %s:.gitmodules" %
> (ud.basecmd, ud.revisions[name]), d, quiet=True, workdir=workdir)
> except: # No submodules to update
>                  continue
>  
> -            for m, md in self.parse_gitmodules(gitmodules).items():
> +            for m, md in parse_gitmodules(gitmodules).items():
> +                try:
> +                    module_hash = runfetchcmd("%s ls-tree -z -d %s
> %s" % (ud.basecmd, ud.revisions[name], md['path']), d, quiet=True,
> workdir=workdir)
> +                except:
> +                    # If the command fails, we don't have a valid
> file to check.  If it doesn't
> +                    # fail -- it still might be a failure, see next
> check...
> +                    module_hash = ""
> +
> +                if not module_hash:
> +                    logger.debug(1, "submodule %s is defined, but is
> not initialized in the repository. Skipping", m)
> +                    continue
> +
>                  submodules.append(m)
>                  paths[m] = md['path']
> +                revision[m] = ud.revisions[name]
>                  uris[m] = md['url']
> +                subrevision[m] = module_hash.split()[2]
> +
> +                # Convert relative to absolute uri based on parent
> uri if uris[m].startswith('..'):
>                      newud = copy.copy(ud)
> -                    newud.path =
> os.path.realpath(os.path.join(newud.path, md['url']))
> +                    newud.path =
> os.path.realpath(os.path.join(newud.path, uris[m])) uris[m] =
> Git._get_repo_url(self, newud) 
>          for module in submodules:
> -            module_hash = runfetchcmd("%s ls-tree -z -d %s %s" %
> (ud.basecmd, ud.revisions[name], paths[module]), d, quiet=True,
> workdir=ud.clonedir)
> -            module_hash = module_hash.split()[2]
> +            # Translate the module url into a SRC_URI
> +
> +            if "://" in uris[module]:
> +                # Properly formated URL already
> +                proto = uris[module].split(':', 1)[0]
> +                url = uris[module].replace('%s:' % proto, 'gitsm:',
> 1)
> +            else:
> +                if ":" in uris[module]:
> +                    # Most likely an SSH style reference
> +                    proto = "ssh"
> +                    if ":/" in uris[module]:
> +                        # Absolute reference, easy to convert..
> +                        url = "gitsm://" +
> uris[module].replace(':/', '/', 1)
> +                    else:
> +                        # Relative reference, no way to know if this
> is right!
> +                        logger.warning("Submodule included by %s
> refers to relative ssh reference %s.  References may fail if not
> absolute." % (ud.url, uris[module]))
> +                        url = "gitsm://" + uris[module].replace(':',
> '/', 1)
> +                else:
> +                    # This has to be a file reference
> +                    proto = "file"
> +                    url = "gitsm://" + uris[module]
>  
> -            # Build new SRC_URI
> -            proto = uris[module].split(':', 1)[0]
> -            url = uris[module].replace('%s:' % proto, 'gitsm:', 1)
>              url += ';protocol=%s' % proto
>              url += ";name=%s" % module
> -            url += ";bareclone=1;nocheckout=1;nobranch=1"
> +            url += ";subpath=%s" % module
>  
>              ld = d.createCopy()
>              # Not necessary to set SRC_URI, since we're passing the
> URI to # Fetch.
>              #ld.setVar('SRC_URI', url)
> -            ld.setVar('SRCREV_%s' % module, module_hash)
> +            ld.setVar('SRCREV_%s' % module, subrevision[module])
>  
>              # Workaround for issues with SRCPV/SRCREV_FORMAT errors
>              # error refer to 'multiple' repositories.  Only the
> repository @@ -106,145 +131,85 @@ class GitSM(Git):
>              ld.setVar('SRCPV', d.getVar('SRCPV'))
>              ld.setVar('SRCREV_FORMAT', module)
>  
> -            newfetch = Fetch([url], ld, cache=False)
> -            newfetch.download()
> -            local_paths[module] = newfetch.localpath(url)
> +            function(ud, url, module, paths[module], ld)
>  
> -            # Correct the submodule references to the local download
> version...
> -            runfetchcmd("%(basecmd)s config submodule.%(module)s.url
> %(url)s" % {'basecmd': ud.basecmd, 'module': module, 'url' :
> local_paths[module]}, d, workdir=ud.clonedir) -
> -            symlink_path = os.path.join(ud.clonedir, 'modules',
> paths[module])
> -            if not os.path.exists(symlink_path):
> -                try:
> -                    os.makedirs(os.path.dirname(symlink_path),
> exist_ok=True)
> -                except OSError:
> -                    pass
> -                os.symlink(local_paths[module], symlink_path)
> -
> -        return True
> +        return submodules != []
>  
>      def need_update(self, ud, d):
> -        main_repo_needs_update = Git.need_update(self, ud, d)
> -
> -        # First check that the main repository has enough history
> fetched. If it doesn't, then we don't
> -        # even have the .gitmodules and gitlinks for the submodules
> to attempt asking whether the
> -        # submodules' histories are recent enough.
> -        if main_repo_needs_update:
> +        if Git.need_update(self, ud, d):
>              return True
>  
> -        # Now check that the submodule histories are new enough. The
> git-submodule command doesn't have
> -        # any clean interface for doing this aside from just
> attempting the checkout (with network
> -        # fetched disabled).
> -        return not self.update_submodules(ud, d)
> +        try:
> +            # Check for the nugget dropped by the download operation
> +            known_srcrevs = runfetchcmd("%s config --get-all
> bitbake.srcrev" % \
> +                            (ud.basecmd), d, workdir=ud.clonedir)
>  
> -    def download(self, ud, d):
> -        Git.download(self, ud, d)
> +            if ud.revisions[ud.names[0]] not in
> known_srcrevs.split():
> +                return True
> +        except bb.fetch2.FetchError:
> +            # No srcrev nuggets, so this is new and needs to be
> updated
> +            return True
>  
> -        if not ud.shallow or ud.localpath != ud.fullshallow:
> -            self.update_submodules(ud, d)
> +        return False
>  
> -    def copy_submodules(self, submodules, ud, destdir, d):
> -        if ud.bareclone:
> -            repo_conf = destdir
> -        else:
> -            repo_conf = os.path.join(destdir, '.git')
> +    def download(self, ud, d):
> +        def download_submodule(ud, url, module, modpath, d):
> +            url += ";bareclone=1;nobranch=1"
>  
> -        if submodules and not os.path.exists(os.path.join(repo_conf,
> 'modules')):
> -            os.mkdir(os.path.join(repo_conf, 'modules'))
> +            # Is the following still needed?
> +            #url += ";nocheckout=1"
>  
> -        for module, md in submodules.items():
> -            srcpath = os.path.join(ud.clonedir, 'modules',
> md['path'])
> -            modpath = os.path.join(repo_conf, 'modules', md['path'])
> +            try:
> +                newfetch = Fetch([url], d, cache=False)
> +                newfetch.download()
> +                # Drop a nugget to add each of the srcrevs we've
> fetched (used by need_update)
> +                runfetchcmd("%s config --add bitbake.srcrev %s" % \
> +                            (ud.basecmd, ud.revisions[ud.names[0]]),
> d, workdir=ud.clonedir)
> +            except Exception as e:
> +                logger.error('gitsm: submodule download failed: %s
> %s' % (type(e).__name__, str(e)))
> +                raise
>  
> -            if os.path.exists(srcpath):
> -                if os.path.exists(os.path.join(srcpath, '.git')):
> -                    srcpath = os.path.join(srcpath, '.git')
> +        Git.download(self, ud, d)
> +        self.process_submodules(ud, ud.clonedir, download_submodule,
> d) 
> -                target = modpath
> -                if os.path.exists(modpath):
> -                    target = os.path.dirname(modpath)
> +    def unpack(self, ud, destdir, d):
> +        def unpack_submodules(ud, url, module, modpath, d):
> +            url += ";bareclone=1;nobranch=1"
>  
> -                os.makedirs(os.path.dirname(target), exist_ok=True)
> -                runfetchcmd("cp -fpLR %s %s" % (srcpath, target), d)
> -            elif os.path.exists(modpath):
> -                # Module already exists, likely unpacked from a
> shallow mirror clone
> -                pass
> +            # Figure out where we clone over the bare submodules...
> +            if ud.bareclone:
> +                repo_conf = ud.destdir
>              else:
> -                # This is fatal, as we do NOT want git-submodule to
> hit the network
> -                raise bb.fetch2.FetchError('Submodule %s does not
> exist in %s or %s.' % (module, srcpath, modpath)) -
> -    def clone_shallow_local(self, ud, dest, d):
> -        super(GitSM, self).clone_shallow_local(ud, dest, d)
> +                repo_conf = os.path.join(ud.destdir, '.git')
>  
> -        # Copy over the submodules' fetched histories too.
> -        repo_conf = os.path.join(dest, '.git')
> -
> -        submodules = []
> -        for name in ud.names:
>              try:
> -                gitmodules = runfetchcmd("%s show %s:.gitmodules" %
> (ud.basecmd, ud.revision), d, quiet=True, workdir=dest)
> -            except:
> -                # No submodules to update
> -                continue
> +                newfetch = Fetch([url], d, cache=False)
> +
> newfetch.unpack(root=os.path.dirname(os.path.join(repo_conf,
> 'modules', module)))
> +            except Exception as e:
> +                logger.error('gitsm: submodule unpack failed: %s %s'
> % (type(e).__name__, str(e)))
> +                raise
>  
> -            submodules = self.parse_gitmodules(gitmodules)
> -            self.copy_submodules(submodules, ud, dest, d)
> +            local_path = newfetch.localpath(url)
>  
> -    def unpack(self, ud, destdir, d):
> -        Git.unpack(self, ud, destdir, d)
> +            # Correct the submodule references to the local download
> version...
> +            runfetchcmd("%(basecmd)s config submodule.%(module)s.url
> %(url)s" % {'basecmd': ud.basecmd, 'module': module, 'url' :
> local_path}, d, workdir=ud.destdir) 
> -        # Copy over the submodules' fetched histories too.
> -        if ud.bareclone:
> -            repo_conf = ud.destdir
> -        else:
> -            repo_conf = os.path.join(ud.destdir, '.git')
> +            if ud.shallow:
> +                runfetchcmd("%(basecmd)s config
> submodule.%(module)s.shallow true" % {'basecmd': ud.basecmd,
> 'module': module}, d, workdir=ud.destdir) 
> -        update_submodules = False
> -        paths = {}
> -        uris = {}
> -        local_paths = {}
> -        for name in ud.names:
> +            # Ensure the submodule repository is NOT set to bare,
> since we're checking it out... try:
> -                gitmodules = runfetchcmd("%s show HEAD:.gitmodules"
> % (ud.basecmd), d, quiet=True, workdir=ud.destdir)
> +                runfetchcmd("%s config core.bare false" %
> (ud.basecmd), d, quiet=True, workdir=os.path.join(repo_conf,
> 'modules', module)) except:
> -                # No submodules to update
> -                continue
> -
> -            submodules = self.parse_gitmodules(gitmodules)
> -            self.copy_submodules(submodules, ud, ud.destdir, d)
> -
> -            submodules_queue = [(module, os.path.join(repo_conf,
> 'modules', md['path'])) for module, md in submodules.items()]
> -            while len(submodules_queue) != 0:
> -                module, modpath = submodules_queue.pop()
> -
> -                # add submodule children recursively
> -                try:
> -                    gitmodules = runfetchcmd("%s show
> HEAD:.gitmodules" % (ud.basecmd), d, quiet=True, workdir=modpath)
> -                    for m, md in
> self.parse_gitmodules(gitmodules).items():
> -                        submodules_queue.append([m,
> os.path.join(modpath, 'modules', md['path'])])
> -                except:
> -                    # no children
> -                    pass
> -
> +                logger.error("Unable to set git config core.bare to
> false for %s" % os.path.join(repo_conf, 'modules', module))
> +                raise
>  
> -                # There are submodules to update
> -                update_submodules = True
> -
> -                # Determine (from the submodule) the correct url to
> reference
> -                try:
> -                    output = runfetchcmd("%(basecmd)s config
> remote.origin.url" % {'basecmd': ud.basecmd}, d, workdir=modpath)
> -                except bb.fetch2.FetchError as e:
> -                    # No remote url defined in this submodule
> -                    continue
> -
> -                local_paths[module] = output
> -
> -                # Setup the local URL properly (like git submodule
> init or sync would do...)
> -                runfetchcmd("%(basecmd)s config
> submodule.%(module)s.url %(url)s" % {'basecmd': ud.basecmd, 'module':
> module, 'url' : local_paths[module]}, d, workdir=ud.destdir)
> +        Git.unpack(self, ud, destdir, d)
>  
> -                # Ensure the submodule repository is NOT set to
> bare, since we're checking it out...
> -                runfetchcmd("%s config core.bare false" %
> (ud.basecmd), d, quiet=True, workdir=modpath)
> +        ret = self.process_submodules(ud, ud.destdir,
> unpack_submodules, d) 
> -        if update_submodules:
> -            # Run submodule update, this sets up the directories --
> without touching the config
> +        if not ud.bareclone and ret:
> +            # All submodules should already be downloaded and
> configured in the tree.  This simply sets
> +            # up the configuration and checks out the files.  The
> main project config should remain
> +            # unmodified, and no download from the internet should
> occur. runfetchcmd("%s submodule update --recursive --no-fetch" %
> (ud.basecmd), d, quiet=True, workdir=ud.destdir) diff --git
> a/bitbake/lib/bb/fetch2/hg.py b/bitbake/lib/bb/fetch2/hg.py index
> 936d043..15d729e 100644 --- a/bitbake/lib/bb/fetch2/hg.py
> +++ b/bitbake/lib/bb/fetch2/hg.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Fetch' implementation for mercurial DRCS (hg).
>  
> @@ -9,20 +7,10 @@ BitBake 'Fetch' implementation for mercurial DRCS
> (hg). # Copyright (C) 2004        Marcin Juszkiewicz
>  # Copyright (C) 2007        Robert Schuster
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # Based on functions from the base bb module, Copyright 2003 Holger
> Schurig +#
>  
>  import os
>  import sys
> @@ -99,7 +87,7 @@ class Hg(FetchMethod):
>      def try_premirror(self, ud, d):
>          # If we don't do this, updating an existing checkout with
> only premirrors # is not possible
> -        if d.getVar("BB_FETCH_PREMIRRORONLY") is not None:
> +        if bb.utils.to_boolean(d.getVar("BB_FETCH_PREMIRRORONLY")):
>              return True
>          if os.path.exists(ud.moddir):
>              return False
> diff --git a/bitbake/lib/bb/fetch2/local.py
> b/bitbake/lib/bb/fetch2/local.py index a114ac1..01d9ff9 100644
> --- a/bitbake/lib/bb/fetch2/local.py
> +++ b/bitbake/lib/bb/fetch2/local.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Fetch' implementations
>  
> @@ -10,20 +8,10 @@ BitBake build tools.
>  
>  # Copyright (C) 2003, 2004  Chris Larson
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # Based on functions from the base bb module, Copyright 2003 Holger
> Schurig +#
>  
>  import os
>  import urllib.request, urllib.parse, urllib.error
> diff --git a/bitbake/lib/bb/fetch2/npm.py
> b/bitbake/lib/bb/fetch2/npm.py index 65bf5a3..9700e61 100644
> --- a/bitbake/lib/bb/fetch2/npm.py
> +++ b/bitbake/lib/bb/fetch2/npm.py
> @@ -1,5 +1,6 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
>  """
>  BitBake 'Fetch' NPM implementation
>  
> @@ -100,11 +101,19 @@ class Npm(FetchMethod):
>              return False
>          return True
>  
> -    def _runwget(self, ud, d, command, quiet):
> -        logger.debug(2, "Fetching %s using command '%s'" % (ud.url,
> command))
> -        bb.fetch2.check_network_access(d, command, ud.url)
> +    def _runpack(self, ud, d, pkgfullname: str, quiet=False) -> str:
> +        """
> +        Runs npm pack on a full package name.
> +        Returns the filename of the downloaded package
> +        """
> +        bb.fetch2.check_network_access(d, pkgfullname, ud.registry)
>          dldir = d.getVar("DL_DIR")
> -        runfetchcmd(command, d, quiet, workdir=dldir)
> +        dldir = os.path.join(dldir, ud.prefixdir)
> +
> +        command = "npm pack {} --registry {}".format(pkgfullname,
> ud.registry)
> +        logger.debug(2, "Fetching {} using command '{}' in
> {}".format(pkgfullname, command, dldir))
> +        filename = runfetchcmd(command, d, quiet, workdir=dldir)
> +        return filename.rstrip()
>  
>      def _unpackdep(self, ud, pkg, data, destdir, dldir, d):
>          file = data[pkg]['tgz']
> @@ -150,20 +159,11 @@ class Npm(FetchMethod):
>          Parse the output of npm view --json; the last JSON result
>          is assumed to be the one that we're interested in.
>          '''
> -        pdata = None
> -        outdeps = {}
> -        datalines = []
> -        bracelevel = 0
> -        for line in output.splitlines():
> -            if bracelevel:
> -                datalines.append(line)
> -            elif '{' in line:
> -                datalines = []
> -                datalines.append(line)
> -            bracelevel = bracelevel + line.count('{') -
> line.count('}')
> -        if datalines:
> -            pdata = json.loads('\n'.join(datalines))
> -        return pdata
> +        pdata = json.loads(output);
> +        try:
> +            return pdata[-1]
> +        except:
> +            return pdata
>  
>      def _getdependencies(self, pkg, data, version, d, ud,
> optional=False, fetchedlist=None): if fetchedlist is None:
> @@ -171,6 +171,9 @@ class Npm(FetchMethod):
>          pkgfullname = pkg
>          if version != '*' and not '/' in version:
>              pkgfullname += "@'%s'" % version
> +        if pkgfullname in fetchedlist:
> +            return
> +
>          logger.debug(2, "Calling getdeps on %s" % pkg)
>          fetchcmd = "npm view %s --json --registry %s" %
> (pkgfullname, ud.registry) output = runfetchcmd(fetchcmd, d, True)
> @@ -190,15 +193,10 @@ class Npm(FetchMethod):
>                  if (not blacklist and 'linux' not in pkg_os) or
> '!linux' in pkg_os: logger.debug(2, "Skipping %s since it's
> incompatible with Linux" % pkg) return
> -        #logger.debug(2, "Output URL is %s - %s - %s" %
> (ud.basepath, ud.basename, ud.localfile))
> -        outputurl = pdata['dist']['tarball']
> +        filename = self._runpack(ud, d, pkgfullname)
>          data[pkg] = {}
> -        data[pkg]['tgz'] = os.path.basename(outputurl)
> -        if outputurl in fetchedlist:
> -            return
> -
> -        self._runwget(ud, d, "%s --directory-prefix=%s %s" %
> (self.basecmd, ud.prefixdir, outputurl), False)
> -        fetchedlist.append(outputurl)
> +        data[pkg]['tgz'] = filename
> +        fetchedlist.append(pkgfullname)
>  
>          dependencies = pdata.get('dependencies', {})
>          optionalDependencies = pdata.get('optionalDependencies', {})
> @@ -225,17 +223,12 @@ class Npm(FetchMethod):
>                      if obj == pkg:
>                          self._getshrinkeddependencies(obj,
> data['dependencies'][obj], data['dependencies'][obj]['version'], d,
> ud, lockdown, manifest, False) return
> -        outputurl = "invalid"
> -        if ('resolved' not in data) or (not
> data['resolved'].startswith('http://') and not
> data['resolved'].startswith('https://')):
> -            # will be the case for ${PN}
> -            fetchcmd = "npm view %s@%s dist.tarball --registry %s" %
> (pkg, version, ud.registry)
> -            logger.debug(2, "Found this matching URL: %s" %
> str(fetchcmd))
> -            outputurl = runfetchcmd(fetchcmd, d, True)
> -        else:
> -            outputurl = data['resolved']
> -        self._runwget(ud, d, "%s --directory-prefix=%s %s" %
> (self.basecmd, ud.prefixdir, outputurl), False) +
> +        pkgnameWithVersion = "{}@{}".format(pkg, version)
> +        logger.debug(2, "Get dependencies for
> {}".format(pkgnameWithVersion))
> +        filename = self._runpack(ud, d, pkgnameWithVersion)
>          manifest[pkg] = {}
> -        manifest[pkg]['tgz'] = os.path.basename(outputurl).rstrip()
> +        manifest[pkg]['tgz'] = filename
>          manifest[pkg]['deps'] = {}
>  
>          if pkg in lockdown:
> diff --git a/bitbake/lib/bb/fetch2/osc.py
> b/bitbake/lib/bb/fetch2/osc.py index 6c60456..3e56715 100644
> --- a/bitbake/lib/bb/fetch2/osc.py
> +++ b/bitbake/lib/bb/fetch2/osc.py
> @@ -1,5 +1,6 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
>  """
>  Bitbake "Fetch" implementation for osc (Opensuse build service
> client). Based on the svn "Fetch" implementation.
> diff --git a/bitbake/lib/bb/fetch2/perforce.py
> b/bitbake/lib/bb/fetch2/perforce.py index 903a8e6..54d001e 100644
> --- a/bitbake/lib/bb/fetch2/perforce.py
> +++ b/bitbake/lib/bb/fetch2/perforce.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Fetch' implementation for perforce
>  
> @@ -8,18 +6,7 @@ BitBake 'Fetch' implementation for perforce
>  # Copyright (C) 2003, 2004  Chris Larson
>  # Copyright (C) 2016 Kodak Alaris, Inc.
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # Based on functions from the base bb module, Copyright 2003 Holger
> Schurig 
> diff --git a/bitbake/lib/bb/fetch2/repo.py
> b/bitbake/lib/bb/fetch2/repo.py index 8c7e818..2bdbbd4 100644
> --- a/bitbake/lib/bb/fetch2/repo.py
> +++ b/bitbake/lib/bb/fetch2/repo.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake "Fetch" repo (git) implementation
>  
> @@ -8,20 +6,10 @@ BitBake "Fetch" repo (git) implementation
>  # Copyright (C) 2009 Tom Rini <trini@embeddedalley.com>
>  #
>  # Based on git.py which is:
> -#Copyright (C) 2005 Richard Purdie
> +# Copyright (C) 2005 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  import bb
> diff --git a/bitbake/lib/bb/fetch2/s3.py b/bitbake/lib/bb/fetch2/s3.py
> index 1629288..ffca73c 100644
> --- a/bitbake/lib/bb/fetch2/s3.py
> +++ b/bitbake/lib/bb/fetch2/s3.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Fetch' implementation for Amazon AWS S3.
>  
> @@ -13,18 +11,7 @@ The aws tool must be correctly installed and
> configured prior to use. # Based in part on bb.fetch2.wget:
>  #    Copyright (C) 2003, 2004  Chris Larson
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # Based on functions from the base bb module, Copyright 2003 Holger
> Schurig 
> diff --git a/bitbake/lib/bb/fetch2/sftp.py
> b/bitbake/lib/bb/fetch2/sftp.py index 81884a6..f87f292 100644
> --- a/bitbake/lib/bb/fetch2/sftp.py
> +++ b/bitbake/lib/bb/fetch2/sftp.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake SFTP Fetch implementation
>  
> @@ -44,18 +42,7 @@ SRC_URI =
> "sftp://user@host.example.com/dir/path.file.txt" # Based in part on
> bb.fetch2.wget: #    Copyright (C) 2003, 2004  Chris Larson
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # Based on functions from the base bb module, Copyright 2003 Holger
> Schurig 
> diff --git a/bitbake/lib/bb/fetch2/ssh.py
> b/bitbake/lib/bb/fetch2/ssh.py index 6047ee4..f5be060 100644
> --- a/bitbake/lib/bb/fetch2/ssh.py
> +++ b/bitbake/lib/bb/fetch2/ssh.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  '''
>  BitBake 'Fetch' implementations
>  
> @@ -29,18 +27,8 @@ IETF secsh internet draft:
>  #            Copyright 2003 Holger Schurig
>  #
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import re, os
>  from   bb.fetch2 import FetchMethod
> diff --git a/bitbake/lib/bb/fetch2/svn.py
> b/bitbake/lib/bb/fetch2/svn.py index ed70bcf..96d666b 100644
> --- a/bitbake/lib/bb/fetch2/svn.py
> +++ b/bitbake/lib/bb/fetch2/svn.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Fetch' implementation for svn.
>  
> @@ -8,18 +6,7 @@ BitBake 'Fetch' implementation for svn.
>  # Copyright (C) 2003, 2004  Chris Larson
>  # Copyright (C) 2004        Marcin Juszkiewicz
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # Based on functions from the base bb module, Copyright 2003 Holger
> Schurig 
> @@ -63,6 +50,9 @@ class Svn(FetchMethod):
>          relpath = self._strip_leading_slashes(ud.path)
>          ud.pkgdir = os.path.join(svndir, ud.host, relpath)
>          ud.moddir = os.path.join(ud.pkgdir, ud.module)
> +        # Protects the repository from concurrent updates, e.g. from
> two
> +        # recipes fetching different revisions at the same time
> +        ud.svnlock = os.path.join(ud.pkgdir, "svn.lock")
>  
>          ud.setup_revisions(d)
>  
> @@ -101,6 +91,13 @@ class Svn(FetchMethod):
>              svncmd = "%s log --limit 1 %s %s://%s/%s/" %
> (ud.basecmd, " ".join(options), proto, svnroot, ud.module) else:
>              suffix = ""
> +
> +            # externals may be either 'allowed' or 'nowarn', but not
> both.  Allowed
> +            # will not issue a warning, but will log to the debug
> buffer what has likely
> +            # been downloaded by SVN.
> +            if not ("externals" in ud.parm and ud.parm["externals"]
> == "allowed"):
> +                options.append("--ignore-externals")
> +
>              if ud.revision:
>                  options.append("-r %s" % ud.revision)
>                  suffix = "@%s" % (ud.revision)
> @@ -123,35 +120,52 @@ class Svn(FetchMethod):
>  
>          logger.debug(2, "Fetch: checking for module directory '" +
> ud.moddir + "'") 
> -        if os.access(os.path.join(ud.moddir, '.svn'), os.R_OK):
> -            svnupdatecmd = self._buildsvncommand(ud, d, "update")
> -            logger.info("Update " + ud.url)
> -            # We need to attempt to run svn upgrade first in case
> its an older working format
> -            try:
> -                runfetchcmd(ud.basecmd + " upgrade", d,
> workdir=ud.moddir)
> -            except FetchError:
> -                pass
> -            logger.debug(1, "Running %s", svnupdatecmd)
> -            bb.fetch2.check_network_access(d, svnupdatecmd, ud.url)
> -            runfetchcmd(svnupdatecmd, d, workdir=ud.moddir)
> -        else:
> -            svnfetchcmd = self._buildsvncommand(ud, d, "fetch")
> -            logger.info("Fetch " + ud.url)
> -            # check out sources there
> -            bb.utils.mkdirhier(ud.pkgdir)
> -            logger.debug(1, "Running %s", svnfetchcmd)
> -            bb.fetch2.check_network_access(d, svnfetchcmd, ud.url)
> -            runfetchcmd(svnfetchcmd, d, workdir=ud.pkgdir)
> -
> -        scmdata = ud.parm.get("scmdata", "")
> -        if scmdata == "keep":
> -            tar_flags = ""
> -        else:
> -            tar_flags = "--exclude='.svn'"
> +        lf = bb.utils.lockfile(ud.svnlock)
> +
> +        try:
> +            if os.access(os.path.join(ud.moddir, '.svn'), os.R_OK):
> +                svnupdatecmd = self._buildsvncommand(ud, d, "update")
> +                logger.info("Update " + ud.url)
> +                # We need to attempt to run svn upgrade first in
> case its an older working format
> +                try:
> +                    runfetchcmd(ud.basecmd + " upgrade", d,
> workdir=ud.moddir)
> +                except FetchError:
> +                    pass
> +                logger.debug(1, "Running %s", svnupdatecmd)
> +                bb.fetch2.check_network_access(d, svnupdatecmd,
> ud.url)
> +                runfetchcmd(svnupdatecmd, d, workdir=ud.moddir)
> +            else:
> +                svnfetchcmd = self._buildsvncommand(ud, d, "fetch")
> +                logger.info("Fetch " + ud.url)
> +                # check out sources there
> +                bb.utils.mkdirhier(ud.pkgdir)
> +                logger.debug(1, "Running %s", svnfetchcmd)
> +                bb.fetch2.check_network_access(d, svnfetchcmd,
> ud.url)
> +                runfetchcmd(svnfetchcmd, d, workdir=ud.pkgdir)
> +
> +            if not ("externals" in ud.parm and ud.parm["externals"]
> == "nowarn"):
> +                # Warn the user if this had externals (won't catch
> them all)
> +                output = runfetchcmd("svn propget svn:externals ||
> true", d, workdir=ud.moddir)
> +                if output:
> +                    if "--ignore-externals" in svnfetchcmd.split():
> +                        bb.warn("%s contains svn:externals." %
> ud.url)
> +                        bb.warn("These should be added to the recipe
> SRC_URI as necessary.")
> +                        bb.warn("svn fetch has ignored
> externals:\n%s" % output)
> +                        bb.warn("To disable this warning add
> ';externals=nowarn' to the url.")
> +                    else:
> +                        bb.debug(1, "svn repository has
> externals:\n%s" % output) +
> +            scmdata = ud.parm.get("scmdata", "")
> +            if scmdata == "keep":
> +                tar_flags = ""
> +            else:
> +                tar_flags = "--exclude='.svn'"
>  
> -        # tar them up to a defined filename
> -        runfetchcmd("tar %s -czf %s %s" % (tar_flags, ud.localpath,
> ud.path_spec), d,
> -                    cleanup=[ud.localpath], workdir=ud.pkgdir)
> +            # tar them up to a defined filename
> +            runfetchcmd("tar %s -czf %s %s" % (tar_flags,
> ud.localpath, ud.path_spec), d,
> +                        cleanup=[ud.localpath], workdir=ud.pkgdir)
> +        finally:
> +            bb.utils.unlockfile(lf)
>  
>      def clean(self, ud, d):
>          """ Clean SVN specific files and dirs """
> diff --git a/bitbake/lib/bb/fetch2/wget.py
> b/bitbake/lib/bb/fetch2/wget.py index 8f505b6..725586d 100644
> --- a/bitbake/lib/bb/fetch2/wget.py
> +++ b/bitbake/lib/bb/fetch2/wget.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'Fetch' implementations
>  
> @@ -10,18 +8,7 @@ BitBake build tools.
>  
>  # Copyright (C) 2003, 2004  Chris Larson
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # Based on functions from the base bb module, Copyright 2003 Holger
> Schurig 
> @@ -33,11 +20,14 @@ import logging
>  import errno
>  import bb
>  import bb.progress
> +import socket
> +import http.client
>  import urllib.request, urllib.parse, urllib.error
>  from   bb.fetch2 import FetchMethod
>  from   bb.fetch2 import FetchError
>  from   bb.fetch2 import logger
>  from   bb.fetch2 import runfetchcmd
> +from   bb.fetch2 import FetchConnectionCache
>  from   bb.utils import export_proxies
>  from   bs4 import BeautifulSoup
>  from   bs4 import SoupStrainer
> @@ -132,10 +122,6 @@ class Wget(FetchMethod):
>          return True
>  
>      def checkstatus(self, fetch, ud, d, try_again=True):
> -        import urllib.request, urllib.error, urllib.parse, socket,
> http.client
> -        from urllib.response import addinfourl
> -        from bb.fetch2 import FetchConnectionCache
> -
>          class HTTPConnectionCache(http.client.HTTPConnection):
>              if fetch.connection_cache:
>                  def connect(self):
> @@ -168,7 +154,7 @@ class Wget(FetchMethod):
>                  """
>                  host = req.host
>                  if not host:
> -                    raise urlllib2.URLError('no host given')
> +                    raise urllib.error.URLError('no host given')
>  
>                  h = http_class(host, timeout=req.timeout) # will
> parse host:port h.set_debuglevel(self._debuglevel)
> @@ -185,7 +171,7 @@ class Wget(FetchMethod):
>                  # request.
>  
>                  # Don't close connection when connection_cache is
> enabled,
> -                if fetch.connection_cache is None: 
> +                if fetch.connection_cache is None:
>                      headers["Connection"] = "close"
>                  else:
>                      headers["Connection"] = "Keep-Alive" # Works for
> HTTP/1.0 @@ -252,7 +238,7 @@ class Wget(FetchMethod):
>                          pass
>                      closed = False
>  
> -                resp = addinfourl(fp_dummy(), r.msg,
> req.get_full_url())
> +                resp = urllib.response.addinfourl(fp_dummy(), r.msg,
> req.get_full_url()) resp.code = r.status
>                  resp.msg = r.reason
>  
> @@ -271,17 +257,18 @@ class Wget(FetchMethod):
>                  fp.read()
>                  fp.close()
>  
> -                newheaders = dict((k,v) for k,v in
> list(req.headers.items())
> -                                  if k.lower() not in
> ("content-length", "content-type"))
> -                return
> self.parent.open(urllib.request.Request(req.get_full_url(),
> -
> headers=newheaders,
> -
> origin_req_host=req.origin_req_host,
> -
> unverifiable=True))
> +                if req.get_method() != 'GET':
> +                    newheaders = dict((k, v) for k, v in
> list(req.headers.items())
> +                                      if k.lower() not in
> ("content-length", "content-type"))
> +                    return
> self.parent.open(urllib.request.Request(req.get_full_url(),
> +
> headers=newheaders,
> +
> origin_req_host=req.origin_req_host,
> +
> unverifiable=True)) 
> -            """
> -            Some servers (e.g. GitHub archives, hosted on Amazon S3)
> return 403
> -            Forbidden when they actually mean 405 Method Not Allowed.
> -            """
> +                raise urllib.request.HTTPError(req, code, msg,
> headers, None) +
> +            # Some servers (e.g. GitHub archives, hosted on Amazon
> S3) return 403
> +            # Forbidden when they actually mean 405 Method Not
> Allowed. http_error_403 = http_error_405
>  
>  
> @@ -292,15 +279,15 @@ class Wget(FetchMethod):
>              """
>              def redirect_request(self, req, fp, code, msg, headers,
> newurl): newreq =
> urllib.request.HTTPRedirectHandler.redirect_request(self, req, fp,
> code, msg, headers, newurl)
> -                newreq.get_method = lambda: req.get_method()
> +                newreq.get_method = req.get_method
>                  return newreq
>          exported_proxies = export_proxies(d)
>  
>          handlers = [FixedHTTPRedirectHandler, HTTPMethodFallback]
> -        if export_proxies:
> +        if exported_proxies:
>              handlers.append(urllib.request.ProxyHandler())
>          handlers.append(CacheHTTPHandler())
> -        # XXX: Since Python 2.7.9 ssl cert validation is enabled by
> default
> +        # Since Python 2.7.9 ssl cert validation is enabled by
> default # see PEP-0476, this causes verification errors on some https
> servers # so disable by default.
>          import ssl
> @@ -319,19 +306,19 @@ class Wget(FetchMethod):
>                  '''Adds Basic auth to http request, pass in
> login:password as string''' import base64
>                  encodeuser =
> base64.b64encode(login_str.encode('utf-8')).decode("utf-8")
> -                authheader =  "Basic %s" % encodeuser
> +                authheader = "Basic %s" % encodeuser
>                  r.add_header("Authorization", authheader)
>  
> -            if ud.user:
> -                add_basic_auth(ud.user, r)
> +            if ud.user and ud.pswd:
> +                add_basic_auth(ud.user + ':' + ud.pswd, r)
>  
>              try:
> -                import netrc, urllib.parse
> +                import netrc
>                  n = netrc.netrc()
>                  login, unused, password =
> n.authenticators(urllib.parse.urlparse(uri).hostname)
> add_basic_auth("%s:%s" % (login, password), r) except (TypeError,
> ImportError, IOError, netrc.NetrcParseError):
> -                 pass
> +                pass
>  
>              with opener.open(r) as response:
>                  pass
> @@ -396,18 +383,14 @@ class Wget(FetchMethod):
>          (oldpn, oldpv, oldsuffix) = old
>          (newpn, newpv, newsuffix) = new
>  
> -        """
> -        Check for a new suffix type that we have never heard of
> before
> -        """
> -        if (newsuffix):
> +        # Check for a new suffix type that we have never heard of
> before
> +        if newsuffix:
>              m = self.suffix_regex_comp.search(newsuffix)
>              if not m:
>                  bb.warn("%s has a possible unknown suffix: %s" %
> (newpn, newsuffix)) return False
>  
> -        """
> -        Not our package so ignore it
> -        """
> +        # Not our package so ignore it
>          if oldpn != newpn:
>              return False
>  
> @@ -473,15 +456,14 @@ class Wget(FetchMethod):
>  
>          return ""
>  
> -    def _check_latest_version_by_dir(self, dirver, package,
> package_regex,
> -            current_version, ud, d):
> +    def _check_latest_version_by_dir(self, dirver, package,
> package_regex, current_version, ud, d): """
> -            Scan every directory in order to get upstream version.
> +        Scan every directory in order to get upstream version.
>          """
>          version_dir = ['', '', '']
>          version = ['', '', '']
>  
> -        dirver_regex =
> re.compile("(?P<pfx>\D*)(?P<ver>(\d+[\.\-_])+(\d+))")
> +        dirver_regex =
> re.compile(r"(?P<pfx>\D*)(?P<ver>(\d+[\.\-_])+(\d+))") s =
> dirver_regex.search(dirver) if s:
>              version_dir[1] = s.group('ver')
> @@ -541,26 +523,26 @@ class Wget(FetchMethod):
>                  gst-fluendo-mp3
>          """
>          # match most patterns which uses "-" as separator to version
> digits
> -        pn_prefix1 = "[a-zA-Z][a-zA-Z0-9]*([-_][a-zA-Z]\w+)*\+?[-_]"
> +        pn_prefix1 = r"[a-zA-Z][a-zA-Z0-9]*([-_][a-zA-Z]\w+)*\+?[-_]"
>          # a loose pattern such as for unzip552.tar.gz
> -        pn_prefix2 = "[a-zA-Z]+"
> +        pn_prefix2 = r"[a-zA-Z]+"
>          # a loose pattern such as for 80325-quicky-0.4.tar.gz
> -        pn_prefix3 = "[0-9]+[-]?[a-zA-Z]+"
> +        pn_prefix3 = r"[0-9]+[-]?[a-zA-Z]+"
>          # Save the Package Name (pn) Regex for use later
> -        pn_regex = "(%s|%s|%s)" % (pn_prefix1, pn_prefix2,
> pn_prefix3)
> +        pn_regex = r"(%s|%s|%s)" % (pn_prefix1, pn_prefix2,
> pn_prefix3) 
>          # match version
> -        pver_regex = "(([A-Z]*\d+[a-zA-Z]*[\.\-_]*)+)"
> +        pver_regex = r"(([A-Z]*\d+[a-zA-Z]*[\.\-_]*)+)"
>  
>          # match arch
>          parch_regex = "-source|_all_"
>  
>          # src.rpm extension was added only for rpm package. Can be
> removed if the rpm # packaged will always be considered as having to
> be manually upgraded
> -        psuffix_regex =
> "(tar\.gz|tgz|tar\.bz2|zip|xz|tar\.lz|rpm|bz2|orig\.tar\.gz|tar\.xz|src\.tar\.gz|src\.tgz|svnr\d+\.tar\.bz2|stable\.tar\.gz|src\.rpm)"
> +        psuffix_regex =
> r"(tar\.gz|tgz|tar\.bz2|zip|xz|tar\.lz|rpm|bz2|orig\.tar\.gz|tar\.xz|src\.tar\.gz|src\.tgz|svnr\d+\.tar\.bz2|stable\.tar\.gz|src\.rpm)" 
>          # match name, version and archive type of a package
> -        package_regex_comp =
> re.compile("(?P<name>%s?\.?v?)(?P<pver>%s)(?P<arch>%s)?[\.-](?P<type>%s$)"
> +        package_regex_comp =
> re.compile(r"(?P<name>%s?\.?v?)(?P<pver>%s)(?P<arch>%s)?[\.-](?P<type>%s$)"
> % (pn_regex, pver_regex, parch_regex, psuffix_regex))
> self.suffix_regex_comp = re.compile(psuffix_regex) 
> @@ -572,7 +554,7 @@ class Wget(FetchMethod):
>              version = self._parse_path(package_regex_comp, package)
>              if version:
>                  package_custom_regex_comp = re.compile(
> -
> "(?P<name>%s)(?P<pver>%s)(?P<arch>%s)?[\.-](?P<type>%s)" %
> +
> r"(?P<name>%s)(?P<pver>%s)(?P<arch>%s)?[\.-](?P<type>%s)" %
> (re.escape(version[0]), pver_regex, parch_regex, psuffix_regex)) else:
>                  package_custom_regex_comp = None
> @@ -589,7 +571,7 @@ class Wget(FetchMethod):
>          current_version = ['', d.getVar('PV'), '']
>  
>          """possible to have no version in pkg name, such as
> spectrum-fw"""
> -        if not re.search("\d+", package):
> +        if not re.search(r"\d+", package):
>              current_version[1] = re.sub('_', '.', current_version[1])
>              current_version[1] = re.sub('-', '.', current_version[1])
>              return (current_version[1], '')
> @@ -607,13 +589,13 @@ class Wget(FetchMethod):
>  
>              # search for version matches on folders inside the path,
> like: # "5.7" in
> http://download.gnome.org/sources/${PN}/5.7/${PN}-${PV}.tar.gz
> -            dirver_regex =
> re.compile("(?P<dirver>[^/]*(\d+\.)*\d+([-_]r\d+)*)/")
> +            dirver_regex =
> re.compile(r"(?P<dirver>[^/]*(\d+\.)*\d+([-_]r\d+)*)/") m =
> dirver_regex.search(path) if m:
>                  pn = d.getVar('PN')
>                  dirver = m.group('dirver')
>  
> -                dirver_pn_regex = re.compile("%s\d?" %
> (re.escape(pn)))
> +                dirver_pn_regex = re.compile(r"%s\d?" %
> (re.escape(pn))) if not dirver_pn_regex.search(dirver):
>                      return (self._check_latest_version_by_dir(dirver,
>                          package, package_regex, current_version, ud,
> d), '') diff --git a/bitbake/lib/bb/main.py b/bitbake/lib/bb/main.py
> index 732a315..af2880f 100755
> --- a/bitbake/lib/bb/main.py
> +++ b/bitbake/lib/bb/main.py
> @@ -1,6 +1,3 @@
> -#!/usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # Copyright (C) 2003, 2004  Chris Larson
>  # Copyright (C) 2003, 2004  Phil Blundell
> @@ -9,18 +6,8 @@
>  # Copyright (C) 2005        ROAD GmbH
>  # Copyright (C) 2006        Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  import sys
> @@ -267,6 +254,11 @@ class
> BitBakeConfigParameters(cookerdata.ConfigParameters): help="Do not
> run any setscene tasks. sstate will be ignored and " "everything
> needed, built.") 
> +        parser.add_option("", "--skip-setscene", action="store_true",
> +                          dest="skipsetscene", default=False,
> +                          help="Skip setscene tasks if they would be
> executed. Tasks previously "
> +                               "restored from sstate will be kept,
> unlike --no-setscene") +
>          parser.add_option("", "--setscene-only", action="store_true",
>                            dest="setsceneonly", default=False,
>                            help="Only run setscene tasks, don't run
> any real tasks.") @@ -448,7 +440,7 @@ def setup_bitbake(configParams,
> configuration, extrafeatures=None): else:
>                      logger.info("Reconnecting to bitbake server...")
>                      if not os.path.exists(sockname):
> -                        print("Previous bitbake instance shutting
> down?, waiting to retry...")
> +                        logger.info("Previous bitbake instance
> shutting down?, waiting to retry...") i = 0
>                          lock = None
>                          # Wait for 5s or until we can get the lock
> @@ -460,12 +452,7 @@ def setup_bitbake(configParams, configuration,
> extrafeatures=None): bb.utils.unlockfile(lock)
>                          raise
> bb.server.process.ProcessTimeout("Bitbake still shutting down as
> socket exists but no lock?") if not configParams.server_only:
> -                    try:
> -                        server_connection =
> bb.server.process.connectProcessServer(sockname, featureset)
> -                    except EOFError:
> -                        # The server may have been shutting down but
> not closed the socket yet. If that happened,
> -                        # ignore it.
> -                        pass
> +                    server_connection =
> bb.server.process.connectProcessServer(sockname, featureset) 
>                  if server_connection or configParams.server_only:
>                      break
> @@ -475,12 +462,14 @@ def setup_bitbake(configParams, configuration,
> extrafeatures=None): if not retries:
>                      raise
>                  retries -= 1
> -                if isinstance(e, (bb.server.process.ProcessTimeout,
> BrokenPipeError)):
> -                    logger.info("Retrying server connection...")
> +                tryno = 8 - retries
> +                if isinstance(e, (bb.server.process.ProcessTimeout,
> BrokenPipeError, EOFError)):
> +                    logger.info("Retrying server connection
> (#%d)..." % tryno) else:
> -                    logger.info("Retrying server connection... (%s)"
> % traceback.format_exc())
> +                    logger.info("Retrying server connection (#%d)...
> (%s)" % (tryno, traceback.format_exc())) if not retries:
> -                bb.fatal("Unable to connect to bitbake server, or
> start one")
> +                bb.fatal("Unable to connect to bitbake server, or
> start one (server startup failures would be in
> bitbake-cookerdaemon.log).")
> +            bb.event.print_ui_queue()
>              if retries < 5:
>                  time.sleep(5)
>  
> @@ -502,7 +491,7 @@ def setup_bitbake(configParams, configuration,
> extrafeatures=None): def lockBitbake():
>      topdir = bb.cookerdata.findTopdir()
>      if not topdir:
> -        bb.error("Unable to find conf/bblayers.conf or
> conf/bitbake.conf. BBAPTH is unset and/or not in a build directory?")
> +        bb.error("Unable to find conf/bblayers.conf or
> conf/bitbake.conf. BBPATH is unset and/or not in a build directory?")
> raise BBMainFatal lockfile = topdir + "/bitbake.lock"
>      return topdir, bb.utils.lockfile(lockfile, False, False)
> diff --git a/bitbake/lib/bb/methodpool.py
> b/bitbake/lib/bb/methodpool.py index 49aed33..51783ac 100644
> --- a/bitbake/lib/bb/methodpool.py
> +++ b/bitbake/lib/bb/methodpool.py
> @@ -1,21 +1,8 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  #
>  # Copyright (C)       2006 Holger Hans Peter Freyther
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from bb.utils import better_compile, better_exec
>  
> diff --git a/bitbake/lib/bb/monitordisk.py
> b/bitbake/lib/bb/monitordisk.py index 833cd3d..1a25b00 100644
> --- a/bitbake/lib/bb/monitordisk.py
> +++ b/bitbake/lib/bb/monitordisk.py
> @@ -1,21 +1,8 @@
> -#!/usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # Copyright (C) 2012 Robert Yang
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os, logging, re, sys
>  import bb
> @@ -28,16 +15,16 @@ def convertGMK(unit):
>  
>      """ Convert the space unit G, M, K, the unit is case-insensitive
> """ 
> -    unitG = re.match('([1-9][0-9]*)[gG]\s?$', unit)
> +    unitG = re.match(r'([1-9][0-9]*)[gG]\s?$', unit)
>      if unitG:
>          return int(unitG.group(1)) * (1024 ** 3)
> -    unitM = re.match('([1-9][0-9]*)[mM]\s?$', unit)
> +    unitM = re.match(r'([1-9][0-9]*)[mM]\s?$', unit)
>      if unitM:
>          return int(unitM.group(1)) * (1024 ** 2)
> -    unitK = re.match('([1-9][0-9]*)[kK]\s?$', unit)
> +    unitK = re.match(r'([1-9][0-9]*)[kK]\s?$', unit)
>      if unitK:
>          return int(unitK.group(1)) * 1024
> -    unitN = re.match('([1-9][0-9]*)\s?$', unit)
> +    unitN = re.match(r'([1-9][0-9]*)\s?$', unit)
>      if unitN:
>          return int(unitN.group(1))
>      else:
> @@ -83,7 +70,7 @@ def getDiskData(BBDirs, configuration):
>      for pathSpaceInode in BBDirs.split():
>          # The input format is: "dir,space,inode", dir is a must,
> space # and inode are optional
> -        pathSpaceInodeRe = re.match('([^,]*),([^,]*),([^,]*),?(.*)',
> pathSpaceInode)
> +        pathSpaceInodeRe =
> re.match(r'([^,]*),([^,]*),([^,]*),?(.*)', pathSpaceInode) if not
> pathSpaceInodeRe: printErr("Invalid value in BB_DISKMON_DIRS: %s" %
> pathSpaceInode) return None
> @@ -147,7 +134,7 @@ def getInterval(configuration):
>      else:
>          # The disk space or inode interval is optional, but it should
>          # have a correct value once it is specified
> -        intervalRe = re.match('([^,]*),?\s*(.*)', interval)
> +        intervalRe = re.match(r'([^,]*),?\s*(.*)', interval)
>          if intervalRe:
>              intervalSpace = intervalRe.group(1)
>              if intervalSpace:
> diff --git a/bitbake/lib/bb/msg.py b/bitbake/lib/bb/msg.py
> index 96f077e..6216eb3 100644
> --- a/bitbake/lib/bb/msg.py
> +++ b/bitbake/lib/bb/msg.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'msg' implementation
>  
> @@ -9,18 +7,8 @@ Message handling infrastructure for bitbake
>  
>  # Copyright (C) 2006        Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import sys
>  import copy
> diff --git a/bitbake/lib/bb/namedtuple_with_abc.py
> b/bitbake/lib/bb/namedtuple_with_abc.py index 32f2fc6..646aed6 100644
> --- a/bitbake/lib/bb/namedtuple_with_abc.py
> +++ b/bitbake/lib/bb/namedtuple_with_abc.py
> @@ -1,6 +1,8 @@
>  #
> http://code.activestate.com/recipes/577629-namedtupleabc-abstract-base-class-mix-in-for-named/
> -#!/usr/bin/env python # Copyright (c) 2011 Jan Kaliszewski (zuo).
> Available under the MIT License. +#
> +# SPDX-License-Identifier: MIT
> +#
>  
>  """
>  namedtuple_with_abc.py:
> diff --git a/bitbake/lib/bb/parse/__init__.py
> b/bitbake/lib/bb/parse/__init__.py index 5397d57..76e180b 100644
> --- a/bitbake/lib/bb/parse/__init__.py
> +++ b/bitbake/lib/bb/parse/__init__.py
> @@ -9,20 +9,10 @@ File parsers for the BitBake build tools.
>  # Copyright (C) 2003, 2004  Chris Larson
>  # Copyright (C) 2003, 2004  Phil Blundell
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # Based on functions from the base bb module, Copyright 2003 Holger
> Schurig +#
>  
>  handlers = []
>  
> diff --git a/bitbake/lib/bb/parse/ast.py b/bitbake/lib/bb/parse/ast.py
> index 6d7c80b..f0911e6 100644
> --- a/bitbake/lib/bb/parse/ast.py
> +++ b/bitbake/lib/bb/parse/ast.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>   AbstractSyntaxTree classes for the Bitbake language
>  """
> @@ -8,19 +6,8 @@
>  # Copyright (C) 2003, 2004 Phil Blundell
>  # Copyright (C) 2009 Holger Hans Peter Freyther
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -
>  
>  import re
>  import string
> diff --git a/bitbake/lib/bb/parse/parse_py/BBHandler.py
> b/bitbake/lib/bb/parse/parse_py/BBHandler.py index 01fc47e..6f7cf82
> 100644 --- a/bitbake/lib/bb/parse/parse_py/BBHandler.py
> +++ b/bitbake/lib/bb/parse/parse_py/BBHandler.py
> @@ -1,6 +1,3 @@
> -#!/usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>     class for handling .bb files
>  
> @@ -12,19 +9,8 @@
>  #  Copyright (C) 2003, 2004  Chris Larson
>  #  Copyright (C) 2003, 2004  Phil Blundell
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -
>  
>  import re, bb, os
>  import logging
> @@ -38,14 +24,15 @@ from .ConfHandler import include, init
>  # For compatibility
>  bb.deprecate_import(__name__, "bb.parse", ["vars_from_file"])
>  
> -__func_start_regexp__    =
> re.compile( r"(((?P<py>python)|(?P<fr>fakeroot))\s*)*(?P<func>[\w\.\-\+\{\}\$]+)?\s*\(\s*\)\s*{$" )
> -__inherit_regexp__       = re.compile( r"inherit\s+(.+)" )
> -__export_func_regexp__   = re.compile( r"EXPORT_FUNCTIONS\s+(.+)" )
> -__addtask_regexp__       =
> re.compile("addtask\s+(?P<func>\w+)\s*((before\s*(?P<before>((.*(?=after))|(.*))))|(after\s*(?P<after>((.*(?=before))|(.*)))))*")
> -__deltask_regexp__       = re.compile("deltask\s+(?P<func>\w+)")
> -__addhandler_regexp__    = re.compile( r"addhandler\s+(.+)" )
> -__def_regexp__           = re.compile( r"def\s+(\w+).*:" )
> -__python_func_regexp__   = re.compile( r"(\s+.*)|(^$)|(^#)" )
> +__func_start_regexp__    =
> re.compile(r"(((?P<py>python)|(?P<fr>fakeroot))\s*)*(?P<func>[\w\.\-\+\{\}\$]+)?\s*\(\s*\)\s*{$" )
> +__inherit_regexp__       = re.compile(r"inherit\s+(.+)" )
> +__export_func_regexp__   = re.compile(r"EXPORT_FUNCTIONS\s+(.+)" )
> +__addtask_regexp__       =
> re.compile(r"addtask\s+(?P<func>\w+)\s*((before\s*(?P<before>((.*(?=after))|(.*))))|(after\s*(?P<after>((.*(?=before))|(.*)))))*")
> +__deltask_regexp__       =
> re.compile(r"deltask\s+(?P<func>\w+)(?P<ignores>.*)")
> +__addhandler_regexp__    = re.compile(r"addhandler\s+(.+)" )
> +__def_regexp__           = re.compile(r"def\s+(\w+).*:" )
> +__python_func_regexp__   = re.compile(r"(\s+.*)|(^$)|(^#)" )
> +__python_tab_regexp__    = re.compile(r" *\t") __infunc__ = []
> __inpython__ = False @@ -160,6 +147,16 @@ def handle(fn, d, include):
> def feeder(lineno, s, fn, root, statements, eof=False): global
> __func_start_regexp__, __inherit_regexp__, __export_func_regexp__,
> __addtask_regexp__, __addhandler_regexp__, __def_regexp__,
> __python_func_regexp__, __inpython__, __infunc__, __body__, bb,
> __residue__, __classname__ +
> +    # Check tabs in python functions:
> +    # - def py_funcname(): covered by __inpython__
> +    # - python(): covered by '__anonymous' == __infunc__[0]
> +    # - python funcname(): covered by __infunc__[3]
> +    if __inpython__ or (__infunc__ and ('__anonymous' ==
> __infunc__[0] or __infunc__[3])):
> +        tab = __python_tab_regexp__.match(s)
> +        if tab:
> +            bb.warn('python should use 4 spaces indentation, but
> found tabs in %s, line %s' % (root, lineno)) +
>      if __infunc__:
>          if s == '}':
>              __body__.append('')
> @@ -225,11 +222,27 @@ def feeder(lineno, s, fn, root, statements,
> eof=False): 
>      m = __addtask_regexp__.match(s)
>      if m:
> +        if len(m.group().split()) == 2:
> +            # Check and warn for "addtask task1 task2"
> +            m2 = re.match(r"addtask\s+(?P<func>\w+)(?P<ignores>.*)",
> s)
> +            if m2 and m2.group('ignores'):
> +                logger.warning('addtask ignored: "%s"' %
> m2.group('ignores')) +
> +        # Check and warn for "addtask task1 before task2 before
> task3", the
> +        # similar to "after"
> +        taskexpression = s.split()
> +        for word in ('before', 'after'):
> +            if taskexpression.count(word) > 1:
> +                logger.warning("addtask contained multiple '%s'
> keywords, only one is supported" % word) +
>          ast.handleAddTask(statements, fn, lineno, m)
>          return
>  
>      m = __deltask_regexp__.match(s)
>      if m:
> +        # Check and warn "for deltask task1 task2"
> +        if m.group('ignores'):
> +            logger.warning('deltask ignored: "%s"' %
> m.group('ignores')) ast.handleDelTask(statements, fn, lineno, m)
>          return
>  
> diff --git a/bitbake/lib/bb/parse/parse_py/ConfHandler.py
> b/bitbake/lib/bb/parse/parse_py/ConfHandler.py index 9d3ebe1..2e84b91
> 100644 --- a/bitbake/lib/bb/parse/parse_py/ConfHandler.py
> +++ b/bitbake/lib/bb/parse/parse_py/ConfHandler.py
> @@ -1,6 +1,3 @@
> -#!/usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>     class for handling configuration data files
>  
> @@ -11,18 +8,8 @@
>  # Copyright (C) 2003, 2004  Chris Larson
>  # Copyright (C) 2003, 2004  Phil Blundell
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import errno
>  import re
> @@ -147,7 +134,7 @@ def handle(fn, data, include):
>              continue
>          s = s.rstrip()
>          while s[-1] == '\\':
> -            s2 = f.readline().strip()
> +            s2 = f.readline().rstrip()
>              lineno = lineno + 1
>              if (not s2 or s2 and s2[0] != "#") and s[0] == "#" :
>                  bb.fatal("There is a confusing multiline, partially
> commented expression on line %s of file %s (%s).\nPlease clarify
> whether this is all a comment or should be parsed." % (lineno, fn,
> s)) diff --git a/bitbake/lib/bb/parse/parse_py/__init__.py
> b/bitbake/lib/bb/parse/parse_py/__init__.py index 3e658d0..f508afa
> 100644 --- a/bitbake/lib/bb/parse/parse_py/__init__.py +++
> b/bitbake/lib/bb/parse/parse_py/__init__.py @@ -1,6 +1,3 @@
> -#!/usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake Parsers
>  
> @@ -11,20 +8,10 @@ File parsers for the BitBake build tools.
>  # Copyright (C) 2003, 2004  Chris Larson
>  # Copyright (C) 2003, 2004  Phil Blundell
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # Based on functions from the base bb module, Copyright 2003 Holger
> Schurig +#
>  
>  from __future__ import absolute_import
>  from . import ConfHandler
> diff --git a/bitbake/lib/bb/persist_data.py
> b/bitbake/lib/bb/persist_data.py index bef7018..de8f87a 100644
> --- a/bitbake/lib/bb/persist_data.py
> +++ b/bitbake/lib/bb/persist_data.py
> @@ -8,18 +8,8 @@ currently, providing a key/value store accessed by
> 'domain'. # Copyright (C) 2007        Richard Purdie
>  # Copyright (C) 2010        Chris Larson <chris_larson@mentor.com>
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import collections
>  import logging
> @@ -29,6 +19,7 @@ import warnings
>  from bb.compat import total_ordering
>  from collections import Mapping
>  import sqlite3
> +import contextlib
>  
>  sqlversion = sqlite3.sqlite_version_info
>  if sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3):
> @@ -36,84 +27,181 @@ if sqlversion[0] < 3 or (sqlversion[0] == 3 and
> sqlversion[1] < 3): 
>  
>  logger = logging.getLogger("BitBake.PersistData")
> -if hasattr(sqlite3, 'enable_shared_cache'):
> -    try:
> -        sqlite3.enable_shared_cache(True)
> -    except sqlite3.OperationalError:
> -        pass
> -
>  
>  @total_ordering
>  class SQLTable(collections.MutableMapping):
> +    class _Decorators(object):
> +        @staticmethod
> +        def retry(*, reconnect=True):
> +            """
> +            Decorator that restarts a function if a database locked
> sqlite
> +            exception occurs. If reconnect is True, the database
> connection
> +            will be closed and reopened each time a failure occurs
> +            """
> +            def retry_wrapper(f):
> +                def wrap_func(self, *args, **kwargs):
> +                    # Reconnect if necessary
> +                    if self.connection is None and reconnect:
> +                        self.reconnect()
> +
> +                    count = 0
> +                    while True:
> +                        try:
> +                            return f(self, *args, **kwargs)
> +                        except sqlite3.OperationalError as exc:
> +                            if count < 500 and ('is locked' in
> str(exc) or 'locking protocol' in str(exc)):
> +                                count = count + 1
> +                                if reconnect:
> +                                    self.reconnect()
> +                                continue
> +                            raise
> +                return wrap_func
> +            return retry_wrapper
> +
> +        @staticmethod
> +        def transaction(f):
> +            """
> +            Decorator that starts a database transaction and creates
> a database
> +            cursor for performing queries. If no exception is
> thrown, the
> +            database results are commited. If an exception occurs,
> the database
> +            is rolled back. In all cases, the cursor is closed after
> the
> +            function ends.
> +
> +            Note that the cursor is passed as an extra argument to
> the function
> +            after `self` and before any of the normal arguments
> +            """
> +            def wrap_func(self, *args, **kwargs):
> +                # Context manager will COMMIT the database on
> success,
> +                # or ROLLBACK on an exception
> +                with self.connection:
> +                    # Automatically close the cursor when done
> +                    with
> contextlib.closing(self.connection.cursor()) as cursor:
> +                        return f(self, cursor, *args, **kwargs)
> +            return wrap_func
> +
>      """Object representing a table/domain in the database"""
>      def __init__(self, cachefile, table):
>          self.cachefile = cachefile
>          self.table = table
> -        self.cursor = connect(self.cachefile)
> -
> -        self._execute("CREATE TABLE IF NOT EXISTS %s(key TEXT, value
> TEXT);"
> -                      % table)
> -
> -    def _execute(self, *query):
> -        """Execute a query, waiting to acquire a lock if necessary"""
> -        count = 0
> -        while True:
> -            try:
> -                return self.cursor.execute(*query)
> -            except sqlite3.OperationalError as exc:
> -                if 'database is locked' in str(exc) and count < 500:
> -                    count = count + 1
> +
> +        self.connection = None
> +        self._execute_single("CREATE TABLE IF NOT EXISTS %s(key TEXT
> PRIMARY KEY NOT NULL, value TEXT);" % table) +
> +    @_Decorators.retry(reconnect=False)
> +    @_Decorators.transaction
> +    def _setup_database(self, cursor):
> +        cursor.execute("pragma synchronous = off;")
> +        # Enable WAL and keep the autocheckpoint length small (the
> default is
> +        # usually 1000). Persistent caches are usually read-mostly,
> so keeping
> +        # this short will keep readers running quickly
> +        cursor.execute("pragma journal_mode = WAL;")
> +        cursor.execute("pragma wal_autocheckpoint = 100;")
> +
> +    def reconnect(self):
> +        if self.connection is not None:
> +            self.connection.close()
> +        self.connection = sqlite3.connect(self.cachefile, timeout=5)
> +        self.connection.text_factory = str
> +        self._setup_database()
> +
> +    @_Decorators.retry()
> +    @_Decorators.transaction
> +    def _execute_single(self, cursor, *query):
> +        """
> +        Executes a single query and discards the results. This
> correctly closes
> +        the database cursor when finished
> +        """
> +        cursor.execute(*query)
> +
> +    @_Decorators.retry()
> +    def _row_iter(self, f, *query):
> +        """
> +        Helper function that returns a row iterator. Each time
> __next__ is
> +        called on the iterator, the provided function is evaluated
> to determine
> +        the return value
> +        """
> +        class CursorIter(object):
> +            def __init__(self, cursor):
> +                self.cursor = cursor
> +
> +            def __iter__(self):
> +                return self
> +
> +            def __next__(self):
> +                row = self.cursor.fetchone()
> +                if row is None:
>                      self.cursor.close()
> -                    self.cursor = connect(self.cachefile)
> -                    continue
> -                raise
> +                    raise StopIteration
> +                return f(row)
> +
> +            def __enter__(self):
> +                return self
> +
> +            def __exit__(self, typ, value, traceback):
> +                self.cursor.close()
> +                return False
> +
> +        cursor = self.connection.cursor()
> +        try:
> +            cursor.execute(*query)
> +            return CursorIter(cursor)
> +        except:
> +            cursor.close()
>  
>      def __enter__(self):
> -        self.cursor.__enter__()
> +        self.connection.__enter__()
>          return self
>  
>      def __exit__(self, *excinfo):
> -        self.cursor.__exit__(*excinfo)
> -
> -    def __getitem__(self, key):
> -        data = self._execute("SELECT * from %s where key=?;" %
> -                             self.table, [key])
> -        for row in data:
> +        self.connection.__exit__(*excinfo)
> +
> +    @_Decorators.retry()
> +    @_Decorators.transaction
> +    def __getitem__(self, cursor, key):
> +        cursor.execute("SELECT * from %s where key=?;" % self.table,
> [key])
> +        row = cursor.fetchone()
> +        if row is not None:
>              return row[1]
>          raise KeyError(key)
>  
> -    def __delitem__(self, key):
> +    @_Decorators.retry()
> +    @_Decorators.transaction
> +    def __delitem__(self, cursor, key):
>          if key not in self:
>              raise KeyError(key)
> -        self._execute("DELETE from %s where key=?;" % self.table,
> [key])
> +        cursor.execute("DELETE from %s where key=?;" % self.table,
> [key]) 
> -    def __setitem__(self, key, value):
> +    @_Decorators.retry()
> +    @_Decorators.transaction
> +    def __setitem__(self, cursor, key, value):
>          if not isinstance(key, str):
>              raise TypeError('Only string keys are supported')
>          elif not isinstance(value, str):
>              raise TypeError('Only string values are supported')
>  
> -        data = self._execute("SELECT * from %s where key=?;" %
> -                                   self.table, [key])
> -        exists = len(list(data))
> -        if exists:
> -            self._execute("UPDATE %s SET value=? WHERE key=?;" %
> self.table,
> -                          [value, key])
> +        cursor.execute("SELECT * from %s where key=?;" % self.table,
> [key])
> +        row = cursor.fetchone()
> +        if row is not None:
> +            cursor.execute("UPDATE %s SET value=? WHERE key=?;" %
> self.table, [value, key]) else:
> -            self._execute("INSERT into %s(key, value) values
> (?, ?);" %
> -                          self.table, [key, value])
> -
> -    def __contains__(self, key):
> -        return key in set(self)
> -
> -    def __len__(self):
> -        data = self._execute("SELECT COUNT(key) FROM %s;" %
> self.table)
> -        for row in data:
> +            cursor.execute("INSERT into %s(key, value) values
> (?, ?);" % self.table, [key, value]) +
> +    @_Decorators.retry()
> +    @_Decorators.transaction
> +    def __contains__(self, cursor, key):
> +        cursor.execute('SELECT * from %s where key=?;' % self.table,
> [key])
> +        return cursor.fetchone() is not None
> +
> +    @_Decorators.retry()
> +    @_Decorators.transaction
> +    def __len__(self, cursor):
> +        cursor.execute("SELECT COUNT(key) FROM %s;" % self.table)
> +        row = cursor.fetchone()
> +        if row is not None:
>              return row[0]
>  
>      def __iter__(self):
> -        data = self._execute("SELECT key FROM %s;" % self.table)
> -        return (row[0] for row in data)
> +        return self._row_iter(lambda row: row[0], "SELECT key from
> %s;" % self.table) 
>      def __lt__(self, other):
>          if not isinstance(other, Mapping):
> @@ -122,25 +210,27 @@ class SQLTable(collections.MutableMapping):
>          return len(self) < len(other)
>  
>      def get_by_pattern(self, pattern):
> -        data = self._execute("SELECT * FROM %s WHERE key LIKE ?;" %
> -                             self.table, [pattern])
> -        return [row[1] for row in data]
> +        return self._row_iter(lambda row: row[1], "SELECT * FROM %s
> WHERE key LIKE ?;" %
> +                              self.table, [pattern])
>  
>      def values(self):
>          return list(self.itervalues())
>  
>      def itervalues(self):
> -        data = self._execute("SELECT value FROM %s;" % self.table)
> -        return (row[0] for row in data)
> +        return self._row_iter(lambda row: row[0], "SELECT value FROM
> %s;" %
> +                              self.table)
>  
>      def items(self):
>          return list(self.iteritems())
>  
>      def iteritems(self):
> -        return self._execute("SELECT * FROM %s;" % self.table)
> +        return self._row_iter(lambda row: (row[0], row[1]), "SELECT
> * FROM %s;" %
> +                              self.table)
>  
> -    def clear(self):
> -        self._execute("DELETE FROM %s;" % self.table)
> +    @_Decorators.retry()
> +    @_Decorators.transaction
> +    def clear(self, cursor):
> +        cursor.execute("DELETE FROM %s;" % self.table)
>  
>      def has_key(self, key):
>          return key in self
> @@ -194,12 +284,6 @@ class PersistData(object):
>          """
>          del self.data[domain][key]
>  
> -def connect(database):
> -    connection = sqlite3.connect(database, timeout=5,
> isolation_level=None)
> -    connection.execute("pragma synchronous = off;")
> -    connection.text_factory = str
> -    return connection
> -
>  def persist(domain, d):
>      """Convenience factory for SQLTable objects based upon
> metadata""" import bb.utils
> diff --git a/bitbake/lib/bb/process.py b/bitbake/lib/bb/process.py
> index e69697c..2dc472a 100644
> --- a/bitbake/lib/bb/process.py
> +++ b/bitbake/lib/bb/process.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  import logging
>  import signal
>  import subprocess
> diff --git a/bitbake/lib/bb/progress.py b/bitbake/lib/bb/progress.py
> index f54d1c7..4022caa 100644
> --- a/bitbake/lib/bb/progress.py
> +++ b/bitbake/lib/bb/progress.py
> @@ -4,18 +4,8 @@ BitBake progress handling code
>  
>  # Copyright (C) 2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import sys
>  import re
> @@ -23,6 +13,7 @@ import time
>  import inspect
>  import bb.event
>  import bb.build
> +from bb.build import StdoutNoopContextManager
>  
>  class ProgressHandler(object):
>      """
> @@ -37,7 +28,14 @@ class ProgressHandler(object):
>          if outfile:
>              self._outfile = outfile
>          else:
> -            self._outfile = sys.stdout
> +            self._outfile = StdoutNoopContextManager()
> +
> +    def __enter__(self):
> +        self._outfile.__enter__()
> +        return self
> +
> +    def __exit__(self, *excinfo):
> +        self._outfile.__exit__(*excinfo)
>  
>      def _fire_progress(self, taskprogress, rate=None):
>          """Internal function to fire the progress event"""
> @@ -157,6 +155,12 @@ class MultiStageProgressReporter(object):
>              self._stage_total = None
>              self._callers = []
>  
> +    def __enter__(self):
> +        return self
> +
> +    def __exit__(self, *excinfo):
> +        pass
> +
>      def _fire_progress(self, taskprogress):
>          bb.event.fire(bb.build.TaskProgress(taskprogress),
> self._data) 
> diff --git a/bitbake/lib/bb/providers.py b/bitbake/lib/bb/providers.py
> index c2aa98c..f80963c 100644
> --- a/bitbake/lib/bb/providers.py
> +++ b/bitbake/lib/bb/providers.py
> @@ -1,5 +1,3 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # Copyright (C) 2003, 2004  Chris Larson
>  # Copyright (C) 2003, 2004  Phil Blundell
> @@ -8,18 +6,8 @@
>  # Copyright (C) 2005        ROAD GmbH
>  # Copyright (C) 2006        Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import re
>  import logging
> @@ -129,7 +117,7 @@ def findPreferredProvider(pn, cfgData, dataCache,
> pkg_pn = None, item = None): preferred_v =
> cfgData.getVar("PREFERRED_VERSION") 
>      if preferred_v:
> -        m = re.match('(\d+:)*(.*)(_.*)*', preferred_v)
> +        m = re.match(r'(\d+:)*(.*)(_.*)*', preferred_v)
>          if m:
>              if m.group(1):
>                  preferred_e = m.group(1)[:-1]
> @@ -384,7 +372,7 @@ def getRuntimeProviders(dataCache, rdepend):
>  
>      # Only search dynamic packages if we can't find anything in
> other variables for pattern in dataCache.packages_dynamic:
> -        pattern = pattern.replace('+', "\+")
> +        pattern = pattern.replace(r'+', r"\+")
>          if pattern in regexp_cache:
>              regexp = regexp_cache[pattern]
>          else:
> diff --git a/bitbake/lib/bb/pysh/builtin.py
> b/bitbake/lib/bb/pysh/builtin.py deleted file mode 100644
> index a8814dc..0000000
> --- a/bitbake/lib/bb/pysh/builtin.py
> +++ /dev/null
> @@ -1,710 +0,0 @@
> -# builtin.py - builtins and utilities definitions for pysh.
> -#
> -# Copyright 2007 Patrick Mezard
> -#
> -# This software may be used and distributed according to the terms
> -# of the GNU General Public License, incorporated herein by
> reference. -
> -"""Builtin and internal utilities implementations.
> -
> -- Beware not to use python interpreter environment as if it were the
> shell -environment. For instance, commands working directory must be
> explicitely handled -through env['PWD'] instead of relying on python
> working directory. -"""
> -import errno
> -import optparse
> -import os
> -import re
> -import subprocess
> -import sys
> -import time
> -
> -def has_subprocess_bug():
> -    return getattr(subprocess, 'list2cmdline') and \
> -       (    subprocess.list2cmdline(['']) == '' or \
> -            subprocess.list2cmdline(['foo|bar']) == 'foo|bar')
> -            
> -# Detect python bug 1634343: "subprocess swallows empty arguments
> under win32" -#
> <http://sourceforge.net/tracker/index.php?func=detail&aid=1634343&group_id=5470&atid=105470>
> -# Also detect: "[ 1710802 ] subprocess must escape redirection
> characters under win32" -#
> <http://sourceforge.net/tracker/index.php?func=detail&aid=1710802&group_id=5470&atid=105470>
> -if has_subprocess_bug():
> -    import subprocess_fix
> -    subprocess.list2cmdline = subprocess_fix.list2cmdline
> -
> -from sherrors import *
> -
> -class NonExitingParser(optparse.OptionParser):
> -    """OptionParser default behaviour upon error is to print the
> error message and
> -    exit. Raise a utility error instead.
> -    """
> -    def error(self, msg):
> -        raise UtilityError(msg)
> -
> -#-------------------------------------------------------------------------------  
> -# set special builtin
> -#-------------------------------------------------------------------------------  
> -OPT_SET = NonExitingParser(usage="set - set or unset options and
> positional parameters") -OPT_SET.add_option( '-f',
> action='store_true', dest='has_f', default=False,
> -    help='The shell shall disable pathname expansion.')
> -OPT_SET.add_option('-e', action='store_true', dest='has_e',
> default=False,
> -    help="""When this option is on, if a simple command fails for
> any of the \
> -    reasons listed in Consequences of Shell Errors or returns an
> exit status \
> -    value >0, and is not part of the compound list following a
> while, until, \
> -    or if keyword, and is not a part of an AND or OR list, and is
> not a \
> -    pipeline preceded by the ! reserved word, then the shell shall
> immediately \
> -    exit.""")
> -OPT_SET.add_option('-x', action='store_true', dest='has_x',
> default=False,
> -    help="""The shell shall write to standard error a trace for each
> command \
> -    after it expands the command and before it executes it. It is
> unspecified \
> -    whether the command that turns tracing off is traced.""")
> -
> -def builtin_set(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n') -
> -    option, args = OPT_SET.parse_args(args)
> -    env = interp.get_env()
> -    
> -    if option.has_f:
> -        env.set_opt('-f')
> -    if option.has_e:
> -        env.set_opt('-e')
> -    if option.has_x:
> -        env.set_opt('-x')
> -    return 0
> -    
> -#-------------------------------------------------------------------------------  
> -# shift special builtin
> -#-------------------------------------------------------------------------------  
> -def builtin_shift(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -        
> -    params = interp.get_env().get_positional_args()
> -    if args:
> -        try:
> -            n = int(args[0])
> -            if n > len(params):
> -                raise ValueError()
> -        except ValueError:
> -            return 1
> -    else:
> -        n = 1
> -        
> -    params[:n] = []
> -    interp.get_env().set_positional_args(params)
> -    return 0
> -    
> -#-------------------------------------------------------------------------------  
> -# export special builtin
> -#-------------------------------------------------------------------------------  
> -OPT_EXPORT = NonExitingParser(usage="set - set or unset options and
> positional parameters") -OPT_EXPORT.add_option('-p',
> action='store_true', dest='has_p', default=False) -
> -def builtin_export(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -        
> -    option, args = OPT_EXPORT.parse_args(args)
> -    if option.has_p:
> -        raise NotImplementedError()
> -    
> -    for arg in args:
> -        try:
> -            name, value = arg.split('=', 1)
> -        except ValueError:
> -            name, value = arg, None
> -        env = interp.get_env().export(name, value)
> -    
> -    return 0
> -    
> -#-------------------------------------------------------------------------------  
> -# return special builtin
> -#-------------------------------------------------------------------------------  
> -def builtin_return(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -    res = 0
> -    if args:
> -        try:
> -            res = int(args[0])
> -        except ValueError:
> -            res = 0
> -        if not 0<=res<=255:
> -            res = 0
> -            
> -    # BUG: should be last executed command exit code        
> -    raise ReturnSignal(res)
> -
> -#-------------------------------------------------------------------------------  
> -# trap special builtin
> -#-------------------------------------------------------------------------------  
> -def builtin_trap(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -    if len(args) < 2:
> -        stderr.write('trap: usage: trap [[arg] signal_spec ...]\n')
> -        return 2
> -
> -    action = args[0]
> -    for sig in args[1:]:
> -        try:
> -            env.traps[sig] = action
> -        except Exception as e:
> -            stderr.write('trap: %s\n' % str(e))
> -    return 0
> -
> -#-------------------------------------------------------------------------------  
> -# unset special builtin
> -#-------------------------------------------------------------------------------
> -OPT_UNSET = NonExitingParser("unset - unset values and attributes of
> variables and functions") -OPT_UNSET.add_option( '-f',
> action='store_true', dest='has_f', default=False)
> -OPT_UNSET.add_option( '-v', action='store_true', dest='has_v',
> default=False) - -def builtin_unset(name, args, interp, env, stdin,
> stdout, stderr, debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -        
> -    option, args = OPT_UNSET.parse_args(args)
> -    
> -    status = 0
> -    env = interp.get_env()
> -    for arg in args:    
> -        try:
> -            if option.has_f:
> -                env.remove_function(arg)
> -            else:
> -                del env[arg]
> -        except KeyError:
> -            pass
> -        except VarAssignmentError:
> -            status = 1
> -            
> -    return status
> -
> -#-------------------------------------------------------------------------------  
> -# wait special builtin
> -#-------------------------------------------------------------------------------  
> -def builtin_wait(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n') -
> -    return interp.wait([int(arg) for arg in args])
> -
> -#-------------------------------------------------------------------------------  
> -# cat utility
> -#-------------------------------------------------------------------------------
> -def utility_cat(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n') -
> -    if not args:
> -        args = ['-']
> -
> -    status = 0
> -    for arg in args:
> -        if arg == '-':
> -            data = stdin.read()
> -        else:
> -            path = os.path.join(env['PWD'], arg)
> -            try:
> -                f = file(path, 'rb')
> -                try:
> -                    data = f.read()
> -                finally:
> -                    f.close()
> -            except IOError as e:
> -                if e.errno != errno.ENOENT:
> -                    raise
> -                status = 1
> -                continue
> -        stdout.write(data)
> -        stdout.flush()
> -    return status
> -    
> -#-------------------------------------------------------------------------------  
> -# cd utility
> -#-------------------------------------------------------------------------------  
> -OPT_CD = NonExitingParser("cd - change the working directory")
> -
> -def utility_cd(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n') -
> -    option, args = OPT_CD.parse_args(args)
> -    env = interp.get_env()
> -    
> -    directory = None
> -    printdir = False
> -    if not args:
> -        home = env.get('HOME')
> -        if home:
> -            # Unspecified, do nothing
> -            return 0
> -        else:
> -            directory = home
> -    elif len(args)==1:
> -        directory = args[0]
> -        if directory=='-':
> -            if 'OLDPWD' not in env:
> -                raise UtilityError("OLDPWD not set")
> -            printdir = True
> -            directory = env['OLDPWD']
> -    else:
> -        raise UtilityError("too many arguments")
> -            
> -    curpath = None
> -    # Absolute directories will be handled correctly by the
> os.path.join call.
> -    if not directory.startswith('.') and not
> directory.startswith('..'):
> -        cdpaths = env.get('CDPATH', '.').split(';')
> -        for cdpath in cdpaths:
> -            p = os.path.join(cdpath, directory)
> -            if os.path.isdir(p):
> -                curpath = p
> -                break
> -    
> -    if curpath is None:
> -        curpath = directory
> -    curpath = os.path.join(env['PWD'], directory)
> -
> -    env['OLDPWD'] = env['PWD']
> -    env['PWD'] = curpath
> -    if printdir:
> -        stdout.write('%s\n' % curpath)
> -    return 0
> -
> -#-------------------------------------------------------------------------------  
> -# colon utility
> -#-------------------------------------------------------------------------------  
> -def utility_colon(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -    return 0
> -    
> -#-------------------------------------------------------------------------------  
> -# echo utility
> -#-------------------------------------------------------------------------------  
> -def utility_echo(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -        
> -    # Echo only takes arguments, no options. Use printf if you need
> fancy stuff.
> -    output = ' '.join(args) + '\n'
> -    stdout.write(output)
> -    stdout.flush()
> -    return 0
> -    
> -#-------------------------------------------------------------------------------  
> -# egrep utility
> -#-------------------------------------------------------------------------------
> -# egrep is usually a shell script.
> -# Unfortunately, pysh does not support shell scripts *with
> arguments* right now, -# so the redirection is implemented here,
> assuming grep is available. -def utility_egrep(name, args, interp,
> env, stdin, stdout, stderr, debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -        
> -    return run_command('grep', ['-E'] + args, interp, env, stdin,
> stdout, 
> -        stderr, debugflags)
> -    
> -#-------------------------------------------------------------------------------  
> -# env utility
> -#-------------------------------------------------------------------------------  
> -def utility_env(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -    
> -    if args and args[0]=='-i':
> -        raise NotImplementedError('env: -i option is not
> implemented')
> -    
> -    i = 0
> -    for arg in args:
> -        if '=' not in arg:
> -            break
> -        # Update the current environment
> -        name, value = arg.split('=', 1)
> -        env[name] = value
> -        i += 1
> -        
> -    if args[i:]:
> -        # Find then execute the specified interpreter
> -        utility = env.find_in_path(args[i])
> -        if not utility:
> -            return 127
> -        args[i:i+1] = utility
> -        name = args[i]
> -        args = args[i+1:]
> -        try:
> -            return run_command(name, args, interp, env, stdin,
> stdout, stderr, 
> -                debugflags)
> -        except UtilityError:
> -            stderr.write('env: failed to execute %s' % '
> '.join([name]+args))
> -            return 126            
> -    else:
> -        for pair in env.get_variables().iteritems():
> -            stdout.write('%s=%s\n' % pair)
> -    return 0
> -    
> -#-------------------------------------------------------------------------------  
> -# exit utility
> -#-------------------------------------------------------------------------------
> -def utility_exit(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -        
> -    res = None
> -    if args:
> -        try:
> -            res = int(args[0])
> -        except ValueError:
> -            res = None
> -        if not 0<=res<=255:
> -            res = None
> -            
> -    if res is None:
> -        # BUG: should be last executed command exit code
> -        res = 0
> -        
> -    raise ExitSignal(res)
> -
> -#-------------------------------------------------------------------------------  
> -# fgrep utility
> -#-------------------------------------------------------------------------------
> -# see egrep
> -def utility_fgrep(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -        
> -    return run_command('grep', ['-F'] + args, interp, env, stdin,
> stdout, 
> -        stderr, debugflags)
> -
> -#-------------------------------------------------------------------------------  
> -# gunzip utility
> -#-------------------------------------------------------------------------------
> -# see egrep
> -def utility_gunzip(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -        
> -    return run_command('gzip', ['-d'] + args, interp, env, stdin,
> stdout, 
> -        stderr, debugflags)
> -    
> -#-------------------------------------------------------------------------------  
> -# kill utility
> -#-------------------------------------------------------------------------------
> -def utility_kill(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -        
> -    for arg in args:
> -        pid = int(arg)
> -        status = subprocess.call(['pskill', '/T', str(pid)],
> -                                shell=True,
> -                                stdout=subprocess.PIPE,
> -                                stderr=subprocess.PIPE)
> -        # pskill is asynchronous, hence the stupid polling loop
> -        while 1:
> -            p = subprocess.Popen(['pslist', str(pid)],
> -                                shell=True,
> -                                stdout=subprocess.PIPE,
> -                                stderr=subprocess.STDOUT)
> -            output = p.communicate()[0]
> -            if ('process %d was not' % pid) in output:
> -                break
> -            time.sleep(1)
> -    return status
> -    
> -#-------------------------------------------------------------------------------  
> -# mkdir utility
> -#-------------------------------------------------------------------------------
> -OPT_MKDIR = NonExitingParser("mkdir - make directories.")
> -OPT_MKDIR.add_option('-p', action='store_true', dest='has_p',
> default=False) -
> -def utility_mkdir(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -        
> -    # TODO: implement umask
> -    # TODO: implement proper utility error report
> -    option, args = OPT_MKDIR.parse_args(args)
> -    for arg in args:
> -        path = os.path.join(env['PWD'], arg)
> -        if option.has_p:
> -            try:
> -                os.makedirs(path)
> -            except IOError as e:
> -                if e.errno != errno.EEXIST:
> -                    raise
> -        else:               
> -            os.mkdir(path)
> -    return 0
> -
> -#-------------------------------------------------------------------------------  
> -# netstat utility
> -#-------------------------------------------------------------------------------
> -def utility_netstat(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    # Do you really expect me to implement netstat ?
> -    # This empty form is enough for Mercurial tests since it's
> -    # supposed to generate nothing upon success. Faking this test
> -    # is not a big deal either.
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -    return 0
> -    
> -#-------------------------------------------------------------------------------  
> -# pwd utility
> -#-------------------------------------------------------------------------------  
> -OPT_PWD = NonExitingParser("pwd - return working directory name")
> -OPT_PWD.add_option('-L', action='store_true', dest='has_L',
> default=True,
> -    help="""If the PWD environment variable contains an absolute
> pathname of \
> -    the current directory that does not contain the filenames dot or
> dot-dot, \
> -    pwd shall write this pathname to standard output. Otherwise, the
> -L option \
> -    shall behave as the -P option.""")
> -OPT_PWD.add_option('-P', action='store_true', dest='has_L',
> default=False,
> -    help="""The absolute pathname written shall not contain
> filenames that, in \
> -    the context of the pathname, refer to files of type symbolic
> link.""") -
> -def utility_pwd(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n') -
> -    option, args = OPT_PWD.parse_args(args)        
> -    stdout.write('%s\n' % env['PWD'])
> -    return 0
> -    
> -#-------------------------------------------------------------------------------  
> -# printf utility
> -#-------------------------------------------------------------------------------
> -RE_UNESCAPE = re.compile(r'(\\x[a-zA-Z0-9]{2}|\\[0-7]{1,3}|\\.)')
> -
> -def utility_printf(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -        
> -    def replace(m):
> -        assert m.group()
> -        g = m.group()[1:]
> -        if g.startswith('x'):
> -            return chr(int(g[1:], 16))
> -        if len(g) <= 3 and len([c for c in g if c in '01234567']) ==
> len(g):
> -            # Yay, an octal number
> -            return chr(int(g, 8))
> -        return {
> -            'a': '\a',
> -            'b': '\b',
> -            'f': '\f',
> -            'n': '\n',
> -            'r': '\r',
> -            't': '\t',
> -            'v': '\v',
> -            '\\': '\\',
> -        }.get(g)
> -        
> -    # Convert escape sequences
> -    format = re.sub(RE_UNESCAPE, replace, args[0])
> -    stdout.write(format % tuple(args[1:]))
> -    return 0
> -    
> -#-------------------------------------------------------------------------------  
> -# true utility
> -#-------------------------------------------------------------------------------
> -def utility_true(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -    return 0
> -
> -#-------------------------------------------------------------------------------  
> -# sed utility
> -#-------------------------------------------------------------------------------
> -RE_SED = re.compile(r'^s(.).*\1[a-zA-Z]*$')
> -
> -# cygwin sed fails with some expressions when they do not end with a
> single space. -# see unit tests for details. Interestingly, the same
> expressions works perfectly -# in cygwin shell.
> -def utility_sed(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -        
> -    # Scan pattern arguments and append a space if necessary
> -    for i in range(len(args)):
> -        if not RE_SED.search(args[i]):
> -            continue
> -        args[i] = args[i] + ' '
> -
> -    return run_command(name, args, interp, env, stdin, stdout, 
> -        stderr, debugflags)
> -
> -#-------------------------------------------------------------------------------  
> -# sleep utility
> -#-------------------------------------------------------------------------------
> -def utility_sleep(name, args, interp, env, stdin, stdout, stderr,
> debugflags):
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -    time.sleep(int(args[0]))
> -    return 0
> -    
> -#-------------------------------------------------------------------------------  
> -# sort utility
> -#-------------------------------------------------------------------------------
> -OPT_SORT = NonExitingParser("sort - sort, merge, or sequence check
> text files") -
> -def utility_sort(name, args, interp, env, stdin, stdout, stderr,
> debugflags): -
> -    def sort(path):
> -        if path == '-':
> -            lines = stdin.readlines()
> -        else:
> -            try:
> -                f = file(path)
> -                try:
> -                    lines = f.readlines()
> -                finally:
> -                    f.close()
> -            except IOError as e:
> -                stderr.write(str(e) + '\n')
> -                return 1
> -        
> -        if lines and lines[-1][-1]!='\n':
> -            lines[-1] = lines[-1] + '\n'
> -        return lines
> -    
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n')
> -        
> -    option, args = OPT_SORT.parse_args(args)
> -    alllines = []
> -    
> -    if len(args)<=0:
> -        args += ['-']
> -        
> -    # Load all files lines
> -    curdir = os.getcwd()
> -    try:
> -        os.chdir(env['PWD'])
> -        for path in args:
> -            alllines += sort(path)
> -    finally:
> -        os.chdir(curdir)
> -            
> -    alllines.sort()
> -    for line in alllines:
> -        stdout.write(line)
> -    return 0
> -    
> -#-------------------------------------------------------------------------------
> -# hg utility
> -#-------------------------------------------------------------------------------
> -
> -hgcommands = [
> -    'add',
> -    'addremove',
> -    'commit', 'ci',
> -    'debugrename',
> -    'debugwalk',
> -    'falabala', # Dummy command used in a mercurial test
> -    'incoming',
> -    'locate',
> -    'pull',
> -    'push',
> -    'qinit',
> -    'remove', 'rm',
> -    'rename', 'mv',
> -    'revert',    
> -    'showconfig',
> -    'status', 'st',
> -    'strip',
> -    ]
> -
> -def rewriteslashes(name, args):
> -    # Several hg commands output file paths, rewrite the separators
> -    if len(args) > 1 and name.lower().endswith('python') \
> -       and args[0].endswith('hg'):
> -        for cmd in hgcommands:
> -            if cmd in args[1:]:
> -                return True
> -            
> -    # svn output contains many paths with OS specific separators.
> -    # Normalize these to unix paths.
> -    base = os.path.basename(name)
> -    if base.startswith('svn'):
> -        return True
> -    
> -    return False
> -
> -def rewritehg(output):
> -    if not output:
> -        return output
> -    # Rewrite os specific messages
> -    output = output.replace(': The system cannot find the file
> specified',
> -                            ': No such file or directory')
> -    output = re.sub(': Access is denied.*$', ': Permission denied',
> output)
> -    output = output.replace(': No connection could be made because
> the target machine actively refused it',
> -                            ': Connection refused')
> -    return output
> -                            
> -
> -def run_command(name, args, interp, env, stdin, stdout,
> -                stderr, debugflags):
> -    # Execute the command
> -    if 'debug-utility' in debugflags:
> -        print interp.log(' '.join([name, str(args), interp['PWD']])
> + '\n') -
> -    hgbin = interp.options().hgbinary
> -    ishg = hgbin and ('hg' in name or args and 'hg' in args[0])
> -    unixoutput = 'cygwin' in name or ishg
> -    
> -    exec_env = env.get_variables()        
> -    try:
> -        # BUG: comparing file descriptor is clearly not a reliable
> way to tell
> -        # whether they point on the same underlying object. But in
> pysh limited
> -        # scope this is usually right, we do not expect complicated
> redirections
> -        # besides usual 2>&1.
> -        # Still there is one case we have but cannot deal with is
> when stdout
> -        # and stderr are redirected *by pysh caller*. This the
> reason for the
> -        # --redirect pysh() option.
> -        # Now, we want to know they are the same because we
> sometimes need to 
> -        # transform the command output, mostly remove CR-LF to
> ensure that
> -        # command output is unix-like. Cygwin utilies are a special
> case because
> -        # they explicitely set their output streams to binary mode,
> so we have
> -        # nothing to do. For all others commands, we have to guess
> whether they
> -        # are sending text data, in which case the transformation
> must be done.
> -        # Again, the NUL character test is unreliable but should be
> enough for
> -        # hg tests.
> -        redirected = stdout.fileno()==stderr.fileno()
> -        if not redirected:
> -            p = subprocess.Popen([name] + args, cwd=env['PWD'],
> env=exec_env, 
> -                    stdin=stdin, stdout=subprocess.PIPE,
> stderr=subprocess.PIPE)
> -        else:
> -            p = subprocess.Popen([name] + args, cwd=env['PWD'],
> env=exec_env, 
> -                    stdin=stdin, stdout=subprocess.PIPE,
> stderr=subprocess.STDOUT)
> -        out, err = p.communicate()
> -    except WindowsError as e:
> -        raise UtilityError(str(e))
> -
> -    if not unixoutput:
> -        def encode(s):
> -            if '\0' in s:
> -                return s
> -            return s.replace('\r\n', '\n')
> -    else:
> -        encode = lambda s: s
> -
> -    if rewriteslashes(name, args):
> -        encode1_ = encode
> -        def encode(s):
> -            s = encode1_(s)
> -            s = s.replace('\\\\', '\\')
> -            s = s.replace('\\', '/')
> -            return s
> -
> -    if ishg:
> -        encode2_ = encode
> -        def encode(s):
> -            return rewritehg(encode2_(s))
> -    
> -    stdout.write(encode(out))
> -    if not redirected:
> -        stderr.write(encode(err))
> -    return p.returncode
> -            
> diff --git a/bitbake/lib/bb/pysh/interp.py
> b/bitbake/lib/bb/pysh/interp.py deleted file mode 100644
> index d14ecf3..0000000
> --- a/bitbake/lib/bb/pysh/interp.py
> +++ /dev/null
> @@ -1,1367 +0,0 @@
> -# interp.py - shell interpreter for pysh.
> -#
> -# Copyright 2007 Patrick Mezard
> -#
> -# This software may be used and distributed according to the terms
> -# of the GNU General Public License, incorporated herein by
> reference. -
> -"""Implement the shell interpreter.
> -
> -Most references are made to "The Open Group Base Specifications
> Issue 6".
> -<http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html>
> -""" -# TODO: document the fact input streams must implement fileno()
> so Popen will work correctly. -# it requires non-stdin stream to be
> implemented as files. Still to be tested... -# DOC: pathsep is used
> in PATH instead of ':'. Clearly, there are path syntax issues here.
> -# TODO: stop command execution upon error. -# TODO: sort out the
> filename/io_number mess. It should be possible to use filenames only.
> -# TODO: review subshell implementation -# TODO: test environment
> cloning for non-special builtins -# TODO: set -x should not rebuild
> commands from tokens, assignments/redirections are lost -# TODO: unit
> test for variable assignment -# TODO: test error management wrt error
> type/utility type -# TODO: test for binary output everywhere
> -# BUG: debug-parsing does not pass log file to PLY. Maybe a PLY
> upgrade is necessary. -import base64
> -import cPickle as pickle
> -import errno
> -import glob
> -import os
> -import re
> -import subprocess
> -import sys
> -import tempfile
> -
> -try:
> -    s = set()
> -    del s
> -except NameError:
> -    from Set import Set as set
> -
> -import builtin
> -from sherrors import *
> -import pyshlex
> -import pyshyacc
> -
> -def mappend(func, *args, **kargs):
> -    """Like map but assume func returns a list. Returned lists are
> merged into
> -    a single one.
> -    """
> -    return reduce(lambda a,b: a+b, map(func, *args, **kargs), [])
> -
> -class FileWrapper:
> -    """File object wrapper to ease debugging.
> -    
> -    Allow mode checking and implement file duplication through a
> simple 
> -    reference counting scheme. Not sure the latter is really useful
> since
> -    only real file descriptors can be used.
> -    """
> -    def __init__(self, mode, file, close=True):
> -        if mode not in ('r', 'w', 'a'):
> -            raise IOError('invalid mode: %s' % mode)
> -        self._mode = mode
> -        self._close = close
> -        if isinstance(file, FileWrapper):
> -            if file._refcount[0] <= 0:
> -                raise IOError(0, 'Error')
> -            self._refcount = file._refcount
> -            self._refcount[0] += 1
> -            self._file = file._file
> -        else:
> -            self._refcount = [1]
> -            self._file = file
> -        
> -    def dup(self):
> -        return FileWrapper(self._mode, self, self._close)
> -        
> -    def fileno(self):
> -        """fileno() should be only necessary for input streams."""
> -        return self._file.fileno()
> -        
> -    def read(self, size=-1):
> -        if self._mode!='r':
> -            raise IOError(0, 'Error')
> -        return self._file.read(size)
> -        
> -    def readlines(self, *args, **kwargs):
> -        return self._file.readlines(*args, **kwargs)
> -        
> -    def write(self, s):
> -        if self._mode not in ('w', 'a'):
> -            raise IOError(0, 'Error')
> -        return self._file.write(s)
> -        
> -    def flush(self):
> -        self._file.flush()
> -        
> -    def close(self):        
> -        if not self._refcount:
> -            return
> -        assert  self._refcount[0] > 0
> -        
> -        self._refcount[0] -= 1    
> -        if self._refcount[0] == 0:
> -            self._mode = 'c'
> -            if self._close:
> -                self._file.close()
> -        self._refcount = None
> -                
> -    def mode(self):
> -        return self._mode
> -
> -    def __getattr__(self, name):
> -        if name == 'name':
> -            self.name = getattr(self._file, name)
> -            return self.name
> -        else:
> -            raise AttributeError(name)
> -
> -    def __del__(self):
> -        self.close()
> -               
> -               
> -def win32_open_devnull(mode):
> -    return open('NUL', mode)
> -    
> -        
> -class Redirections:
> -    """Stores open files and their mapping to pseudo-sh file
> descriptor.
> -    """
> -    # BUG: redirections are not handled correctly: 1>&3 2>&3 3>&4
> does 
> -    # not make 1 to redirect to 4
> -    def __init__(self, stdin=None, stdout=None, stderr=None):
> -        self._descriptors = {}
> -        if stdin is not None:
> -            self._add_descriptor(0, stdin)
> -        if stdout is not None:
> -            self._add_descriptor(1, stdout)
> -        if stderr is not None:
> -            self._add_descriptor(2, stderr)
> -            
> -    def add_here_document(self, interp, name, content,
> io_number=None):
> -        if io_number is None:
> -            io_number = 0
> -            
> -        if name==pyshlex.unquote_wordtree(name):
> -            content = interp.expand_here_document(('TOKEN', content))
> -    
> -        # Write document content in a temporary file
> -        tmp = tempfile.TemporaryFile()
> -        try:
> -            tmp.write(content)
> -            tmp.flush()
> -            tmp.seek(0)
> -            self._add_descriptor(io_number, FileWrapper('r', tmp))
> -        except:
> -            tmp.close()
> -            raise                
> -        
> -    def add(self, interp, op, filename, io_number=None):
> -        if op not in ('<', '>', '>|', '>>', '>&'):
> -            # TODO: add descriptor duplication and
> here_documents      
> -            raise RedirectionError('Unsupported redirection operator
> "%s"' % op)
> -            
> -        if io_number is not None:
> -            io_number = int(io_number)
> -            
> -        if (op == '>&' and filename.isdigit()) or filename=='-':
> -            # No expansion for file descriptors, quote them if you
> want a filename
> -            fullname = filename
> -        else:
> -            if filename.startswith('/'):
> -                # TODO: win32 kludge
> -                if filename=='/dev/null':
> -                    fullname = 'NUL'
> -                else:
> -                    # TODO: handle absolute pathnames, they are
> unlikely to exist on the
> -                    # current platform (win32 for instance).
> -                    raise NotImplementedError()
> -            else:
> -                fullname = interp.expand_redirection(('TOKEN',
> filename))
> -                if not fullname:
> -                    raise RedirectionError('%s: ambiguous redirect'
> % filename)
> -                # Build absolute path based on PWD
> -                fullname = os.path.join(interp.get_env()['PWD'],
> fullname)
> -            
> -        if op=='<':
> -            return self._add_input_redirection(interp, fullname,
> io_number)
> -        elif op in ('>', '>|'):
> -            clobber = ('>|'==op)
> -            return self._add_output_redirection(interp, fullname,
> io_number, clobber)
> -        elif op=='>>':
> -            return self._add_output_appending(interp, fullname,
> io_number)
> -        elif op=='>&':
> -            return self._dup_output_descriptor(fullname, io_number)
> -        
> -    def close(self):
> -        if self._descriptors is not None:
> -            for desc in self._descriptors.itervalues():
> -                desc.flush()
> -                desc.close()
> -            self._descriptors = None
> -            
> -    def stdin(self):
> -        return self._descriptors[0]
> -          
> -    def stdout(self):
> -        return self._descriptors[1] 
> -        
> -    def stderr(self):
> -        return self._descriptors[2] 
> -            
> -    def clone(self):
> -        clone = Redirections()
> -        for desc, fileobj in self._descriptors.iteritems():
> -            clone._descriptors[desc] = fileobj.dup()
> -        return clone
> -           
> -    def _add_output_redirection(self, interp, filename, io_number,
> clobber):    
> -        if io_number is None:
> -            # io_number default to standard output
> -            io_number = 1
> -        
> -        if not clobber and interp.get_env().has_opt('-C') and
> os.path.isfile(filename):
> -            # File already exist in no-clobber mode, bail out
> -            raise RedirectionError('File "%s" already exists' %
> filename)
> -            
> -        # Open and register
> -        self._add_file_descriptor(io_number, filename, 'w')
> -        
> -    def _add_output_appending(self, interp, filename, io_number):    
> -        if io_number is None:
> -            io_number = 1
> -        self._add_file_descriptor(io_number, filename, 'a')
> -            
> -    def _add_input_redirection(self, interp, filename, io_number):
> -        if io_number is None:
> -            io_number = 0
> -        self._add_file_descriptor(io_number, filename, 'r')
> -        
> -    def _add_file_descriptor(self, io_number, filename, mode):    
> -        try:            
> -            if filename.startswith('/'):
> -                if filename=='/dev/null':
> -                    f = win32_open_devnull(mode+'b')
> -                else:
> -                    # TODO: handle absolute pathnames, they are
> unlikely to exist on the
> -                    # current platform (win32 for instance).
> -                    raise NotImplementedError('cannot open absolute
> path %s' % repr(filename))
> -            else:
> -                f = file(filename, mode+'b')
> -        except IOError as e:
> -            raise RedirectionError(str(e))
> -            
> -        wrapper = None
> -        try:
> -            wrapper = FileWrapper(mode, f)
> -            f = None
> -            self._add_descriptor(io_number, wrapper)
> -        except:
> -            if f: f.close()
> -            if wrapper: wrapper.close()
> -            raise
> -            
> -    def _dup_output_descriptor(self, source_fd, dest_fd):
> -        if source_fd is None:
> -            source_fd = 1
> -        self._dup_file_descriptor(source_fd, dest_fd, 'w')
> -            
> -    def _dup_file_descriptor(self, source_fd, dest_fd, mode):
> -        source_fd = int(source_fd)
> -        if source_fd not in self._descriptors:
> -            raise RedirectionError('"%s" is not a valid file
> descriptor' % str(source_fd))
> -        source = self._descriptors[source_fd]
> -        
> -        if source.mode()!=mode:
> -            raise RedirectionError('Descriptor %s cannot be
> duplicated in mode "%s"' % (str(source), mode))
> -        
> -        if dest_fd=='-':
> -            # Close the source descriptor
> -            del self._descriptors[source_fd]
> -            source.close()
> -        else:
> -            dest_fd = int(dest_fd)
> -            if dest_fd not in self._descriptors:
> -                raise RedirectionError('Cannot replace file
> descriptor %s' % str(dest_fd))
> -                
> -            dest = self._descriptors[dest_fd]
> -            if dest.mode()!=mode:
> -                raise RedirectionError('Descriptor %s cannot be
> cannot be redirected in mode "%s"' % (str(dest), mode))
> -            
> -            self._descriptors[dest_fd] = source.dup()
> -            dest.close()        
> -            
> -    def _add_descriptor(self, io_number, file):
> -        io_number = int(io_number)
> -        
> -        if io_number in self._descriptors:
> -            # Close the current descriptor
> -            d = self._descriptors[io_number]
> -            del self._descriptors[io_number]
> -            d.close()
> -            
> -        self._descriptors[io_number] = file
> -
> -    def __str__(self):
> -        names = [('%d=%r' % (k, getattr(v, 'name', None))) for k,v
> -                 in self._descriptors.iteritems()]
> -        names = ','.join(names)
> -        return 'Redirections(%s)' % names
> -
> -    def __del__(self):
> -        self.close()
> -    
> -def cygwin_to_windows_path(path):
> -    """Turn /cygdrive/c/foo into c:/foo, or return path if it
> -    is not a cygwin path.
> -    """
> -    if not path.startswith('/cygdrive/'):
> -        return path
> -    path = path[len('/cygdrive/'):]
> -    path = path[:1] + ':' + path[1:]
> -    return path
> -    
> -def win32_to_unix_path(path):
> -    if path is not None:
> -        path = path.replace('\\', '/')
> -    return path    
> -    
> -_RE_SHEBANG = re.compile(r'^\#!\s?([^\s]+)(?:\s([^\s]+))?')
> -_SHEBANG_CMDS = {
> -    '/usr/bin/env': 'env',
> -    '/bin/sh': 'pysh',
> -    'python': 'python',
> -}
> -    
> -def resolve_shebang(path, ignoreshell=False):
> -    """Return a list of arguments as shebang interpreter call or an
> empty list
> -    if path does not refer to an executable script.
> -    See <http://www.opengroup.org/austin/docs/austin_51r2.txt>.
> -    
> -    ignoreshell - set to True to ignore sh shebangs. Return an empty
> list instead.
> -    """
> -    try:
> -        f = file(path)
> -        try:
> -            # At most 80 characters in the first line
> -            header = f.read(80).splitlines()[0]
> -        finally:
> -            f.close()
> -            
> -        m = _RE_SHEBANG.search(header)
> -        if not m:
> -            return []
> -        cmd, arg = m.group(1,2)
> -        if os.path.isfile(cmd):
> -            # Keep this one, the hg script for instance contains a
> weird windows
> -            # shebang referencing the current python install.
> -            cmdfile = os.path.basename(cmd).lower()
> -            if cmdfile == 'python.exe':
> -                cmd = 'python'
> -            pass
> -        elif cmd not in _SHEBANG_CMDS:
> -            raise CommandNotFound('Unknown interpreter "%s"
> referenced in '\
> -                'shebang' % header)
> -        cmd = _SHEBANG_CMDS.get(cmd)
> -        if cmd is None or (ignoreshell and cmd == 'pysh'):
> -            return []
> -        if arg is None:
> -            return [cmd, win32_to_unix_path(path)]
> -        return [cmd, arg, win32_to_unix_path(path)]
> -    except IOError as e:
> -        if  e.errno!=errno.ENOENT and \
> -            (e.errno!=errno.EPERM and not os.path.isdir(path)): #
> Opening a directory raises EPERM
> -            raise
> -        return []
> -        
> -def win32_find_in_path(name, path):
> -    if isinstance(path, str):
> -        path = path.split(os.pathsep)
> -        
> -    exts = os.environ.get('PATHEXT', '').lower().split(os.pathsep)
> -    for p in path:
> -        p_name = os.path.join(p, name)
> -        
> -        prefix = resolve_shebang(p_name)
> -        if prefix:
> -            return prefix
> -            
> -        for ext in exts:    
> -            p_name_ext = p_name + ext
> -            if os.path.exists(p_name_ext):
> -                return [win32_to_unix_path(p_name_ext)]
> -    return []
> -
> -class Traps(dict):
> -    def __setitem__(self, key, value):
> -        if key not in ('EXIT',):
> -            raise NotImplementedError()
> -        super(Traps, self).__setitem__(key, value)
> -
> -# IFS white spaces character class
> -_IFS_WHITESPACES = (' ', '\t', '\n')
> -
> -class Environment:
> -    """Environment holds environment variables, export table,
> function 
> -    definitions and whatever is defined in 2.12 "Shell Execution
> Environment",
> -    redirection excepted.
> -    """
> -    def __init__(self, pwd):
> -        self._opt = set()       #Shell options
> -        
> -        self._functions = {}        
> -        self._env = {'?': '0', '#': '0'}
> -        self._exported = set([
> -            'HOME', 'IFS', 'PATH'
> -        ])
> -        
> -        # Set environment vars with side-effects
> -        self._ifs_ws = None     # Set of IFS whitespace characters
> -        self._ifs_re = None     # Regular expression used to split
> between words using IFS classes
> -        self['IFS'] = ''.join(_IFS_WHITESPACES) #Default environment
> values
> -        self['PWD'] = pwd
> -        self.traps = Traps()
> -        
> -    def clone(self, subshell=False):
> -        env = Environment(self['PWD'])
> -        env._opt = set(self._opt)
> -        for k,v in self.get_variables().iteritems():
> -            if k in self._exported:
> -                env.export(k,v)
> -            elif subshell:
> -                env[k] = v
> -                
> -        if subshell:
> -            env._functions = dict(self._functions)
> -            
> -        return env        
> -        
> -    def __getitem__(self, key):
> -        if key in ('@', '*', '-', '$'):
> -            raise NotImplementedError('%s is not implemented' %
> repr(key))
> -        return self._env[key]
> -        
> -    def get(self, key, defval=None):
> -        try:
> -            return self[key]
> -        except KeyError:
> -            return defval
> -        
> -    def __setitem__(self, key, value):
> -        if key=='IFS':
> -            # Update the whitespace/non-whitespace classes
> -            self._update_ifs(value)
> -        elif key=='PWD':
> -            pwd = os.path.abspath(value)
> -            if not os.path.isdir(pwd):
> -                raise VarAssignmentError('Invalid directory %s' %
> value)
> -            value = pwd
> -        elif key in ('?', '!'):
> -            value = str(int(value))
> -        self._env[key] = value
> -        
> -    def __delitem__(self, key):
> -        if key in ('IFS', 'PWD', '?'):
> -            raise VarAssignmentError('%s cannot be unset' % key)
> -        del self._env[key]
> -
> -    def __contains__(self, item):
> -        return item in self._env
> -        
> -    def set_positional_args(self, args):
> -        """Set the content of 'args' as positional argument from 1
> to len(args).
> -        Return previous argument as a list of strings.
> -        """
> -        # Save and remove previous arguments
> -        prevargs = []        
> -        for i in range(int(self._env['#'])):
> -            i = str(i+1)
> -            prevargs.append(self._env[i])
> -            del self._env[i]
> -        self._env['#'] = '0'
> -                
> -        #Set new ones
> -        for i,arg in enumerate(args):
> -            self._env[str(i+1)] = str(arg)
> -        self._env['#'] = str(len(args))
> -        
> -        return prevargs
> -        
> -    def get_positional_args(self):
> -        return [self._env[str(i+1)] for i in
> range(int(self._env['#']))]
> -        
> -    def get_variables(self):
> -        return dict(self._env)
> -        
> -    def export(self, key, value=None):
> -        if value is not None:
> -            self[key] = value
> -        self._exported.add(key)
> -        
> -    def get_exported(self):
> -        return [(k,self._env.get(k)) for k in self._exported]
> -            
> -    def split_fields(self, word):
> -        if not self._ifs_ws or not word:
> -            return [word]
> -        return re.split(self._ifs_re, word)
> -   
> -    def _update_ifs(self, value):
> -        """Update the split_fields related variables when IFS
> character set is
> -        changed.
> -        """
> -        # TODO: handle NULL IFS
> -        
> -        # Separate characters in whitespace and non-whitespace
> -        chars = set(value)
> -        ws = [c for c in chars if c in _IFS_WHITESPACES]
> -        nws = [c for c in chars if c not in _IFS_WHITESPACES]
> -        
> -        # Keep whitespaces in a string for left and right stripping
> -        self._ifs_ws = ''.join(ws)
> -        
> -        # Build a regexp to split fields
> -        trailing = '[' + ''.join([re.escape(c) for c in ws]) + ']'
> -        if nws:
> -            # First, the single non-whitespace occurence.
> -            nws = '[' + ''.join([re.escape(c) for c in nws]) + ']'
> -            nws = '(?:' + trailing + '*' + nws + trailing + '*' +
> '|' + trailing + '+)'
> -        else:
> -            # Then mix all parts with quantifiers
> -            nws = trailing + '+'
> -        self._ifs_re = re.compile(nws)
> -       
> -    def has_opt(self, opt, val=None):
> -        return (opt, val) in self._opt
> -        
> -    def set_opt(self, opt, val=None):
> -        self._opt.add((opt, val))
> -        
> -    def find_in_path(self, name, pwd=False):
> -        path = self._env.get('PATH', '').split(os.pathsep)
> -        if pwd:
> -            path[:0] = [self['PWD']]
> -        if os.name == 'nt':
> -            return win32_find_in_path(name, self._env.get('PATH',
> ''))
> -        else:
> -            raise NotImplementedError()
> -            
> -    def define_function(self, name, body):
> -        if not is_name(name):
> -            raise ShellSyntaxError('%s is not a valid function name'
> % repr(name))
> -        self._functions[name] = body
> -        
> -    def remove_function(self, name):
> -        del self._functions[name]
> -        
> -    def is_function(self, name):
> -        return name in self._functions
> -        
> -    def get_function(self, name):
> -        return self._functions.get(name)
> -        
> -       
> -name_charset =
> 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
> -name_charset = dict(zip(name_charset,name_charset))
> -           
> -def match_name(s):
> -    """Return the length in characters of the longest prefix made of
> name
> -    allowed characters in s.
> -    """
> -    for i,c in enumerate(s):
> -        if c not in name_charset:
> -            return s[:i]
> -    return s
> -    
> -def is_name(s):
> -    return len([c for c in s if c not in name_charset])<=0
> -    
> -def is_special_param(c):
> -    return len(c)==1 and c in ('@','*','#','?','-','$','!','0')
> -    
> -def utility_not_implemented(name, *args, **kwargs):
> -    raise NotImplementedError('%s utility is not implemented' % name)
> -    
> -
> -class Utility:
> -    """Define utilities properties:
> -    func -- utility callable. See builtin module for utility samples.
> -    is_special -- see XCU 2.8.
> -    """
> -    def __init__(self, func, is_special=0):
> -        self.func = func
> -        self.is_special = bool(is_special)
> -
> -
> -def encodeargs(args):
> -    def encodearg(s):
> -        lines = base64.encodestring(s)
> -        lines = [l.splitlines()[0] for l in lines]
> -        return ''.join(lines)
> -
> -    s = pickle.dumps(args)
> -    return encodearg(s)
> -
> -def decodeargs(s):
> -    s = base64.decodestring(s)
> -    return pickle.loads(s)
> -    
> -
> -class GlobError(Exception):
> -    pass
> -
> -class Options:
> -    def __init__(self):
> -        # True if Mercurial operates with binary streams
> -        self.hgbinary = True
> -
> -class Interpreter:
> -    # Implementation is very basic: the execute() method just makes
> a DFS on the
> -    # AST and execute nodes one by one. Nodes are tuple (name,obj)
> where name
> -    # is a string identifier and obj the AST element returned by the
> parser.
> -    #
> -    # Handler are named after the node identifiers.
> -    # TODO: check node names and remove the switch in execute with
> some
> -    # dynamic getattr() call to find node handlers.
> -    """Shell interpreter.
> -    
> -    The following debugging flags can be passed:
> -    debug-parsing - enable PLY debugging.
> -    debug-tree - print the generated AST.
> -    debug-cmd - trace command execution before word expansion, plus
> exit status.
> -    debug-utility - trace utility execution.
> -    """
> -    
> -    # List supported commands.
> -    COMMANDS = {
> -        'cat':       Utility(builtin.utility_cat,),
> -        'cd':       Utility(builtin.utility_cd,),
> -        ':':        Utility(builtin.utility_colon,),
> -        'echo':     Utility(builtin.utility_echo),
> -        'env':      Utility(builtin.utility_env),
> -        'exit':     Utility(builtin.utility_exit),
> -        'export':   Utility(builtin.builtin_export,
> is_special=1),
> -        'egrep':    Utility(builtin.utility_egrep),
> -        'fgrep':    Utility(builtin.utility_fgrep),
> -        'gunzip':   Utility(builtin.utility_gunzip),
> -        'kill':     Utility(builtin.utility_kill),
> -        'mkdir':    Utility(builtin.utility_mkdir),
> -        'netstat':  Utility(builtin.utility_netstat),
> -        'printf':   Utility(builtin.utility_printf),
> -        'pwd':      Utility(builtin.utility_pwd),
> -        'return':   Utility(builtin.builtin_return,
> is_special=1),
> -        'sed':      Utility(builtin.utility_sed,),
> -        'set':      Utility(builtin.builtin_set,),
> -        'shift':    Utility(builtin.builtin_shift,),
> -        'sleep':    Utility(builtin.utility_sleep,),
> -        'sort':     Utility(builtin.utility_sort,),
> -        'trap':     Utility(builtin.builtin_trap,
> is_special=1),
> -        'true':     Utility(builtin.utility_true),
> -        'unset':    Utility(builtin.builtin_unset,
> is_special=1),
> -        'wait':     Utility(builtin.builtin_wait,
> is_special=1),
> -    }
> -    
> -    def __init__(self, pwd, debugflags = [], env=None, redirs=None,
> stdin=None,
> -                 stdout=None, stderr=None, opts=Options()):
> -        self._env = env
> -        if self._env is None:
> -            self._env = Environment(pwd)
> -        self._children = {}
> -            
> -        self._redirs = redirs
> -        self._close_redirs = False
> -        
> -        if self._redirs is None:
> -            if stdin is None:
> -                stdin = sys.stdin
> -            if stdout is None:
> -                stdout = sys.stdout
> -            if stderr is None:
> -                stderr = sys.stderr
> -            stdin = FileWrapper('r', stdin, False)
> -            stdout = FileWrapper('w', stdout, False)
> -            stderr = FileWrapper('w', stderr, False)
> -            self._redirs = Redirections(stdin, stdout, stderr)
> -            self._close_redirs = True
> -            
> -        self._debugflags = list(debugflags)
> -        self._logfile = sys.stderr
> -        self._options = opts
> -        
> -    def close(self):
> -        """Must be called when the interpreter is no longer used."""
> -        script = self._env.traps.get('EXIT')
> -        if script:
> -            try:
> -                self.execute_script(script=script)
> -            except:
> -                pass
> -
> -        if self._redirs is not None and self._close_redirs:
> -            self._redirs.close()
> -            self._redirs = None
> -            
> -    def log(self, s):
> -        self._logfile.write(s)
> -        self._logfile.flush()
> -            
> -    def __getitem__(self, key):
> -        return self._env[key]
> -        
> -    def __setitem__(self, key, value):
> -        self._env[key] = value
> -
> -    def options(self):
> -        return self._options
> -
> -    def redirect(self, redirs, ios):
> -        def add_redir(io):
> -            if isinstance(io, pyshyacc.IORedirect):
> -                redirs.add(self, io.op, io.filename, io.io_number)
> -            else:
> -                redirs.add_here_document(self, io.name, io.content,
> io.io_number)
> -                    
> -        map(add_redir, ios)
> -        return redirs
> -            
> -    def execute_script(self, script=None, ast=None, sourced=False,
> -                       scriptpath=None):
> -        """If script is not None, parse the input. Otherwise takes
> the supplied
> -        AST. Then execute the AST.
> -        Return the script exit status.
> -        """
> -        try:
> -            if scriptpath is not None:
> -                self._env['0'] = os.path.abspath(scriptpath)
> -
> -            if script is not None:
> -                debug_parsing = ('debug-parsing' in
> self._debugflags)    
> -                cmds, script = pyshyacc.parse(script, True,
> debug_parsing)
> -                if 'debug-tree' in self._debugflags:
> -                    pyshyacc.print_commands(cmds, self._logfile)
> -                    self._logfile.flush()
> -            else:
> -                cmds, script = ast, ''                
> -                
> -            status = 0
> -            for cmd in cmds:
> -                try:
> -                    status = self.execute(cmd)
> -                except ExitSignal as e:
> -                    if sourced:
> -                        raise
> -                    status = int(e.args[0])
> -                    return status
> -                except ShellError:
> -                    self._env['?'] = 1
> -                    raise
> -                if 'debug-utility' in self._debugflags or
> 'debug-cmd' in self._debugflags:
> -                    self.log('returncode ' + str(status)+ '\n')
> -            return status
> -        except CommandNotFound as e:
> -            print >>self._redirs.stderr, str(e)
> -            self._redirs.stderr.flush()
> -            # Command not found by non-interactive shell
> -            # return 127
> -            raise
> -        except RedirectionError as e:
> -            # TODO: should be handled depending on the utility status
> -            print >>self._redirs.stderr, str(e)
> -            self._redirs.stderr.flush()
> -            # Command not found by non-interactive shell
> -            # return 127
> -            raise
> -
> -    def dotcommand(self, env, args):
> -        if len(args) < 1:
> -            raise ShellError('. expects at least one argument')
> -        path = args[0]
> -        if '/' not in path:
> -            found = env.find_in_path(args[0], True)
> -            if found:
> -                path = found[0]
> -        script = file(path).read()
> -        return self.execute_script(script=script, sourced=True)
> -
> -    def execute(self, token, redirs=None):
> -        """Execute and AST subtree with supplied redirections
> overriding default
> -        interpreter ones.
> -        Return the exit status.
> -        """
> -        if not token:
> -            return 0
> -            
> -        if redirs is None:
> -            redirs = self._redirs
> -            
> -        if isinstance(token, list):
> -            # Commands sequence
> -            res = 0
> -            for t in token:
> -                res = self.execute(t, redirs)
> -            return res
> -
> -        type, value = token
> -        status = 0
> -        if type=='simple_command':
> -            redirs_copy = redirs.clone()
> -            try:
> -                # TODO: define and handle command return values
> -                # TODO: implement set -e
> -                status = self._execute_simple_command(value,
> redirs_copy)
> -            finally:
> -                redirs_copy.close()
> -        elif type=='pipeline':
> -            status = self._execute_pipeline(value, redirs)
> -        elif type=='and_or':
> -            status = self._execute_and_or(value, redirs)
> -        elif type=='for_clause':
> -            status = self._execute_for_clause(value, redirs)
> -        elif type=='while_clause':
> -            status = self._execute_while_clause(value, redirs)
> -        elif type=='function_definition':
> -            status = self._execute_function_definition(value, redirs)
> -        elif type=='brace_group':
> -            status = self._execute_brace_group(value, redirs)
> -        elif type=='if_clause':
> -            status = self._execute_if_clause(value, redirs)
> -        elif type=='subshell':
> -            status = self.subshell(ast=value.cmds, redirs=redirs)
> -        elif type=='async':
> -            status = self._asynclist(value)
> -        elif type=='redirect_list':
> -            redirs_copy = self.redirect(redirs.clone(), value.redirs)
> -            try:
> -                status = self.execute(value.cmd, redirs_copy)
> -            finally:
> -                redirs_copy.close()
> -        else:
> -            raise NotImplementedError('Unsupported token type ' +
> type) -
> -        if status < 0:
> -            status = 255
> -        return status
> -            
> -    def _execute_if_clause(self, if_clause, redirs):
> -        cond_status = self.execute(if_clause.cond, redirs)
> -        if cond_status==0:
> -            return self.execute(if_clause.if_cmds, redirs)
> -        else:
> -            return self.execute(if_clause.else_cmds, redirs)
> -      
> -    def _execute_brace_group(self, group, redirs):
> -        status = 0
> -        for cmd in group.cmds:
> -            status = self.execute(cmd, redirs)
> -        return status
> -            
> -    def _execute_function_definition(self, fundef, redirs):
> -        self._env.define_function(fundef.name, fundef.body)
> -        return 0
> -            
> -    def _execute_while_clause(self, while_clause, redirs):
> -        status = 0
> -        while 1:
> -            cond_status = 0
> -            for cond in while_clause.condition:
> -                cond_status = self.execute(cond, redirs)
> -                
> -            if cond_status:
> -                break
> -                
> -            for cmd in while_clause.cmds:
> -                status = self.execute(cmd, redirs)
> -                
> -        return status
> -            
> -    def _execute_for_clause(self, for_clause, redirs):
> -        if not is_name(for_clause.name):
> -            raise ShellSyntaxError('%s is not a valid name' %
> repr(for_clause.name))
> -        items = mappend(self.expand_token, for_clause.items)
> -        
> -        status = 0    
> -        for item in items:
> -            self._env[for_clause.name] = item
> -            for cmd in for_clause.cmds:
> -                status = self.execute(cmd, redirs)
> -        return status
> -            
> -    def _execute_and_or(self, or_and, redirs):
> -        res = self.execute(or_and.left, redirs)        
> -        if (or_and.op=='&&' and res==0) or (or_and.op!='&&' and
> res!=0):
> -            res = self.execute(or_and.right, redirs)
> -        return res
> -            
> -    def _execute_pipeline(self, pipeline, redirs):            
> -        if len(pipeline.commands)==1:
> -            status = self.execute(pipeline.commands[0], redirs)
> -        else:
> -            # Execute all commands one after the other
> -            status = 0
> -            inpath, outpath = None, None
> -            try:
> -                # Commands inputs and outputs cannot really be
> plugged as done
> -                # by a real shell. Run commands sequentially and
> chain their
> -                # input/output throught temporary files.
> -                tmpfd, inpath = tempfile.mkstemp()
> -                os.close(tmpfd)
> -                tmpfd, outpath = tempfile.mkstemp()
> -                os.close(tmpfd)
> -                
> -                inpath = win32_to_unix_path(inpath)
> -                outpath = win32_to_unix_path(outpath)
> -                
> -                for i, cmd in enumerate(pipeline.commands):
> -                    call_redirs = redirs.clone()
> -                    try:
> -                        if i!=0:
> -                            call_redirs.add(self, '<', inpath)
> -                        if i!=len(pipeline.commands)-1:
> -                            call_redirs.add(self, '>', outpath)
> -                        
> -                        status = self.execute(cmd, call_redirs)
> -                        
> -                        # Chain inputs/outputs
> -                        inpath, outpath = outpath, inpath
> -                    finally:
> -                        call_redirs.close()            
> -            finally:
> -                if inpath: os.remove(inpath)
> -                if outpath: os.remove(outpath)
> -        
> -        if pipeline.reverse_status:
> -            status = int(not status)
> -        self._env['?'] = status
> -        return status
> -        
> -    def _execute_function(self, name, args, interp, env, stdin,
> stdout, stderr, *others):
> -        assert interp is self
> -        
> -        func = env.get_function(name)
> -        #Set positional parameters
> -        prevargs = None
> -        try:
> -            prevargs = env.set_positional_args(args)
> -            try:
> -                redirs = Redirections(stdin.dup(), stdout.dup(),
> stderr.dup())
> -                try:
> -                    status = self.execute(func, redirs)
> -                finally:
> -                    redirs.close()
> -            except ReturnSignal as e:
> -                status = int(e.args[0])
> -                env['?'] = status
> -            return status
> -        finally:
> -            #Reset positional parameters
> -            if prevargs is not None:
> -                env.set_positional_args(prevargs)
> -                
> -    def _execute_simple_command(self, token, redirs):
> -        """Can raise ReturnSignal when return builtin is called,
> ExitSignal when
> -        exit is called, and other shell exceptions upon builtin
> failures.
> -        """
> -        debug_command = 'debug-cmd' in self._debugflags
> -        if debug_command:
> -            self.log('word' + repr(token.words) + '\n')
> -            self.log('assigns' + repr(token.assigns) + '\n')
> -            self.log('redirs' + repr(token.redirs) + '\n')
> -        
> -        is_special = None
> -        env = self._env
> -        
> -        try:
> -            # Word expansion
> -            args = []
> -            for word in token.words:                
> -                args += self.expand_token(word)
> -                if is_special is None and args:
> -                    is_special = env.is_function(args[0]) or \
> -                        (args[0] in self.COMMANDS and
> self.COMMANDS[args[0]].is_special)
> -                        
> -            if debug_command:
> -                self.log('_execute_simple_command' + str(args) +
> '\n')
> -                
> -            if not args:
> -                # Redirections happen is a subshell
> -                redirs = redirs.clone()
> -            elif not is_special:
> -                env = self._env.clone()
> -            
> -            # Redirections
> -            self.redirect(redirs, token.redirs)
> -                
> -            # Variables assignments
> -            res = 0
> -            for type,(k,v) in token.assigns:
> -                status, expanded = self.expand_variable((k,v))
> -                if status is not None:
> -                    res = status
> -                if args:
> -                    env.export(k, expanded)
> -                else:
> -                    env[k] = expanded
> -                
> -            if args and args[0] in ('.', 'source'):
> -                res = self.dotcommand(env, args[1:])
> -            elif args:
> -                if args[0] in self.COMMANDS:
> -                    command = self.COMMANDS[args[0]]
> -                elif env.is_function(args[0]):
> -                    command = Utility(self._execute_function,
> is_special=True)
> -                else:
> -                    if not '/' in args[0].replace('\\', '/'):
> -                        cmd = env.find_in_path(args[0])
> -                        if not cmd:
> -                            # TODO: test error code on unknown
> command => 127
> -                            raise CommandNotFound('Unknown command:
> "%s"' % args[0])
> -                    else:
> -                        # Handle commands like '/cygdrive/c/foo.bat'
> -                        cmd = cygwin_to_windows_path(args[0])
> -                        if not os.path.exists(cmd):
> -                            raise CommandNotFound('%s: No such file
> or directory' % args[0])
> -                        shebang = resolve_shebang(cmd)
> -                        if shebang:
> -                            cmd = shebang
> -                        else:
> -                            cmd = [cmd]
> -                    args[0:1] = cmd
> -                    command = Utility(builtin.run_command)
> -                
> -                # Command execution
> -                if 'debug-cmd' in self._debugflags:
> -                    self.log('redirections ' + str(redirs) + '\n')
> -                    
> -                res = command.func(args[0], args[1:], self, env,
> -                                   redirs.stdin(), redirs.stdout(), 
> -                                   redirs.stderr(), self._debugflags)
> -            
> -            if self._env.has_opt('-x'):
> -                # Trace command execution in shell environment
> -                # BUG: would be hard to reproduce a real shell
> behaviour since
> -                # the AST is not annotated with source lines/tokens.
> -                self._redirs.stdout().write(' '.join(args))
> -                
> -        except ReturnSignal:
> -            raise
> -        except ShellError as e:
> -            if is_special or isinstance(e, (ExitSignal,
> -                                            ShellSyntaxError,
> ExpansionError)):
> -                raise e
> -            self._redirs.stderr().write(str(e)+'\n')
> -            return 1
> -
> -        return res
> -
> -    def expand_token(self, word):
> -        """Expand a word as specified in [2.6 Word Expansions].
> Return the list
> -        of expanded words.
> -        """
> -        status, wtrees = self._expand_word(word)
> -        return map(pyshlex.wordtree_as_string, wtrees)
> -        
> -    def expand_variable(self, word):
> -        """Return a status code (or None if no command expansion
> occurred)
> -        and a single word.
> -        """
> -        status, wtrees = self._expand_word(word, pathname=False,
> split=False)
> -        words = map(pyshlex.wordtree_as_string, wtrees)
> -        assert len(words)==1
> -        return status, words[0]
> -        
> -    def expand_here_document(self, word):
> -        """Return the expanded document as a single word. The here
> document is 
> -        assumed to be unquoted.
> -        """
> -        status, wtrees = self._expand_word(word, pathname=False,
> -                                           split=False,
> here_document=True)
> -        words = map(pyshlex.wordtree_as_string, wtrees)
> -        assert len(words)==1
> -        return words[0]
> -        
> -    def expand_redirection(self, word):
> -        """Return a single word."""
> -        return self.expand_variable(word)[1]
> -        
> -    def get_env(self):
> -        return self._env
> -        
> -    def _expand_word(self, token, pathname=True, split=True,
> here_document=False):
> -        wtree = pyshlex.make_wordtree(token[1],
> here_document=here_document)
> -        
> -        # TODO: implement tilde expansion
> -        def expand(wtree):
> -            """Return a pseudo wordtree: the tree or its subelements
> can be empty
> -            lists when no value result from the expansion.
> -            """
> -            status = None
> -            for part in wtree:
> -                if not isinstance(part, list):
> -                    continue
> -                if part[0]in ("'", '\\'):
> -                    continue
> -                elif part[0] in ('`', '$('):
> -                    status, result = self._expand_command(part)
> -                    part[:] = result
> -                elif part[0] in ('$', '${'):
> -                    part[:] = self._expand_parameter(part,
> wtree[0]=='"', split)
> -                elif part[0] in ('', '"'):
> -                    status, result = expand(part)
> -                    part[:] = result
> -                else:
> -                    raise NotImplementedError('%s expansion is not
> implemented'
> -                                              % part[0])
> -            # [] is returned when an expansion result in no-field,
> -            # like an empty $@
> -            wtree = [p for p in wtree if p != []]
> -            if len(wtree) < 3:
> -                return status, []
> -            return status, wtree
> -        
> -        status, wtree = expand(wtree)
> -        if len(wtree) == 0:
> -            return status, wtree
> -        wtree = pyshlex.normalize_wordtree(wtree)
> -        
> -        if split:
> -            wtrees = self._split_fields(wtree)
> -        else:
> -            wtrees = [wtree]
> -        
> -        if pathname:
> -            wtrees = mappend(self._expand_pathname, wtrees)
> -        
> -        wtrees = map(self._remove_quotes, wtrees)
> -        return status, wtrees
> -        
> -    def _expand_command(self, wtree):
> -        # BUG: there is something to do with backslashes and quoted
> -        # characters here
> -        command = pyshlex.wordtree_as_string(wtree[1:-1])
> -        status, output = self.subshell_output(command)
> -        return status, ['', output, '']
> -        
> -    def _expand_parameter(self, wtree, quoted=False, split=False):
> -        """Return a valid wtree or an empty list when no parameter
> results."""
> -        # Get the parameter name
> -        # TODO: implement weird expansion rules with ':'
> -        name = pyshlex.wordtree_as_string(wtree[1:-1])
> -        if not is_name(name) and not is_special_param(name):
> -            raise ExpansionError('Bad substitution "%s"' % name)
> -        # TODO: implement special parameters
> -        if name in ('@', '*'):
> -            args = self._env.get_positional_args()
> -            if len(args) == 0:
> -                return []
> -            if len(args)<2:
> -                return ['', ''.join(args), '']
> -                
> -            sep = self._env.get('IFS', '')[:1]
> -            if split and quoted and name=='@':
> -                # Introduce a new token to tell the caller that
> these parameters
> -                # cause a split as specified in 2.5.2
> -                return ['@'] + args + ['']
> -            else:
> -                return ['', sep.join(args), '']                
> -        
> -        return ['', self._env.get(name, ''), '']
> -        
> -    def _split_fields(self, wtree):
> -        def is_empty(split):
> -            return split==['', '', '']
> -            
> -        def split_positional(quoted):
> -            # Return a list of wtree split according positional
> parameters rules.
> -            # All remaining '@' groups are removed.
> -            assert quoted[0]=='"'
> -            
> -            splits = [[]]
> -            for part in quoted:
> -                if not isinstance(part, list) or part[0]!='@':
> -                    splits[-1].append(part)
> -                else:
> -                    # Empty or single argument list were dealt with
> already
> -                    assert len(part)>3
> -                    # First argument must join with the beginning
> part of the original word
> -                    splits[-1].append(part[1])
> -                    # Create double-quotes expressions for every
> argument after the first
> -                    for arg in part[2:-1]:
> -                        splits[-1].append('"')
> -                        splits.append(['"', arg])
> -            return splits
> -        
> -        # At this point, all expansions but pathnames have occured.
> Only quoted
> -        # and positional sequences remain. Thus, all candidates for
> field splitting 
> -        # are in the tree root, or are positional splits ('@') and
> lie in root
> -        # children.
> -        if not wtree or wtree[0] not in ('', '"'):
> -            # The whole token is quoted or empty, nothing to split
> -            return [wtree]
> -            
> -        if wtree[0]=='"':
> -            wtree = ['', wtree, '']
> -        
> -        result = [['', '']]
> -        for part in wtree[1:-1]:
> -            if isinstance(part, list):
> -                if part[0]=='"':
> -                    splits = split_positional(part)
> -                    if len(splits)<=1:
> -                        result[-1] += [part, '']
> -                    else:
> -                        # Terminate the current split
> -                        result[-1] += [splits[0], '']
> -                        result += splits[1:-1]
> -                        # Create a new split
> -                        result += [['', splits[-1], '']]
> -                else:
> -                    result[-1] += [part, '']
> -            else:
> -                splits = self._env.split_fields(part)
> -                if len(splits)<=1:
> -                    # No split
> -                    result[-1][-1] += part
> -                else:
> -                    # Terminate the current resulting part and
> create a new one
> -                    result[-1][-1] += splits[0]
> -                    result[-1].append('')
> -                    result += [['', r, ''] for r in splits[1:-1]]
> -                    result += [['', splits[-1]]]
> -        result[-1].append('')
> -        
> -        # Leading and trailing empty groups come from
> leading/trailing blanks
> -        if result and is_empty(result[-1]):
> -            result[-1:] = []
> -        if result and is_empty(result[0]):
> -            result[:1] = []
> -        return result
> -        
> -    def _expand_pathname(self, wtree):
> -        """See [2.6.6 Pathname Expansion]."""
> -        if self._env.has_opt('-f'):
> -            return [wtree]
> -        
> -        # All expansions have been performed, only quoted sequences
> should remain
> -        # in the tree. Generate the pattern by folding the tree,
> escaping special
> -        # characters when appear quoted
> -        special_chars = '*?[]'
> -        
> -        def make_pattern(wtree):
> -            subpattern = []
> -            for part in wtree[1:-1]:
> -                if isinstance(part, list):
> -                    part = make_pattern(part)
> -                elif wtree[0]!='':
> -                    for c in part:
> -                        # Meta-characters cannot be quoted
> -                        if c in special_chars:
> -                            raise GlobError()
> -                subpattern.append(part)
> -            return ''.join(subpattern)
> -            
> -        def pwd_glob(pattern):
> -            cwd = os.getcwd()
> -            os.chdir(self._env['PWD'])
> -            try:
> -                return glob.glob(pattern) 
> -            finally:
> -                os.chdir(cwd)    
> -            
> -        #TODO: check working directory issues here wrt relative
> patterns
> -        try:
> -            pattern = make_pattern(wtree)
> -            paths = pwd_glob(pattern)
> -        except GlobError:
> -            # BUG: Meta-characters were found in quoted sequences.
> The should 
> -            # have been used literally but this is unsupported in
> current glob module.
> -            # Instead we consider the whole tree must be used
> literally and
> -            # therefore there is no point in globbing. This is wrong
> when meta
> -            # characters are mixed with quoted meta in the same
> pattern like:
> -            # < foo*"py*" >
> -            paths = []
> -            
> -        if not paths:
> -            return [wtree]
> -        return [['', path, ''] for path in paths]
> -        
> -    def _remove_quotes(self, wtree):
> -        """See [2.6.7 Quote Removal]."""
> -        
> -        def unquote(wtree):
> -            unquoted = []
> -            for part in wtree[1:-1]:
> -                if isinstance(part, list):
> -                    part = unquote(part)
> -                unquoted.append(part)
> -            return ''.join(unquoted)
> -            
> -        return ['', unquote(wtree), '']
> -        
> -    def subshell(self, script=None, ast=None, redirs=None):
> -        """Execute the script or AST in a subshell, with inherited
> redirections
> -        if redirs is not None.
> -        """
> -        if redirs:
> -            sub_redirs = redirs
> -        else:
> -            sub_redirs = redirs.clone()
> -        
> -        subshell = None    
> -        try:
> -            subshell = Interpreter(None, self._debugflags,
> self._env.clone(True),
> -                                   sub_redirs, opts=self._options)
> -            return subshell.execute_script(script, ast)
> -        finally:
> -            if not redirs: sub_redirs.close()
> -            if subshell: subshell.close()
> -        
> -    def subshell_output(self, script):
> -        """Execute the script in a subshell and return the captured
> output."""        
> -        # Create temporary file to capture subshell output
> -        tmpfd, tmppath = tempfile.mkstemp()
> -        try:
> -            tmpfile = os.fdopen(tmpfd, 'wb')
> -            stdout = FileWrapper('w', tmpfile)
> -            
> -            redirs = Redirections(self._redirs.stdin().dup(),
> -                                  stdout,
> -
> self._redirs.stderr().dup())            
> -            try:
> -                status = self.subshell(script=script, redirs=redirs)
> -            finally:
> -                redirs.close()
> -                redirs = None
> -            
> -            # Extract subshell standard output
> -            tmpfile = open(tmppath, 'rb')
> -            try:
> -                output = tmpfile.read()
> -                return status, output.rstrip('\n')
> -            finally:
> -                tmpfile.close()
> -        finally:
> -            os.remove(tmppath)
> -
> -    def _asynclist(self, cmd):
> -        args = (self._env.get_variables(), cmd)
> -        arg = encodeargs(args)
> -        assert len(args) < 30*1024
> -        cmd = ['pysh.bat', '--ast', '-c', arg]
> -        p = subprocess.Popen(cmd, cwd=self._env['PWD'])
> -        self._children[p.pid] = p
> -        self._env['!'] = p.pid
> -        return 0
> -
> -    def wait(self, pids=None):
> -        if not pids:
> -            pids = self._children.keys()
> -
> -        status = 127
> -        for pid in pids:
> -            if pid not in self._children:
> -                continue
> -            p = self._children.pop(pid)
> -            status = p.wait()
> -
> -        return status
> -
> diff --git a/bitbake/lib/bb/pysh/lsprof.py
> b/bitbake/lib/bb/pysh/lsprof.py deleted file mode 100644
> index b1831c2..0000000
> --- a/bitbake/lib/bb/pysh/lsprof.py
> +++ /dev/null
> @@ -1,116 +0,0 @@
> -#! /usr/bin/env python
> -
> -import sys
> -from _lsprof import Profiler, profiler_entry
> -
> -__all__ = ['profile', 'Stats']
> -
> -def profile(f, *args, **kwds):
> -    """XXX docstring"""
> -    p = Profiler()
> -    p.enable(subcalls=True, builtins=True)
> -    try:
> -        f(*args, **kwds)
> -    finally:
> -        p.disable()
> -    return Stats(p.getstats())
> -
> -
> -class Stats(object):
> -    """XXX docstring"""
> -
> -    def __init__(self, data):
> -        self.data = data
> -
> -    def sort(self, crit="inlinetime"):
> -        """XXX docstring"""
> -        if crit not in profiler_entry.__dict__:
> -            raise ValueError("Can't sort by %s" % crit)
> -        self.data.sort(lambda b, a: cmp(getattr(a, crit),
> -                                        getattr(b, crit)))
> -        for e in self.data:
> -            if e.calls:
> -                e.calls.sort(lambda b, a: cmp(getattr(a, crit),
> -                                              getattr(b, crit)))
> -
> -    def pprint(self, top=None, file=None, limit=None, climit=None):
> -        """XXX docstring"""
> -        if file is None:
> -            file = sys.stdout
> -        d = self.data
> -        if top is not None:
> -            d = d[:top]
> -        cols = "% 12s %12s %11.4f %11.4f   %s\n"
> -        hcols = "% 12s %12s %12s %12s %s\n"
> -        cols2 = "+%12s %12s %11.4f %11.4f +  %s\n"
> -        file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
> -                            "Inline(ms)", "module:lineno(function)"))
> -        count = 0
> -        for e in d:
> -            file.write(cols % (e.callcount, e.reccallcount,
> e.totaltime,
> -                               e.inlinetime, label(e.code)))
> -            count += 1
> -            if limit is not None and count == limit:
> -                return
> -            ccount = 0
> -            if e.calls:
> -                for se in e.calls:
> -                    file.write(cols % ("+%s" % se.callcount,
> se.reccallcount,
> -                                       se.totaltime, se.inlinetime,
> -                                       "+%s" % label(se.code)))
> -                    count += 1
> -                    ccount += 1
> -                    if limit is not None and count == limit:
> -                        return
> -                    if climit is not None and ccount == climit:
> -                        break
> -
> -    def freeze(self):
> -        """Replace all references to code objects with string
> -        descriptions; this makes it possible to pickle the
> instance.""" -
> -        # this code is probably rather ickier than it needs to be!
> -        for i in range(len(self.data)):
> -            e = self.data[i]
> -            if not isinstance(e.code, str):
> -                self.data[i] = type(e)((label(e.code),) + e[1:])
> -            if e.calls:
> -                for j in range(len(e.calls)):
> -                    se = e.calls[j]
> -                    if not isinstance(se.code, str):
> -                        e.calls[j] = type(se)((label(se.code),) +
> se[1:]) -
> -_fn2mod = {}
> -
> -def label(code):
> -    if isinstance(code, str):
> -        return code
> -    try:
> -        mname = _fn2mod[code.co_filename]
> -    except KeyError:
> -        for k, v in sys.modules.items():
> -            if v is None:
> -                continue
> -            if not hasattr(v, '__file__'):
> -                continue
> -            if not isinstance(v.__file__, str):
> -                continue
> -            if v.__file__.startswith(code.co_filename):
> -                mname = _fn2mod[code.co_filename] = k
> -                break
> -        else:
> -            mname = _fn2mod[code.co_filename] =
> '<%s>'%code.co_filename -
> -    return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
> -
> -
> -if __name__ == '__main__':
> -    import os
> -    sys.argv = sys.argv[1:]
> -    if not sys.argv:
> -        print >> sys.stderr, "usage: lsprof.py <script>
> <arguments...>"
> -        sys.exit(2)
> -    sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
> -    stats = profile(execfile, sys.argv[0], globals(), locals())
> -    stats.sort()
> -    stats.pprint()
> diff --git a/bitbake/lib/bb/pysh/pysh.py b/bitbake/lib/bb/pysh/pysh.py
> deleted file mode 100644
> index b4e6145..0000000
> --- a/bitbake/lib/bb/pysh/pysh.py
> +++ /dev/null
> @@ -1,167 +0,0 @@
> -# pysh.py - command processing for pysh.
> -#
> -# Copyright 2007 Patrick Mezard
> -#
> -# This software may be used and distributed according to the terms
> -# of the GNU General Public License, incorporated herein by
> reference. -
> -import optparse
> -import os
> -import sys
> -
> -import interp
> -
> -SH_OPT = optparse.OptionParser(prog='pysh', usage="%prog [OPTIONS]",
> version='0.1') -SH_OPT.add_option('-c', action='store_true',
> dest='command_string', default=None, 
> -    help='A string that shall be interpreted by the shell as one or
> more commands') -SH_OPT.add_option('--redirect-to',
> dest='redirect_to', default=None, 
> -    help='Redirect script commands stdout and stderr to the
> specified file') -# See utility_command in builtin.py about the
> reason for this flag. -SH_OPT.add_option('--redirected',
> dest='redirected', action='store_true', default=False, 
> -    help='Tell the interpreter that stdout and stderr are actually
> the same objects, which is really stdout')
> -SH_OPT.add_option('--debug-parsing', action='store_true',
> dest='debug_parsing', default=False, 
> -    help='Trace PLY execution')
> -SH_OPT.add_option('--debug-tree', action='store_true',
> dest='debug_tree', default=False, 
> -    help='Display the generated syntax tree.')
> -SH_OPT.add_option('--debug-cmd', action='store_true',
> dest='debug_cmd', default=False, 
> -    help='Trace command execution before parameters expansion and
> exit status.') -SH_OPT.add_option('--debug-utility',
> action='store_true', dest='debug_utility', default=False, 
> -    help='Trace utility calls, after parameters expansions')
> -SH_OPT.add_option('--ast', action='store_true', dest='ast',
> default=False,
> -    help='Encoded commands to execute in a subprocess')
> -SH_OPT.add_option('--profile', action='store_true', default=False,
> -    help='Profile pysh run')
> -    
> -    
> -def split_args(args):
> -    # Separate shell arguments from command ones
> -    # Just stop at the first argument not starting with a dash. I
> know, this is completely broken,
> -    # it ignores files starting with a dash or may take option
> values for command file. This is not
> -    # supposed to happen for now
> -    command_index = len(args)
> -    for i,arg in enumerate(args):
> -        if not arg.startswith('-'):
> -            command_index = i
> -            break
> -            
> -    return args[:command_index], args[command_index:]
> -
> -
> -def fixenv(env):
> -    path = env.get('PATH')
> -    if path is not None:
> -        parts = path.split(os.pathsep)
> -        # Remove Windows utilities from PATH, they are useless at
> best and
> -        # some of them (find) may be confused with other utilities.
> -        parts = [p for p in parts if 'system32' not in p.lower()]
> -        env['PATH'] = os.pathsep.join(parts)
> -    if env.get('HOME') is None:
> -        # Several utilities, including cvsps, cannot work without
> -        # a defined HOME directory.
> -        env['HOME'] = os.path.expanduser('~')
> -    return env
> -
> -def _sh(cwd, shargs, cmdargs, options, debugflags=None, env=None):
> -    if os.environ.get('PYSH_TEXT') != '1':
> -        import msvcrt
> -        for fp in (sys.stdin, sys.stdout, sys.stderr):
> -            msvcrt.setmode(fp.fileno(), os.O_BINARY)
> -
> -    hgbin = os.environ.get('PYSH_HGTEXT') != '1'
> -    
> -    if debugflags is None:
> -        debugflags = []
> -        if options.debug_parsing:
> debugflags.append('debug-parsing')
> -        if options.debug_utility:
> debugflags.append('debug-utility')
> -        if options.debug_cmd:        debugflags.append('debug-cmd')
> -        if options.debug_tree:       debugflags.append('debug-tree')
> -    
> -    if env is None:
> -        env = fixenv(dict(os.environ))
> -    if cwd is None:
> -        cwd = os.getcwd()
> -
> -    if not cmdargs:
> -        # Nothing to do
> -        return 0
> -
> -    ast = None
> -    command_file = None
> -    if options.command_string:
> -        input = cmdargs[0]
> -        if not options.ast:
> -            input += '\n'
> -        else:
> -            args, input = interp.decodeargs(input), None
> -            env, ast = args
> -            cwd = env.get('PWD', cwd)
> -    else:
> -        command_file = cmdargs[0]
> -        arguments = cmdargs[1:]
> -
> -        prefix = interp.resolve_shebang(command_file,
> ignoreshell=True)
> -        if prefix:
> -            input = ' '.join(prefix + [command_file] + arguments)
> -        else:
> -            # Read commands from file
> -            f = file(command_file)
> -            try:
> -                # Trailing newline to help the parser
> -                input = f.read() + '\n'
> -            finally:
> -                f.close()
> -    
> -    redirect = None
> -    try:
> -        if options.redirected:
> -            stdout = sys.stdout
> -            stderr = stdout
> -        elif options.redirect_to:
> -            redirect = open(options.redirect_to, 'wb')
> -            stdout = redirect
> -            stderr = redirect
> -        else:
> -            stdout = sys.stdout
> -            stderr = sys.stderr
> -            
> -        # TODO: set arguments to environment variables
> -        opts = interp.Options()
> -        opts.hgbinary = hgbin
> -        ip = interp.Interpreter(cwd, debugflags, stdout=stdout,
> stderr=stderr,
> -                                opts=opts)
> -        try:
> -            # Export given environment in shell object
> -            for k,v in env.iteritems():
> -                ip.get_env().export(k,v)
> -            return ip.execute_script(input, ast,
> scriptpath=command_file)
> -        finally:
> -            ip.close()
> -    finally:
> -        if redirect is not None:
> -            redirect.close()
> -
> -def sh(cwd=None, args=None, debugflags=None, env=None):
> -    if args is None:
> -        args = sys.argv[1:]
> -    shargs, cmdargs = split_args(args)
> -    options, shargs = SH_OPT.parse_args(shargs)
> -
> -    if options.profile:
> -        import lsprof
> -        p = lsprof.Profiler()
> -        p.enable(subcalls=True)
> -        try:
> -            return _sh(cwd, shargs, cmdargs, options, debugflags,
> env)
> -        finally:
> -            p.disable()
> -            stats = lsprof.Stats(p.getstats())
> -            stats.sort()
> -            stats.pprint(top=10, file=sys.stderr, climit=5)
> -    else:
> -        return _sh(cwd, shargs, cmdargs, options, debugflags, env)
> -            
> -def main():
> -    sys.exit(sh())
> -
> -if __name__=='__main__':
> -    main()
> diff --git a/bitbake/lib/bb/pysh/pyshlex.py
> b/bitbake/lib/bb/pysh/pyshlex.py index fbf094b..a42c294 100644
> --- a/bitbake/lib/bb/pysh/pyshlex.py
> +++ b/bitbake/lib/bb/pysh/pyshlex.py
> @@ -13,11 +13,6 @@
>  # PLY in pull mode. It was designed to work incrementally and it
> would not be # that hard to enable pull mode.
>  import re
> -try:
> -    s = set()
> -    del s
> -except NameError:
> -    from Set import Set as set
>  
>  from ply import lex
>  from bb.pysh.sherrors import *
> diff --git a/bitbake/lib/bb/pysh/pyshyacc.py
> b/bitbake/lib/bb/pysh/pyshyacc.py index ba4cefd..de565dc 100644
> --- a/bitbake/lib/bb/pysh/pyshyacc.py
> +++ b/bitbake/lib/bb/pysh/pyshyacc.py
> @@ -636,13 +636,16 @@ def p_empty(p):
>  def p_error(p):
>      msg = []
>      w = msg.append
> -    w('%r\n' % p)
> -    w('followed by:\n')
> -    for i in range(5):
> -        n = yacc.token()
> -        if not n:
> -            break
> -        w('  %r\n' % n)
> +    if p:
> +        w('%r\n' % p)
> +        w('followed by:\n')
> +        for i in range(5):
> +            n = yacc.token()
> +            if not n:
> +                break
> +            w('  %r\n' % n)
> +    else:
> +        w('Unexpected EOF')
>      raise sherrors.ShellSyntaxError(''.join(msg))
>  
>  # Build the parser
> diff --git a/bitbake/lib/bb/pysh/sherrors.py
> b/bitbake/lib/bb/pysh/sherrors.py index 49d0533..3fe8e47 100644
> --- a/bitbake/lib/bb/pysh/sherrors.py
> +++ b/bitbake/lib/bb/pysh/sherrors.py
> @@ -13,29 +13,3 @@ class ShellError(Exception):
>  
>  class ShellSyntaxError(ShellError):
>      pass
> -    
> -class UtilityError(ShellError):
> -    """Raised upon utility syntax error (option or operand error)."""
> -    pass
> -   
> -class ExpansionError(ShellError):
> -    pass
> -     
> -class CommandNotFound(ShellError):
> -    """Specified command was not found."""
> -    pass
> -    
> -class RedirectionError(ShellError):
> -    pass
> -    
> -class VarAssignmentError(ShellError):
> -    """Variable assignment error."""
> -    pass
> -    
> -class ExitSignal(ShellError):
> -    """Exit signal."""
> -    pass
> -    
> -class ReturnSignal(ShellError):
> -    """Exit signal."""
> -    pass
> diff --git a/bitbake/lib/bb/pysh/subprocess_fix.py
> b/bitbake/lib/bb/pysh/subprocess_fix.py deleted file mode 100644
> index 46eca22..0000000
> --- a/bitbake/lib/bb/pysh/subprocess_fix.py
> +++ /dev/null
> @@ -1,77 +0,0 @@
> -# subprocess - Subprocesses with accessible I/O streams
> -#
> -# For more information about this module, see PEP 324.
> -#
> -# This module should remain compatible with Python 2.2, see PEP 291.
> -#
> -# Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se>
> -#
> -# Licensed to PSF under a Contributor Agreement.
> -# See http://www.python.org/2.4/license for licensing details.
> -
> -def list2cmdline(seq):
> -    """
> -    Translate a sequence of arguments into a command line
> -    string, using the same rules as the MS C runtime:
> -
> -    1) Arguments are delimited by white space, which is either a
> -       space or a tab.
> -
> -    2) A string surrounded by double quotation marks is
> -       interpreted as a single argument, regardless of white space
> -       contained within.  A quoted string can be embedded in an
> -       argument.
> -
> -    3) A double quotation mark preceded by a backslash is
> -       interpreted as a literal double quotation mark.
> -
> -    4) Backslashes are interpreted literally, unless they
> -       immediately precede a double quotation mark.
> -
> -    5) If backslashes immediately precede a double quotation mark,
> -       every pair of backslashes is interpreted as a literal
> -       backslash.  If the number of backslashes is odd, the last
> -       backslash escapes the next double quotation mark as
> -       described in rule 3.
> -    """
> -
> -    # See
> -    #
> http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp
> -    result = []
> -    needquote = False
> -    for arg in seq:
> -        bs_buf = []
> -
> -        # Add a space to separate this argument from the others
> -        if result:
> -            result.append(' ')
> -
> -        needquote = (" " in arg) or ("\t" in arg) or ("|" in arg) or
> arg == ""
> -        if needquote:
> -            result.append('"')
> -
> -        for c in arg:
> -            if c == '\\':
> -                # Don't know if we need to double yet.
> -                bs_buf.append(c)
> -            elif c == '"':
> -                # Double backspaces.
> -                result.append('\\' * len(bs_buf)*2)
> -                bs_buf = []
> -                result.append('\\"')
> -            else:
> -                # Normal char
> -                if bs_buf:
> -                    result.extend(bs_buf)
> -                    bs_buf = []
> -                result.append(c)
> -
> -        # Add remaining backspaces, if any.
> -        if bs_buf:
> -            result.extend(bs_buf)
> -
> -        if needquote:
> -            result.extend(bs_buf)
> -            result.append('"')
> -            
> -    return ''.join(result)
> diff --git a/bitbake/lib/bb/remotedata.py
> b/bitbake/lib/bb/remotedata.py index 68ecffc..7391e1b 100644
> --- a/bitbake/lib/bb/remotedata.py
> +++ b/bitbake/lib/bb/remotedata.py
> @@ -6,18 +6,8 @@ Provides support for using a datastore from the
> bitbake client 
>  # Copyright (C) 2016  Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import bb.data
>  
> diff --git a/bitbake/lib/bb/runqueue.py b/bitbake/lib/bb/runqueue.py
> index 9ce06c4..1804943 100644
> --- a/bitbake/lib/bb/runqueue.py
> +++ b/bitbake/lib/bb/runqueue.py
> @@ -1,6 +1,3 @@
> -#!/usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'RunQueue' implementation
>  
> @@ -9,18 +6,8 @@ Handles preparation and execution of a queue of tasks
>  
>  # Copyright (C) 2006-2007  Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import copy
>  import os
> @@ -37,11 +24,13 @@ from bb import monitordisk
>  import subprocess
>  import pickle
>  from multiprocessing import Process
> +import shlex
> +import pprint
>  
>  bblogger = logging.getLogger("BitBake")
>  logger = logging.getLogger("BitBake.RunQueue")
>  
> -__find_md5__ =
> re.compile( r'(?i)(?<![a-z0-9])[a-f0-9]{32}(?![a-z0-9])' )
> +__find_sha256__ =
> re.compile( r'(?i)(?<![a-z0-9])[a-f0-9]{64}(?![a-z0-9])' ) def
> fn_from_tid(tid): return tid.rsplit(":", 1)[0]
> @@ -49,17 +38,22 @@ def fn_from_tid(tid):
>  def taskname_from_tid(tid):
>      return tid.rsplit(":", 1)[1]
>  
> +def mc_from_tid(tid):
> +    if tid.startswith('mc:'):
> +        return tid.split(':')[1]
> +    return ""
> +
>  def split_tid(tid):
>      (mc, fn, taskname, _) = split_tid_mcfn(tid)
>      return (mc, fn, taskname)
>  
>  def split_tid_mcfn(tid):
> -    if tid.startswith('multiconfig:'):
> +    if tid.startswith('mc:'):
>          elems = tid.split(':')
>          mc = elems[1]
>          fn = ":".join(elems[2:-1])
>          taskname = elems[-1]
> -        mcfn = "multiconfig:" + mc + ":" + fn
> +        mcfn = "mc:" + mc + ":" + fn
>      else:
>          tid = tid.rsplit(":", 1)
>          mc = ""
> @@ -71,9 +65,17 @@ def split_tid_mcfn(tid):
>  
>  def build_tid(mc, fn, taskname):
>      if mc:
> -        return "multiconfig:" + mc + ":" + fn + ":" + taskname
> +        return "mc:" + mc + ":" + fn + ":" + taskname
>      return fn + ":" + taskname
>  
> +# Index used to pair up potentially matching multiconfig tasks
> +# We match on PN, taskname and hash being equal
> +def pending_hash_index(tid, rqdata):
> +    (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> +    pn = rqdata.dataCaches[mc].pkg_fn[taskfn]
> +    h = rqdata.runtaskentries[tid].unihash
> +    return pn + ":" + "taskname" + h
> +
>  class RunQueueStats:
>      """
>      Holds statistics on the tasks handled by the associated runQueue
> @@ -109,8 +111,6 @@ class RunQueueStats:
>  # runQueue state machine
>  runQueuePrepare = 2
>  runQueueSceneInit = 3
> -runQueueSceneRun = 4
> -runQueueRunInit = 5
>  runQueueRunning = 6
>  runQueueFailed = 7
>  runQueueCleanUp = 8
> @@ -133,7 +133,7 @@ class RunQueueScheduler(object):
>  
>          self.prio_map = [self.rqdata.runtaskentries.keys()]
>  
> -        self.buildable = []
> +        self.buildable = set()
>          self.skip_maxthread = {}
>          self.stamps = {}
>          for tid in self.rqdata.runtaskentries:
> @@ -148,8 +148,11 @@ class RunQueueScheduler(object):
>          """
>          Return the id of the first task we find that is buildable
>          """
> -        self.buildable = [x for x in self.buildable if x not in
> self.rq.runq_running]
> -        if not self.buildable:
> +        buildable = set(self.buildable)
> +        buildable.difference_update(self.rq.runq_running)
> +        buildable.difference_update(self.rq.holdoff_tasks)
> +        buildable.intersection_update(self.rq.tasks_covered |
> self.rq.tasks_notcovered)
> +        if not buildable:
>              return None
>  
>          # Filter out tasks that have a max number of threads that
> have been exceeded @@ -165,8 +168,8 @@ class
> RunQueueScheduler(object): else:
>                  skip_buildable[rtaskname] = 1
>  
> -        if len(self.buildable) == 1:
> -            tid = self.buildable[0]
> +        if len(buildable) == 1:
> +            tid = buildable.pop()
>              taskname = taskname_from_tid(tid)
>              if taskname in skip_buildable and
> skip_buildable[taskname] >= int(self.skip_maxthread[taskname]):
> return None @@ -181,7 +184,7 @@ class RunQueueScheduler(object):
>  
>          best = None
>          bestprio = None
> -        for tid in self.buildable:
> +        for tid in buildable:
>              taskname = taskname_from_tid(tid)
>              if taskname in skip_buildable and
> skip_buildable[taskname] >= int(self.skip_maxthread[taskname]):
> continue @@ -203,7 +206,12 @@ class RunQueueScheduler(object):
>              return self.next_buildable_task()
>  
>      def newbuildable(self, task):
> -        self.buildable.append(task)
> +        self.buildable.add(task)
> +        # Once tasks are running we don't need to worry about them
> again
> +        self.buildable.difference_update(self.rq.runq_running)
> +
> +    def removebuildable(self, task):
> +        self.buildable.remove(task)
>  
>      def describe_task(self, taskid):
>          result = 'ID %s' % taskid
> @@ -346,6 +354,7 @@ class RunTaskEntry(object):
>          self.depends = set()
>          self.revdeps = set()
>          self.hash = None
> +        self.unihash = None
>          self.task = None
>          self.weight = 1
>  
> @@ -385,6 +394,9 @@ class RunQueueData:
>      def get_task_hash(self, tid):
>          return self.runtaskentries[tid].hash
>  
> +    def get_task_unihash(self, tid):
> +        return self.runtaskentries[tid].unihash
> +
>      def get_user_idstring(self, tid, task_name_suffix = ""):
>          return tid + task_name_suffix
>  
> @@ -405,6 +417,9 @@ class RunQueueData:
>          explored_deps = {}
>          msgs = []
>  
> +        class TooManyLoops(Exception):
> +            pass
> +
>          def chain_reorder(chain):
>              """
>              Reorder a dependency chain so the lowest task id is first
> @@ -457,7 +472,7 @@ class RunQueueData:
>                          msgs.append("\n")
>                      if len(valid_chains) > 10:
>                          msgs.append("Aborted dependency loops search
> after 10 matches.\n")
> -                        return msgs
> +                        raise TooManyLoops
>                      continue
>                  scan = False
>                  if revdep not in explored_deps:
> @@ -476,8 +491,11 @@ class RunQueueData:
>  
>              explored_deps[tid] = total_deps
>  
> -        for task in tasks:
> -            find_chains(task, [])
> +        try:
> +            for task in tasks:
> +                find_chains(task, [])
> +        except TooManyLoops:
> +            pass
>  
>          return msgs
>  
> @@ -833,6 +851,20 @@ class RunQueueData:
>              for depend in depends:
>                  mark_active(depend, depth+1)
>  
> +        def invalidate_task(tid, error_nostamp):
> +            (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> +            taskdep = self.dataCaches[mc].task_deps[taskfn]
> +            if fn + ":" + taskname not in taskData[mc].taskentries:
> +                logger.warning("Task %s does not exist, invalidating
> this task will have no effect" % taskname)
> +            if 'nostamp' in taskdep and taskname in
> taskdep['nostamp']:
> +                if error_nostamp:
> +                    bb.fatal("Task %s is marked nostamp, cannot
> invalidate this task" % taskname)
> +                else:
> +                    bb.debug(1, "Task %s is marked nostamp, cannot
> invalidate this task" % taskname)
> +            else:
> +                logger.verbose("Invalidate task %s, %s", taskname,
> fn)
> +                bb.parse.siggen.invalidate_task(taskname,
> self.dataCaches[mc], taskfn) +
>          self.target_tids = []
>          for (mc, target, task, fn) in self.targets:
>  
> @@ -901,6 +933,8 @@ class RunQueueData:
>  
>                  for tid in list(runall_tids):
>                      mark_active(tid,1)
> +                    if self.cooker.configuration.force:
> +                        invalidate_task(tid, False)
>  
>              for tid in list(self.runtaskentries.keys()):
>                  if tid not in runq_build:
> @@ -922,6 +956,8 @@ class RunQueueData:
>  
>                  for tid in list(runonly_tids):
>                      mark_active(tid,1)
> +                    if self.cooker.configuration.force:
> +                        invalidate_task(tid, False)
>  
>              for tid in list(self.runtaskentries.keys()):
>                  if tid not in runq_build:
> @@ -1098,20 +1134,6 @@ class RunQueueData:
>                      continue
>                  self.runq_setscene_tids.append(tid)
>  
> -        def invalidate_task(tid, error_nostamp):
> -            (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> -            taskdep = self.dataCaches[mc].task_deps[taskfn]
> -            if fn + ":" + taskname not in taskData[mc].taskentries:
> -                logger.warning("Task %s does not exist, invalidating
> this task will have no effect" % taskname)
> -            if 'nostamp' in taskdep and taskname in
> taskdep['nostamp']:
> -                if error_nostamp:
> -                    bb.fatal("Task %s is marked nostamp, cannot
> invalidate this task" % taskname)
> -                else:
> -                    bb.debug(1, "Task %s is marked nostamp, cannot
> invalidate this task" % taskname)
> -            else:
> -                logger.verbose("Invalidate task %s, %s", taskname,
> fn)
> -                bb.parse.siggen.invalidate_task(taskname,
> self.dataCaches[mc], taskfn) -
>          self.init_progress_reporter.next_stage()
>  
>          # Invalidate task if force mode active
> @@ -1142,6 +1164,8 @@ class RunQueueData:
>  
>          self.init_progress_reporter.next_stage()
>  
> +        bb.parse.siggen.set_setscene_tasks(self.runq_setscene_tids)
> +
>          # Iterate over the task list and call into the siggen code
>          dealtwith = set()
>          todeal = set(self.runtaskentries)
> @@ -1150,18 +1174,20 @@ class RunQueueData:
>                  if len(self.runtaskentries[tid].depends - dealtwith)
> == 0: dealtwith.add(tid)
>                      todeal.remove(tid)
> -                    procdep = []
> -                    for dep in self.runtaskentries[tid].depends:
> -                        procdep.append(fn_from_tid(dep) + "." +
> taskname_from_tid(dep))
> -                    (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> -                    self.runtaskentries[tid].hash =
> bb.parse.siggen.get_taskhash(taskfn, taskname, procdep,
> self.dataCaches[mc])
> -                    task = self.runtaskentries[tid].task
> +                    self.prepare_task_hash(tid)
>  
>          bb.parse.siggen.writeout_file_checksum_cache()
>  
>          #self.dump_data()
>          return len(self.runtaskentries)
>  
> +    def prepare_task_hash(self, tid):
> +        procdep = []
> +        for dep in self.runtaskentries[tid].depends:
> +            procdep.append(dep)
> +        self.runtaskentries[tid].hash =
> bb.parse.siggen.get_taskhash(tid, procdep,
> self.dataCaches[mc_from_tid(tid)])
> +        self.runtaskentries[tid].unihash =
> bb.parse.siggen.get_unihash(tid) +
>      def dump_data(self):
>          """
>          Dump some debug information on the internal data structures
> @@ -1187,7 +1213,6 @@ class RunQueue:
>  
>          self.stamppolicy = cfgData.getVar("BB_STAMP_POLICY") or
> "perfile" self.hashvalidate = cfgData.getVar("BB_HASHCHECK_FUNCTION")
> or None
> -        self.setsceneverify =
> cfgData.getVar("BB_SETSCENE_VERIFY_FUNCTION2") or None
> self.depvalidate = cfgData.getVar("BB_SETSCENE_DEPVALID") or None 
>          self.state = runQueuePrepare
> @@ -1196,7 +1221,7 @@ class RunQueue:
>          # Invoked at regular time intervals via the bitbake
> heartbeat event # while the build is running. We generate a unique
> name for the handler # here, just in case that there ever is more
> than one RunQueue instance,
> -        # start the handler when reaching runQueueSceneRun, and stop
> it when
> +        # start the handler when reaching runQueueSceneInit, and
> stop it when # done with the build.
>          self.dm = monitordisk.diskMonitor(cfgData)
>          self.dm_event_handler_name = '_bb_diskmonitor_' +
> str(id(self)) @@ -1213,28 +1238,23 @@ class RunQueue:
>          if fakeroot:
>              magic = magic + "beef"
>              mcdata = self.cooker.databuilder.mcdata[mc]
> -            fakerootcmd = mcdata.getVar("FAKEROOTCMD")
> +            fakerootcmd = shlex.split(mcdata.getVar("FAKEROOTCMD"))
>              fakerootenv = (mcdata.getVar("FAKEROOTBASEENV") or
> "").split() env = os.environ.copy()
>              for key, value in (var.split('=') for var in
> fakerootenv): env[key] = value
> -            worker = subprocess.Popen([fakerootcmd,
> "bitbake-worker", magic], stdout=subprocess.PIPE,
> stdin=subprocess.PIPE, env=env)
> +            worker = subprocess.Popen(fakerootcmd +
> ["bitbake-worker", magic], stdout=subprocess.PIPE,
> stdin=subprocess.PIPE, env=env) else: worker =
> subprocess.Popen(["bitbake-worker", magic], stdout=subprocess.PIPE,
> stdin=subprocess.PIPE) bb.utils.nonblockingfd(worker.stdout)
> workerpipe = runQueuePipe(worker.stdout, None, self.cfgData, self,
> rqexec) 
> -        runqhash = {}
> -        for tid in self.rqdata.runtaskentries:
> -            runqhash[tid] = self.rqdata.runtaskentries[tid].hash
> -
>          workerdata = {
>              "taskdeps" : self.rqdata.dataCaches[mc].task_deps,
>              "fakerootenv" : self.rqdata.dataCaches[mc].fakerootenv,
>              "fakerootdirs" : self.rqdata.dataCaches[mc].fakerootdirs,
>              "fakerootnoenv" :
> self.rqdata.dataCaches[mc].fakerootnoenv, "sigdata" :
> bb.parse.siggen.get_taskdata(),
> -            "runq_hash" : runqhash,
>              "logdefaultdebug" : bb.msg.loggerDefaultDebugLevel,
>              "logdefaultverbose" : bb.msg.loggerDefaultVerbose,
>              "logdefaultverboselogs" : bb.msg.loggerVerboseLogs,
> @@ -1243,6 +1263,7 @@ class RunQueue:
>              "buildname" : self.cfgData.getVar("BUILDNAME"),
>              "date" : self.cfgData.getVar("DATE"),
>              "time" : self.cfgData.getVar("TIME"),
> +            "hashservaddr" : self.cooker.hashservaddr,
>          }
>  
>          worker.stdin.write(b"<cookerconfig>" +
> pickle.dumps(self.cooker.configuration) + b"</cookerconfig>") @@
> -1376,6 +1397,31 @@ class RunQueue: cache[tid] = iscurrent
>          return iscurrent
>  
> +    def validate_hashes(self, tocheck, data, currentcount=0,
> siginfo=False):
> +        valid = set()
> +        if self.hashvalidate:
> +            sq_data = {}
> +            sq_data['hash'] = {}
> +            sq_data['hashfn'] = {}
> +            sq_data['unihash'] = {}
> +            for tid in tocheck:
> +                (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> +                sq_data['hash'][tid] =
> self.rqdata.runtaskentries[tid].hash
> +                sq_data['hashfn'][tid] =
> self.rqdata.dataCaches[mc].hashfn[taskfn]
> +                sq_data['unihash'][tid] =
> self.rqdata.runtaskentries[tid].unihash +
> +            valid = self.validate_hash(sq_data, data, siginfo,
> currentcount) +
> +        return valid
> +
> +    def validate_hash(self, sq_data, d, siginfo, currentcount):
> +        locs = {"sq_data" : sq_data, "d" : d, "siginfo" : siginfo,
> "currentcount" : currentcount} +
> +        # Metadata has **kwargs so args can be added, sq_data can
> also gain new fields
> +        call = self.hashvalidate + "(sq_data, d, siginfo=siginfo,
> currentcount=currentcount)" +
> +        return bb.utils.better_eval(call, locs)
> +
>      def _execute_runqueue(self):
>          """
>          Run the tasks in a queue prepared by rqdata.prepare()
> @@ -1386,7 +1432,6 @@ class RunQueue:
>          retval = True
>  
>          if self.state is runQueuePrepare:
> -            self.rqexe = RunQueueExecuteDummy(self)
>              # NOTE: if you add, remove or significantly refactor the
> stages of this # process then you should recalculate the weightings
> here. This is quite # easy to do - just change the next line
> temporarily to pass debug=True as @@ -1400,15 +1445,23 @@ class
> RunQueue: self.state = runQueueComplete
>              else:
>                  self.state = runQueueSceneInit
> -                self.rqdata.init_progress_reporter.next_stage()
> -
> -                # we are ready to run,  emit dependency info to any
> UI or class which
> -                # needs it
> -                depgraph = self.cooker.buildDependTree(self,
> self.rqdata.taskData)
> -                self.rqdata.init_progress_reporter.next_stage()
> -                bb.event.fire(bb.event.DepTreeGenerated(depgraph),
> self.cooker.data)
> +                bb.parse.siggen.save_unitaskhashes()
>  
>          if self.state is runQueueSceneInit:
> +            self.rqdata.init_progress_reporter.next_stage()
> +
> +            # we are ready to run,  emit dependency info to any UI
> or class which
> +            # needs it
> +            depgraph = self.cooker.buildDependTree(self,
> self.rqdata.taskData)
> +            self.rqdata.init_progress_reporter.next_stage()
> +            bb.event.fire(bb.event.DepTreeGenerated(depgraph),
> self.cooker.data) +
> +            if not self.dm_event_handler_registered:
> +                 res = bb.event.register(self.dm_event_handler_name,
> +                                         lambda x:
> self.dm.check(self) if self.state in [runQueueRunning,
> runQueueCleanUp] else False,
> +
> ('bb.event.HeartbeatEvent',))
> +                 self.dm_event_handler_registered = True
> +
>              dump = self.cooker.configuration.dump_signatures
>              if dump:
>                  self.rqdata.init_progress_reporter.finish()
> @@ -1418,29 +1471,23 @@ class RunQueue:
>                  if 'printdiff' in dump:
>                      self.write_diffscenetasks(invalidtasks)
>                  self.state = runQueueComplete
> -            else:
> -                self.rqdata.init_progress_reporter.next_stage()
> -                self.start_worker()
> -                self.rqdata.init_progress_reporter.next_stage()
> -                self.rqexe = RunQueueExecuteScenequeue(self)
> -
> -        if self.state is runQueueSceneRun:
> -            if not self.dm_event_handler_registered:
> -                 res = bb.event.register(self.dm_event_handler_name,
> -                                         lambda x:
> self.dm.check(self) if self.state in [runQueueSceneRun,
> runQueueRunning, runQueueCleanUp] else False,
> -
> ('bb.event.HeartbeatEvent',))
> -                 self.dm_event_handler_registered = True
> -            retval = self.rqexe.execute()
>  
> -        if self.state is runQueueRunInit:
> -            if self.cooker.configuration.setsceneonly:
> -                self.state = runQueueComplete
> -            else:
> -                # Just in case we didn't setscene
> -                self.rqdata.init_progress_reporter.finish()
> -                logger.info("Executing RunQueue Tasks")
> -                self.rqexe = RunQueueExecuteTasks(self)
> -                self.state = runQueueRunning
> +        if self.state is runQueueSceneInit:
> +            self.rqdata.init_progress_reporter.next_stage()
> +            self.start_worker()
> +            self.rqdata.init_progress_reporter.next_stage()
> +            self.rqexe = RunQueueExecute(self)
> +
> +            # If we don't have any setscene functions, skip execution
> +            if len(self.rqdata.runq_setscene_tids) == 0:
> +                logger.info('No setscene tasks')
> +                for tid in self.rqdata.runtaskentries:
> +                    if len(self.rqdata.runtaskentries[tid].depends)
> == 0:
> +                        self.rqexe.setbuildable(tid)
> +                    self.rqexe.tasks_notcovered.add(tid)
> +                self.rqexe.sqdone = True
> +            logger.info('Executing Tasks')
> +            self.state = runQueueRunning
>  
>          if self.state is runQueueRunning:
>              retval = self.rqexe.execute()
> @@ -1455,12 +1502,14 @@ class RunQueue:
>              self.dm_event_handler_registered = False
>  
>          if build_done and self.rqexe:
> +            bb.parse.siggen.save_unitaskhashes()
>              self.teardown_workers()
> -            if self.rqexe.stats.failed:
> -                logger.info("Tasks Summary: Attempted %d tasks of
> which %d didn't need to be rerun and %d failed.",
> self.rqexe.stats.completed + self.rqexe.stats.failed,
> self.rqexe.stats.skipped, self.rqexe.stats.failed)
> -            else:
> -                # Let's avoid the word "failed" if nothing actually
> did
> -                logger.info("Tasks Summary: Attempted %d tasks of
> which %d didn't need to be rerun and all succeeded.",
> self.rqexe.stats.completed, self.rqexe.stats.skipped)
> +            if self.rqexe:
> +                if self.rqexe.stats.failed:
> +                    logger.info("Tasks Summary: Attempted %d tasks
> of which %d didn't need to be rerun and %d failed.",
> self.rqexe.stats.completed + self.rqexe.stats.failed,
> self.rqexe.stats.skipped, self.rqexe.stats.failed)
> +                else:
> +                    # Let's avoid the word "failed" if nothing
> actually did
> +                    logger.info("Tasks Summary: Attempted %d tasks
> of which %d didn't need to be rerun and all succeeded.",
> self.rqexe.stats.completed, self.rqexe.stats.skipped) if self.state
> is runQueueFailed: raise
> bb.runqueue.TaskFailure(self.rqexe.failed_tids) @@ -1543,15 +1592,8
> @@ class RunQueue: 
>      def print_diffscenetasks(self):
>  
> -        valid = []
> -        sq_hash = []
> -        sq_hashfn = []
> -        sq_fn = []
> -        sq_taskname = []
> -        sq_task = []
>          noexec = []
> -        stamppresent = []
> -        valid_new = set()
> +        tocheck = set()
>  
>          for tid in self.rqdata.runtaskentries:
>              (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> @@ -1561,21 +1603,9 @@ class RunQueue:
>                  noexec.append(tid)
>                  continue
>  
> -            sq_fn.append(fn)
> -
> sq_hashfn.append(self.rqdata.dataCaches[mc].hashfn[taskfn])
> -            sq_hash.append(self.rqdata.runtaskentries[tid].hash)
> -            sq_taskname.append(taskname)
> -            sq_task.append(tid)
> -        locs = { "sq_fn" : sq_fn, "sq_task" : sq_taskname,
> "sq_hash" : sq_hash, "sq_hashfn" : sq_hashfn, "d" : self.cooker.data }
> -        try:
> -            call = self.hashvalidate + "(sq_fn, sq_task, sq_hash,
> sq_hashfn, d, siginfo=True)"
> -            valid = bb.utils.better_eval(call, locs)
> -        # Handle version with no siginfo parameter
> -        except TypeError:
> -            call = self.hashvalidate + "(sq_fn, sq_task, sq_hash,
> sq_hashfn, d)"
> -            valid = bb.utils.better_eval(call, locs)
> -        for v in valid:
> -            valid_new.add(sq_task[v])
> +            tocheck.add(tid)
> +
> +        valid_new = self.validate_hashes(tocheck, self.cooker.data,
> 0, True) 
>          # Tasks which are both setscene and noexec never care about
> dependencies # We therefore find tasks which are setscene and noexec
> and mark their @@ -1634,7 +1664,7 @@ class RunQueue:
>              recout = []
>              if len(hashfiles) == 2:
>                  out2 = bb.siggen.compare_sigfiles(hashfiles[hash1],
> hashfiles[hash2], recursecb)
> -                recout.extend(list('  ' + l for l in out2))
> +                recout.extend(list('    ' + l for l in out2))
>              else:
>                  recout.append("Unable to find matching sigdata for
> %s with hashes %s or %s" % (key, hash1, hash2)) 
> @@ -1655,10 +1685,11 @@ class RunQueue:
>              matches = {k : v for k, v in iter(matches.items()) if h
> not in k} if matches:
>                  latestmatch = sorted(matches.keys(), key=lambda f:
> matches[f])[-1]
> -                prevh = __find_md5__.search(latestmatch).group(0)
> +                prevh = __find_sha256__.search(latestmatch).group(0)
>                  output = bb.siggen.compare_sigfiles(latestmatch,
> match, recursecb) bb.plain("\nTask %s:%s couldn't be used from the
> cache because:\n  We need hash %s, closest matching task was %s\n  "
> % (pn, taskname, h, prevh) + '\n  '.join(output)) +
>  class RunQueueExecute:
>  
>      def __init__(self, rq):
> @@ -1670,6 +1701,13 @@ class RunQueueExecute:
>          self.number_tasks =
> int(self.cfgData.getVar("BB_NUMBER_THREADS") or 1) self.scheduler =
> self.cfgData.getVar("BB_SCHEDULER") or "speed" 
> +        self.sq_buildable = set()
> +        self.sq_running = set()
> +        self.sq_live = set()
> +
> +        self.updated_taskhash_queue = []
> +        self.pending_migrations = set()
> +
>          self.runq_buildable = set()
>          self.runq_running = set()
>          self.runq_complete = set()
> @@ -1677,9 +1715,17 @@ class RunQueueExecute:
>          self.build_stamps = {}
>          self.build_stamps2 = []
>          self.failed_tids = []
> +        self.sq_deferred = {}
>  
>          self.stampcache = {}
>  
> +        self.holdoff_tasks = set()
> +        self.holdoff_need_update = True
> +        self.sqdone = False
> +
> +        self.stats = RunQueueStats(len(self.rqdata.runtaskentries))
> +        self.sq_stats =
> RunQueueStats(len(self.rqdata.runq_setscene_tids)) +
>          for mc in rq.worker:
>              rq.worker[mc].pipe.setrunqueueexec(self)
>          for mc in rq.fakeworker:
> @@ -1688,6 +1734,34 @@ class RunQueueExecute:
>          if self.number_tasks <= 0:
>               bb.fatal("Invalid BB_NUMBER_THREADS %s" %
> self.number_tasks) 
> +        # List of setscene tasks which we've covered
> +        self.scenequeue_covered = set()
> +        # List of tasks which are covered (including setscene ones)
> +        self.tasks_covered = set()
> +        self.tasks_scenequeue_done = set()
> +        self.scenequeue_notcovered = set()
> +        self.tasks_notcovered = set()
> +        self.scenequeue_notneeded = set()
> +
> +        # We can't skip specified target tasks which aren't setscene
> tasks
> +        self.cantskip = set(self.rqdata.target_tids)
> +
> self.cantskip.difference_update(self.rqdata.runq_setscene_tids)
> +        self.cantskip.intersection_update(self.rqdata.runtaskentries)
> +
> +        schedulers = self.get_schedulers()
> +        for scheduler in schedulers:
> +            if self.scheduler == scheduler.name:
> +                self.sched = scheduler(self, self.rqdata)
> +                logger.debug(1, "Using runqueue scheduler '%s'",
> scheduler.name)
> +                break
> +        else:
> +            bb.fatal("Invalid scheduler '%s'.  Available schedulers:
> %s" %
> +                     (self.scheduler, ", ".join(obj.name for obj in
> schedulers))) +
> +        #if len(self.rqdata.runq_setscene_tids) > 0:
> +        self.sqdata = SQData()
> +        build_scenequeue_data(self.sqdata, self.rqdata, self.rq,
> self.cooker, self.stampcache, self) +
>      def runqueue_process_waitpid(self, task, status):
>  
>          # self.build_stamps[pid] may not exist when use shared work
> directory. @@ -1695,10 +1769,17 @@ class RunQueueExecute:
>              self.build_stamps2.remove(self.build_stamps[task])
>              del self.build_stamps[task]
>  
> -        if status != 0:
> -            self.task_fail(task, status)
> +        if task in self.sq_live:
> +            if status != 0:
> +                self.sq_task_fail(task, status)
> +            else:
> +                self.sq_task_complete(task)
> +            self.sq_live.remove(task)
>          else:
> -            self.task_complete(task)
> +            if status != 0:
> +                self.task_fail(task, status)
> +            else:
> +                self.task_complete(task)
>          return True
>  
>      def finish_now(self):
> @@ -1727,8 +1808,9 @@ class RunQueueExecute:
>      def finish(self):
>          self.rq.state = runQueueCleanUp
>  
> -        if self.stats.active > 0:
> -            bb.event.fire(runQueueExitWait(self.stats.active),
> self.cfgData)
> +        active = self.stats.active + self.sq_stats.active
> +        if active > 0:
> +            bb.event.fire(runQueueExitWait(active), self.cfgData)
>              self.rq.read_workers()
>              return self.rq.active_fds()
>  
> @@ -1739,10 +1821,14 @@ class RunQueueExecute:
>          self.rq.state = runQueueComplete
>          return True
>  
> -    def check_dependencies(self, task, taskdeps, setscene = False):
> +    # Used by setscene only
> +    def check_dependencies(self, task, taskdeps):
>          if not self.rq.depvalidate:
>              return False
>  
> +        # Must not edit parent data
> +        taskdeps = set(taskdeps)
> +
>          taskdata = {}
>          taskdeps.add(task)
>          for dep in taskdeps:
> @@ -1755,121 +1841,10 @@ class RunQueueExecute:
>          return valid
>  
>      def can_start_task(self):
> -        can_start = self.stats.active < self.number_tasks
> +        active = self.stats.active + self.sq_stats.active
> +        can_start = active < self.number_tasks
>          return can_start
>  
> -class RunQueueExecuteDummy(RunQueueExecute):
> -    def __init__(self, rq):
> -        self.rq = rq
> -        self.stats = RunQueueStats(0)
> -
> -    def finish(self):
> -        self.rq.state = runQueueComplete
> -        return
> -
> -class RunQueueExecuteTasks(RunQueueExecute):
> -    def __init__(self, rq):
> -        RunQueueExecute.__init__(self, rq)
> -
> -        self.stats = RunQueueStats(len(self.rqdata.runtaskentries))
> -
> -        self.stampcache = {}
> -
> -        initial_covered = self.rq.scenequeue_covered.copy()
> -
> -        # Mark initial buildable tasks
> -        for tid in self.rqdata.runtaskentries:
> -            if len(self.rqdata.runtaskentries[tid].depends) == 0:
> -                self.runq_buildable.add(tid)
> -            if len(self.rqdata.runtaskentries[tid].revdeps) > 0 and
> self.rqdata.runtaskentries[tid].revdeps.issubset(self.rq.scenequeue_covered):
> -                self.rq.scenequeue_covered.add(tid)
> -
> -        found = True
> -        while found:
> -            found = False
> -            for tid in self.rqdata.runtaskentries:
> -                if tid in self.rq.scenequeue_covered:
> -                    continue
> -                logger.debug(1, 'Considering %s: %s' % (tid,
> str(self.rqdata.runtaskentries[tid].revdeps))) -
> -                if len(self.rqdata.runtaskentries[tid].revdeps) > 0
> and
> self.rqdata.runtaskentries[tid].revdeps.issubset(self.rq.scenequeue_covered):
> -                    if tid in self.rq.scenequeue_notcovered:
> -                        continue
> -                    found = True
> -                    self.rq.scenequeue_covered.add(tid)
> -
> -        logger.debug(1, 'Skip list (pre setsceneverify) %s',
> sorted(self.rq.scenequeue_covered)) -
> -        # Allow the metadata to elect for setscene tasks to run
> anyway
> -        covered_remove = set()
> -        if self.rq.setsceneverify:
> -            invalidtasks = []
> -            tasknames = {}
> -            fns = {}
> -            for tid in self.rqdata.runtaskentries:
> -                (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> -                taskdep =
> self.rqdata.dataCaches[mc].task_deps[taskfn]
> -                fns[tid] = taskfn
> -                tasknames[tid] = taskname
> -                if 'noexec' in taskdep and taskname in
> taskdep['noexec']:
> -                    continue
> -                if self.rq.check_stamp_task(tid, taskname +
> "_setscene", cache=self.stampcache):
> -                    logger.debug(2, 'Setscene stamp current for task
> %s', tid)
> -                    continue
> -                if self.rq.check_stamp_task(tid, taskname, recurse =
> True, cache=self.stampcache):
> -                    logger.debug(2, 'Normal stamp current for task
> %s', tid)
> -                    continue
> -                invalidtasks.append(tid)
> -
> -            call = self.rq.setsceneverify + "(covered, tasknames,
> fns, d, invalidtasks=invalidtasks)"
> -            locs = { "covered" : self.rq.scenequeue_covered,
> "tasknames" : tasknames, "fns" : fns, "d" : self.cooker.data,
> "invalidtasks" : invalidtasks }
> -            covered_remove = bb.utils.better_eval(call, locs)
> -
> -        def removecoveredtask(tid):
> -            (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> -            taskname = taskname + '_setscene'
> -            bb.build.del_stamp(taskname, self.rqdata.dataCaches[mc],
> taskfn)
> -            self.rq.scenequeue_covered.remove(tid)
> -
> -        toremove = covered_remove | self.rq.scenequeue_notcovered
> -        for task in toremove:
> -            logger.debug(1, 'Not skipping task %s due to
> setsceneverify', task)
> -        while toremove:
> -            covered_remove = []
> -            for task in toremove:
> -                if task in self.rq.scenequeue_covered:
> -                    removecoveredtask(task)
> -                for deptask in
> self.rqdata.runtaskentries[task].depends:
> -                    if deptask not in self.rq.scenequeue_covered:
> -                        continue
> -                    if deptask in toremove or deptask in
> covered_remove or deptask in initial_covered:
> -                        continue
> -                    logger.debug(1, 'Task %s depends on task %s so
> not skipping' % (task, deptask))
> -                    covered_remove.append(deptask)
> -            toremove = covered_remove
> -
> -        logger.debug(1, 'Full skip list %s',
> self.rq.scenequeue_covered) -
> -
> -        for mc in self.rqdata.dataCaches:
> -            target_pairs = []
> -            for tid in self.rqdata.target_tids:
> -                (tidmc, fn, taskname, _) = split_tid_mcfn(tid)
> -                if tidmc == mc:
> -                    target_pairs.append((fn, taskname))
> -
> -            event.fire(bb.event.StampUpdate(target_pairs,
> self.rqdata.dataCaches[mc].stamp), self.cfgData) -
> -        schedulers = self.get_schedulers()
> -        for scheduler in schedulers:
> -            if self.scheduler == scheduler.name:
> -                self.sched = scheduler(self, self.rqdata)
> -                logger.debug(1, "Using runqueue scheduler '%s'",
> scheduler.name)
> -                break
> -        else:
> -            bb.fatal("Invalid scheduler '%s'.  Available schedulers:
> %s" %
> -                     (self.scheduler, ", ".join(obj.name for obj in
> schedulers))) -
>      def get_schedulers(self):
>          schedulers = set(obj for obj in globals().values()
>                               if type(obj) is type and
> @@ -1941,67 +1916,172 @@ class RunQueueExecuteTasks(RunQueueExecute):
>          self.stats.taskSkipped()
>          self.stats.taskCompleted()
>  
> +    def summarise_scenequeue_errors(self):
> +        err = False
> +        if not self.sqdone:
> +            logger.debug(1, 'We could skip tasks %s',
> "\n".join(sorted(self.scenequeue_covered)))
> +            completeevent = sceneQueueComplete(self.sq_stats,
> self.rq)
> +            bb.event.fire(completeevent, self.cfgData)
> +        if self.sq_deferred:
> +            logger.error("Scenequeue had deferred entries: %s" %
> pprint.pformat(self.sq_deferred))
> +            err = True
> +        if self.updated_taskhash_queue:
> +            logger.error("Scenequeue had unprocessed changed
> taskhash entries: %s" % pprint.pformat(self.updated_taskhash_queue))
> +            err = True
> +        if self.holdoff_tasks:
> +            logger.error("Scenequeue had holdoff tasks: %s" %
> pprint.pformat(self.holdoff_tasks))
> +            err = True
> +
> +        for tid in self.rqdata.runq_setscene_tids:
> +            if tid not in self.scenequeue_covered and tid not in
> self.scenequeue_notcovered:
> +                err = True
> +                logger.error("Setscene Task %s was never marked as
> covered or not covered" % tid)
> +            if tid not in self.sq_buildable:
> +                err = True
> +                logger.error("Setscene Task %s was never marked as
> buildable" % tid)
> +            if tid not in self.sq_running:
> +                err = True
> +                logger.error("Setscene Task %s was never marked as
> running" % tid) +
> +        for x in self.rqdata.runtaskentries:
> +            if x not in self.tasks_covered and x not in
> self.tasks_notcovered:
> +                logger.error("Task %s was never moved from the
> setscene queue" % x)
> +                err = True
> +            if x not in self.tasks_scenequeue_done:
> +                logger.error("Task %s was never processed by the
> setscene code" % x)
> +                err = True
> +            if len(self.rqdata.runtaskentries[x].depends) == 0 and x
> not in self.runq_buildable:
> +                logger.error("Task %s was never marked as buildable
> by the setscene code" % x)
> +                err = True
> +        return err
> +
> +
>      def execute(self):
>          """
> -        Run the tasks in a queue prepared by rqdata.prepare()
> +        Run the tasks in a queue prepared by prepare_runqueue
>          """
>  
> -        if self.rqdata.setscenewhitelist is not None and not
> self.rqdata.setscenewhitelist_checked:
> -            self.rqdata.setscenewhitelist_checked = True
> -
> -            # Check tasks that are going to run against the whitelist
> -            def check_norun_task(tid, showerror=False):
> -                (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> -                # Ignore covered tasks
> -                if tid in self.rq.scenequeue_covered:
> -                    return False
> -                # Ignore stamped tasks
> -                if self.rq.check_stamp_task(tid, taskname,
> cache=self.stampcache):
> -                    return False
> -                # Ignore noexec tasks
> -                taskdep =
> self.rqdata.dataCaches[mc].task_deps[taskfn]
> -                if 'noexec' in taskdep and taskname in
> taskdep['noexec']:
> -                    return False
> +        self.rq.read_workers()
> +        self.process_possible_migrations()
>  
> -                pn = self.rqdata.dataCaches[mc].pkg_fn[taskfn]
> -                if not check_setscene_enforce_whitelist(pn,
> taskname, self.rqdata.setscenewhitelist):
> -                    if showerror:
> -                        if tid in self.rqdata.runq_setscene_tids:
> -                            logger.error('Task %s.%s attempted to
> execute unexpectedly and should have been setscened' % (pn, taskname))
> +        task = None
> +        if not self.sqdone and self.can_start_task():
> +            # Find the next setscene to run
> +            for nexttask in sorted(self.rqdata.runq_setscene_tids):
> +                if nexttask in self.sq_buildable and nexttask not in
> self.sq_running and self.sqdata.stamps[nexttask] not in
> self.build_stamps.values():
> +                    if nexttask not in self.sqdata.unskippable and
> len(self.sqdata.sq_revdeps[nexttask]) > 0 and
> self.sqdata.sq_revdeps[nexttask].issubset(self.scenequeue_covered)
> and self.check_dependencies(nexttask,
> self.sqdata.sq_revdeps[nexttask]):
> +                        if nexttask not in self.rqdata.target_tids:
> +                            logger.debug(2, "Skipping setscene for
> task %s" % nexttask)
> +                            self.sq_task_skip(nexttask)
> +                            self.scenequeue_notneeded.add(nexttask)
> +                            if nexttask in self.sq_deferred:
> +                                del self.sq_deferred[nexttask]
> +                            return True
> +                    # If covered tasks are running, need to wait for
> them to complete
> +                    for t in self.sqdata.sq_covered_tasks[nexttask]:
> +                        if t in self.runq_running and t not in
> self.runq_complete:
> +                            continue
> +                    if nexttask in self.sq_deferred:
> +                        if self.sq_deferred[nexttask] not in
> self.runq_complete:
> +                            continue
> +                        logger.debug(1, "Task %s no longer deferred"
> % nexttask)
> +                        del self.sq_deferred[nexttask]
> +                        valid =
> self.rq.validate_hashes(set([nexttask]), self.cooker.data, 0, False)
> +                        if not valid:
> +                            logger.debug(1, "%s didn't become valid,
> skipping setscene" % nexttask)
> +                            self.sq_task_failoutright(nexttask)
> +                            return True
>                          else:
> -                            logger.error('Task %s.%s attempted to
> execute unexpectedly' % (pn, taskname))
> -                    return True
> -                return False
> -            # Look to see if any tasks that we think shouldn't run
> are going to
> -            unexpected = False
> -            for tid in self.rqdata.runtaskentries:
> -                if check_norun_task(tid):
> -                    unexpected = True
> +                            self.sqdata.outrightfail.remove(nexttask)
> +                    if nexttask in self.sqdata.outrightfail:
> +                        logger.debug(2, 'No package found, so
> skipping setscene task %s', nexttask)
> +                        self.sq_task_failoutright(nexttask)
> +                        return True
> +                    if nexttask in self.sqdata.unskippable:
> +                        logger.debug(2, "Setscene task %s is
> unskippable" % nexttask)
> +                    task = nexttask
>                      break
> -            if unexpected:
> -                # Run through the tasks in the rough order they'd
> have executed and print errors
> -                # (since the order can be useful - usually missing
> sstate for the last few tasks
> -                # is the cause of the problem)
> -                task = self.sched.next()
> -                while task is not None:
> -                    check_norun_task(task, showerror=True)
> -                    self.task_skip(task, 'Setscene enforcement
> check')
> -                    task = self.sched.next()
> +        if task is not None:
> +            (mc, fn, taskname, taskfn) = split_tid_mcfn(task)
> +            taskname = taskname + "_setscene"
> +            if self.rq.check_stamp_task(task,
> taskname_from_tid(task), recurse = True, cache=self.stampcache):
> +                logger.debug(2, 'Stamp for underlying task %s is
> current, so skipping setscene variant', task)
> +                self.sq_task_failoutright(task)
> +                return True
>  
> -                self.rq.state = runQueueCleanUp
> +            if self.cooker.configuration.force:
> +                if task in self.rqdata.target_tids:
> +                    self.sq_task_failoutright(task)
> +                    return True
> +
> +            if self.rq.check_stamp_task(task, taskname,
> cache=self.stampcache):
> +                logger.debug(2, 'Setscene stamp current task %s, so
> skip it and its dependencies', task)
> +                self.sq_task_skip(task)
>                  return True
>  
> -        self.rq.read_workers()
> +            if self.cooker.configuration.skipsetscene:
> +                logger.debug(2, 'No setscene tasks should be
> executed. Skipping %s', task)
> +                self.sq_task_failoutright(task)
> +                return True
>  
> -        if self.stats.total == 0:
> -            # nothing to do
> -            self.rq.state = runQueueCleanUp
> +            startevent = sceneQueueTaskStarted(task, self.sq_stats,
> self.rq)
> +            bb.event.fire(startevent, self.cfgData)
> +
> +            taskdepdata = self.sq_build_taskdepdata(task)
> +
> +            taskdep = self.rqdata.dataCaches[mc].task_deps[taskfn]
> +            taskhash = self.rqdata.get_task_hash(task)
> +            unihash = self.rqdata.get_task_unihash(task)
> +            if 'fakeroot' in taskdep and taskname in
> taskdep['fakeroot'] and not self.cooker.configuration.dry_run:
> +                if not mc in self.rq.fakeworker:
> +                    self.rq.start_fakeworker(self, mc)
> +
> self.rq.fakeworker[mc].process.stdin.write(b"<runtask>" +
> pickle.dumps((taskfn, task, taskname, taskhash, unihash, True,
> self.cooker.collection.get_file_appends(taskfn), taskdepdata, False))
> + b"</runtask>")
> +                self.rq.fakeworker[mc].process.stdin.flush()
> +            else:
> +                self.rq.worker[mc].process.stdin.write(b"<runtask>"
> + pickle.dumps((taskfn, task, taskname, taskhash, unihash, True,
> self.cooker.collection.get_file_appends(taskfn), taskdepdata, False))
> + b"</runtask>")
> +                self.rq.worker[mc].process.stdin.flush()
>  
> -        task = self.sched.next()
> +            self.build_stamps[task] = bb.build.stampfile(taskname,
> self.rqdata.dataCaches[mc], taskfn, noextra=True)
> +            self.build_stamps2.append(self.build_stamps[task])
> +            self.sq_running.add(task)
> +            self.sq_live.add(task)
> +            self.sq_stats.taskActive()
> +            if self.can_start_task():
> +                return True
> +
> +        self.update_holdofftasks()
> +
> +        if not self.sq_live and not self.sqdone and not
> self.sq_deferred and not self.updated_taskhash_queue and not
> self.holdoff_tasks:
> +            logger.info("Setscene tasks completed")
> +
> +            err = self.summarise_scenequeue_errors()
> +            if err:
> +                self.rq.state = runQueueFailed
> +                return True
> +
> +            if self.cooker.configuration.setsceneonly:
> +                self.rq.state = runQueueComplete
> +                return True
> +            self.sqdone = True
> +
> +            if self.stats.total == 0:
> +                # nothing to do
> +                self.rq.state = runQueueComplete
> +                return True
> +
> +        if self.cooker.configuration.setsceneonly:
> +            task = None
> +        else:
> +            task = self.sched.next()
>          if task is not None:
>              (mc, fn, taskname, taskfn) = split_tid_mcfn(task)
>  
> -            if task in self.rq.scenequeue_covered:
> +            if self.rqdata.setscenewhitelist is not None:
> +                if self.check_setscenewhitelist(task):
> +                    self.task_fail(task, "setscene whitelist")
> +                    return True
> +
> +            if task in self.tasks_covered:
>                  logger.debug(2, "Setscene covered task %s", task)
>                  self.task_skip(task, "covered")
>                  return True
> @@ -2030,6 +2110,8 @@ class RunQueueExecuteTasks(RunQueueExecute):
>              taskdepdata = self.build_taskdepdata(task)
>  
>              taskdep = self.rqdata.dataCaches[mc].task_deps[taskfn]
> +            taskhash = self.rqdata.get_task_hash(task)
> +            unihash = self.rqdata.get_task_unihash(task)
>              if 'fakeroot' in taskdep and taskname in
> taskdep['fakeroot'] and not (self.cooker.configuration.dry_run or
> self.rqdata.setscene_enforce): if not mc in self.rq.fakeworker: try:
> @@ -2039,10 +2121,10 @@ class RunQueueExecuteTasks(RunQueueExecute):
>                          self.rq.state = runQueueFailed
>                          self.stats.taskFailed()
>                          return True
> -
> self.rq.fakeworker[mc].process.stdin.write(b"<runtask>" +
> pickle.dumps((taskfn, task, taskname, False,
> self.cooker.collection.get_file_appends(taskfn), taskdepdata,
> self.rqdata.setscene_enforce)) + b"</runtask>")
> +
> self.rq.fakeworker[mc].process.stdin.write(b"<runtask>" +
> pickle.dumps((taskfn, task, taskname, taskhash, unihash, False,
> self.cooker.collection.get_file_appends(taskfn), taskdepdata,
> self.rqdata.setscene_enforce)) + b"</runtask>")
> self.rq.fakeworker[mc].process.stdin.flush() else:
> -                self.rq.worker[mc].process.stdin.write(b"<runtask>"
> + pickle.dumps((taskfn, task, taskname, False,
> self.cooker.collection.get_file_appends(taskfn), taskdepdata,
> self.rqdata.setscene_enforce)) + b"</runtask>")
> +                self.rq.worker[mc].process.stdin.write(b"<runtask>"
> + pickle.dumps((taskfn, task, taskname, taskhash, unihash, False,
> self.cooker.collection.get_file_appends(taskfn), taskdepdata,
> self.rqdata.setscene_enforce)) + b"</runtask>")
> self.rq.worker[mc].process.stdin.flush() self.build_stamps[task] =
> bb.build.stampfile(taskname, self.rqdata.dataCaches[mc], taskfn,
> noextra=True) @@ -2052,30 +2134,58 @@ class
> RunQueueExecuteTasks(RunQueueExecute): if self.can_start_task():
> return True 
> -        if self.stats.active > 0:
> +        if self.stats.active > 0 or self.sq_stats.active > 0:
>              self.rq.read_workers()
>              return self.rq.active_fds()
>  
> +        # No more tasks can be run. If we have deferred setscene
> tasks we should run them.
> +        if self.sq_deferred:
> +            tid =
> self.sq_deferred.pop(list(self.sq_deferred.keys())[0])
> +            logger.warning("Runqeueue deadlocked on deferred tasks,
> forcing task %s" % tid)
> +            self.sq_task_failoutright(tid)
> +            return True
> +
>          if len(self.failed_tids) != 0:
>              self.rq.state = runQueueFailed
>              return True
>  
>          # Sanity Checks
> +        err = self.summarise_scenequeue_errors()
>          for task in self.rqdata.runtaskentries:
>              if task not in self.runq_buildable:
>                  logger.error("Task %s never buildable!", task)
> -            if task not in self.runq_running:
> +                err = True
> +            elif task not in self.runq_running:
>                  logger.error("Task %s never ran!", task)
> -            if task not in self.runq_complete:
> +                err = True
> +            elif task not in self.runq_complete:
>                  logger.error("Task %s never completed!", task)
> -        self.rq.state = runQueueComplete
> +                err = True
> +
> +        if err:
> +            self.rq.state = runQueueFailed
> +        else:
> +            self.rq.state = runQueueComplete
>  
>          return True
>  
> +    def filtermcdeps(self, task, mc, deps):
> +        ret = set()
> +        for dep in deps:
> +            thismc = mc_from_tid(dep)
> +            if thismc != mc:
> +                continue
> +            ret.add(dep)
> +        return ret
> +
> +    # We filter out multiconfig dependencies from taskdepdata we
> pass to the tasks
> +    # as most code can't handle them
>      def build_taskdepdata(self, task):
>          taskdepdata = {}
> -        next = self.rqdata.runtaskentries[task].depends
> +        mc = mc_from_tid(task)
> +        next = self.rqdata.runtaskentries[task].depends.copy()
>          next.add(task)
> +        next = self.filtermcdeps(task, mc, next)
>          while next:
>              additional = []
>              for revdep in next:
> @@ -2084,7 +2194,9 @@ class RunQueueExecuteTasks(RunQueueExecute):
>                  deps = self.rqdata.runtaskentries[revdep].depends
>                  provides =
> self.rqdata.dataCaches[mc].fn_provides[taskfn] taskhash =
> self.rqdata.runtaskentries[revdep].hash
> -                taskdepdata[revdep] = [pn, taskname, fn, deps,
> provides, taskhash]
> +                unihash = self.rqdata.runtaskentries[revdep].unihash
> +                deps = self.filtermcdeps(task, mc, deps)
> +                taskdepdata[revdep] = [pn, taskname, fn, deps,
> provides, taskhash, unihash] for revdep2 in deps:
>                      if revdep2 not in taskdepdata:
>                          additional.append(revdep2)
> @@ -2093,269 +2205,197 @@ class RunQueueExecuteTasks(RunQueueExecute):
>          #bb.note("Task %s: " % task + str(taskdepdata).replace("],
> ", "],\n")) return taskdepdata
>  
> -class RunQueueExecuteScenequeue(RunQueueExecute):
> -    def __init__(self, rq):
> -        RunQueueExecute.__init__(self, rq)
> -
> -        self.scenequeue_covered = set()
> -        self.scenequeue_notcovered = set()
> -        self.scenequeue_notneeded = set()
> +    def update_holdofftasks(self):
>  
> -        # If we don't have any setscene functions, skip this step
> -        if len(self.rqdata.runq_setscene_tids) == 0:
> -            rq.scenequeue_covered = set()
> -            rq.scenequeue_notcovered = set()
> -            rq.state = runQueueRunInit
> +        if not self.holdoff_need_update:
>              return
>  
> -        self.stats =
> RunQueueStats(len(self.rqdata.runq_setscene_tids))
> +        notcovered = set(self.scenequeue_notcovered)
> +        notcovered |= self.cantskip
> +        for tid in self.scenequeue_notcovered:
> +            notcovered |= self.sqdata.sq_covered_tasks[tid]
> +        notcovered |=
> self.sqdata.unskippable.difference(self.rqdata.runq_setscene_tids)
> +        notcovered.intersection_update(self.tasks_scenequeue_done)
>  
> -        sq_revdeps = {}
> -        sq_revdeps_new = {}
> -        sq_revdeps_squash = {}
> -        self.sq_harddeps = {}
> -        self.stamps = {}
> -
> -        # We need to construct a dependency graph for the setscene
> functions. Intermediate
> -        # dependencies between the setscene tasks only complicate
> the code. This code
> -        # therefore aims to collapse the huge runqueue dependency
> tree into a smaller one
> -        # only containing the setscene functions.
> -
> -        self.rqdata.init_progress_reporter.next_stage()
> -
> -        # First process the chains up to the first setscene task.
> -        endpoints = {}
> -        for tid in self.rqdata.runtaskentries:
> -            sq_revdeps[tid] =
> copy.copy(self.rqdata.runtaskentries[tid].revdeps)
> -            sq_revdeps_new[tid] = set()
> -            if (len(sq_revdeps[tid]) == 0) and tid not in
> self.rqdata.runq_setscene_tids:
> -                #bb.warn("Added endpoint %s" % (tid))
> -                endpoints[tid] = set()
> +        covered = set(self.scenequeue_covered)
> +        for tid in self.scenequeue_covered:
> +            covered |= self.sqdata.sq_covered_tasks[tid]
> +        covered.difference_update(notcovered)
> +        covered.intersection_update(self.tasks_scenequeue_done)
>  
> -        self.rqdata.init_progress_reporter.next_stage()
> +        for tid in notcovered | covered:
> +            if len(self.rqdata.runtaskentries[tid].depends) == 0:
> +                self.setbuildable(tid)
> +            elif
> self.rqdata.runtaskentries[tid].depends.issubset(self.runq_complete):
> +                 self.setbuildable(tid)
>  
> -        # Secondly process the chains between setscene tasks.
> -        for tid in self.rqdata.runq_setscene_tids:
> -            #bb.warn("Added endpoint 2 %s" % (tid))
> -            for dep in self.rqdata.runtaskentries[tid].depends:
> -                    if tid in sq_revdeps[dep]:
> -                        sq_revdeps[dep].remove(tid)
> -                    if dep not in endpoints:
> -                        endpoints[dep] = set()
> -                    #bb.warn("  Added endpoint 3 %s" % (dep))
> -                    endpoints[dep].add(tid)
> -
> -        self.rqdata.init_progress_reporter.next_stage()
> -
> -        def process_endpoints(endpoints):
> -            newendpoints = {}
> -            for point, task in endpoints.items():
> -                tasks = set()
> -                if task:
> -                    tasks |= task
> -                if sq_revdeps_new[point]:
> -                    tasks |= sq_revdeps_new[point]
> -                sq_revdeps_new[point] = set()
> -                if point in self.rqdata.runq_setscene_tids:
> -                    sq_revdeps_new[point] = tasks
> -                    tasks = set()
> -                    continue
> -                for dep in self.rqdata.runtaskentries[point].depends:
> -                    if point in sq_revdeps[dep]:
> -                        sq_revdeps[dep].remove(point)
> -                    if tasks:
> -                        sq_revdeps_new[dep] |= tasks
> -                    if len(sq_revdeps[dep]) == 0 and dep not in
> self.rqdata.runq_setscene_tids:
> -                        newendpoints[dep] = task
> -            if len(newendpoints) != 0:
> -                process_endpoints(newendpoints)
> -
> -        process_endpoints(endpoints)
> -
> -        self.rqdata.init_progress_reporter.next_stage()
> -
> -        # Build a list of setscene tasks which are "unskippable"
> -        # These are direct endpoints referenced by the build
> -        endpoints2 = {}
> -        sq_revdeps2 = {}
> -        sq_revdeps_new2 = {}
> -        def process_endpoints2(endpoints):
> -            newendpoints = {}
> -            for point, task in endpoints.items():
> -                tasks = set([point])
> -                if task:
> -                    tasks |= task
> -                if sq_revdeps_new2[point]:
> -                    tasks |= sq_revdeps_new2[point]
> -                sq_revdeps_new2[point] = set()
> -                if point in self.rqdata.runq_setscene_tids:
> -                    sq_revdeps_new2[point] = tasks
> -                for dep in self.rqdata.runtaskentries[point].depends:
> -                    if point in sq_revdeps2[dep]:
> -                        sq_revdeps2[dep].remove(point)
> -                    if tasks:
> -                        sq_revdeps_new2[dep] |= tasks
> -                    if (len(sq_revdeps2[dep]) == 0 or
> len(sq_revdeps_new2[dep]) != 0) and dep not in
> self.rqdata.runq_setscene_tids:
> -                        newendpoints[dep] = tasks
> -            if len(newendpoints) != 0:
> -                process_endpoints2(newendpoints)
> -        for tid in self.rqdata.runtaskentries:
> -            sq_revdeps2[tid] =
> copy.copy(self.rqdata.runtaskentries[tid].revdeps)
> -            sq_revdeps_new2[tid] = set()
> -            if (len(sq_revdeps2[tid]) == 0) and tid not in
> self.rqdata.runq_setscene_tids:
> -                endpoints2[tid] = set()
> -        process_endpoints2(endpoints2)
> -        self.unskippable = []
> -        for tid in self.rqdata.runq_setscene_tids:
> -            if sq_revdeps_new2[tid]:
> -                self.unskippable.append(tid)
> +        self.tasks_covered = covered
> +        self.tasks_notcovered = notcovered
>  
> -
> self.rqdata.init_progress_reporter.next_stage(len(self.rqdata.runtaskentries))
> +        self.holdoff_tasks = set()
>  
> -        for taskcounter, tid in
> enumerate(self.rqdata.runtaskentries):
> -            if tid in self.rqdata.runq_setscene_tids:
> -                deps = set()
> -                for dep in sq_revdeps_new[tid]:
> -                    deps.add(dep)
> -                sq_revdeps_squash[tid] = deps
> -            elif len(sq_revdeps_new[tid]) != 0:
> -                bb.msg.fatal("RunQueue", "Something went badly wrong
> during scenequeue generation, aborting. Please report this problem.")
> -            self.rqdata.init_progress_reporter.update(taskcounter)
> -
> -        self.rqdata.init_progress_reporter.next_stage()
> -
> -        # Resolve setscene inter-task dependencies
> -        # e.g. do_sometask_setscene[depends] =
> "targetname:do_someothertask_setscene"
> -        # Note that anything explicitly depended upon will have its
> reverse dependencies removed to avoid circular dependencies for tid
> in self.rqdata.runq_setscene_tids:
> -                (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> -                realtid = tid + "_setscene"
> -                idepends =
> self.rqdata.taskData[mc].taskentries[realtid].idepends
> -                self.stamps[tid] = bb.build.stampfile(taskname +
> "_setscene", self.rqdata.dataCaches[mc], taskfn, noextra=True)
> -                for (depname, idependtask) in idepends:
> -
> -                    if depname not in
> self.rqdata.taskData[mc].build_targets:
> -                        continue
> +            if tid not in self.scenequeue_covered and tid not in
> self.scenequeue_notcovered:
> +                self.holdoff_tasks.add(tid)
>  
> -                    depfn =
> self.rqdata.taskData[mc].build_targets[depname][0]
> -                    if depfn is None:
> -                         continue
> -                    deptid = depfn + ":" +
> idependtask.replace("_setscene", "")
> -                    if deptid not in self.rqdata.runtaskentries:
> -                        bb.msg.fatal("RunQueue", "Task %s depends
> upon non-existent task %s:%s" % (realtid, depfn, idependtask)) -
> -                    if not deptid in self.sq_harddeps:
> -                        self.sq_harddeps[deptid] = set()
> -                    self.sq_harddeps[deptid].add(tid)
> -
> -                    sq_revdeps_squash[tid].add(deptid)
> -                    # Have to zero this to avoid circular
> dependencies
> -                    sq_revdeps_squash[deptid] = set()
> -
> -        self.rqdata.init_progress_reporter.next_stage()
> -
> -        for task in self.sq_harddeps:
> -             for dep in self.sq_harddeps[task]:
> -                 sq_revdeps_squash[dep].add(task)
> -
> -        self.rqdata.init_progress_reporter.next_stage()
> -
> -        #for tid in sq_revdeps_squash:
> -        #    for dep in sq_revdeps_squash[tid]:
> -        #        data = data + "\n   %s" % dep
> -        #    bb.warn("Task %s_setscene: is %s " % (tid, data
> -
> -        self.sq_deps = {}
> -        self.sq_revdeps = sq_revdeps_squash
> -        self.sq_revdeps2 = copy.deepcopy(self.sq_revdeps)
> -
> -        for tid in self.sq_revdeps:
> -            self.sq_deps[tid] = set()
> -        for tid in self.sq_revdeps:
> -            for dep in self.sq_revdeps[tid]:
> -                self.sq_deps[dep].add(tid)
> -
> -        self.rqdata.init_progress_reporter.next_stage()
> -
> -        for tid in self.sq_revdeps:
> -            if len(self.sq_revdeps[tid]) == 0:
> -                self.runq_buildable.add(tid)
> -
> -        self.rqdata.init_progress_reporter.finish()
> -
> -        self.outrightfail = []
> -        if self.rq.hashvalidate:
> -            sq_hash = []
> -            sq_hashfn = []
> -            sq_fn = []
> -            sq_taskname = []
> -            sq_task = []
> -            noexec = []
> -            stamppresent = []
> -            for tid in self.sq_revdeps:
> -                (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> +        for tid in self.holdoff_tasks.copy():
> +            for dep in self.sqdata.sq_covered_tasks[tid]:
> +                if dep not in self.runq_complete:
> +                    self.holdoff_tasks.add(dep)
>  
> -                taskdep =
> self.rqdata.dataCaches[mc].task_deps[taskfn]
> +        self.holdoff_need_update = False
>  
> -                if 'noexec' in taskdep and taskname in
> taskdep['noexec']:
> -                    noexec.append(tid)
> -                    self.task_skip(tid)
> -                    bb.build.make_stamp(taskname + "_setscene",
> self.rqdata.dataCaches[mc], taskfn)
> -                    continue
> +    def process_possible_migrations(self):
>  
> -                if self.rq.check_stamp_task(tid, taskname +
> "_setscene", cache=self.stampcache):
> -                    logger.debug(2, 'Setscene stamp current for task
> %s', tid)
> -                    stamppresent.append(tid)
> -                    self.task_skip(tid)
> -                    continue
> +        changed = set()
> +        for tid, unihash in self.updated_taskhash_queue.copy():
> +            if tid in self.runq_running and tid not in
> self.runq_complete:
> +                continue
>  
> -                if self.rq.check_stamp_task(tid, taskname, recurse =
> True, cache=self.stampcache):
> -                    logger.debug(2, 'Normal stamp current for task
> %s', tid)
> -                    stamppresent.append(tid)
> -                    self.task_skip(tid)
> -                    continue
> +            self.updated_taskhash_queue.remove((tid, unihash))
> +
> +            if unihash != self.rqdata.runtaskentries[tid].unihash:
> +                logger.info("Task %s unihash changed to %s" % (tid,
> unihash))
> +                self.rqdata.runtaskentries[tid].unihash = unihash
> +                bb.parse.siggen.set_unihash(tid, unihash)
> +
> +                # Work out all tasks which depend on this one
> +                total = set()
> +                next = set(self.rqdata.runtaskentries[tid].revdeps)
> +                while next:
> +                    current = next.copy()
> +                    total = total |next
> +                    next = set()
> +                    for ntid in current:
> +                        next |=
> self.rqdata.runtaskentries[ntid].revdeps
> +                        next.difference_update(total)
> +
> +                # Now iterate those tasks in dependency order to
> regenerate their taskhash/unihash
> +                done = set()
> +                next = set(self.rqdata.runtaskentries[tid].revdeps)
> +                while next:
> +                    current = next.copy()
> +                    next = set()
> +                    for tid in current:
> +                        if not
> self.rqdata.runtaskentries[tid].depends.isdisjoint(total):
> +                            continue
> +                        procdep = []
> +                        for dep in
> self.rqdata.runtaskentries[tid].depends:
> +                            procdep.append(dep)
> +                        orighash =
> self.rqdata.runtaskentries[tid].hash
> +                        self.rqdata.runtaskentries[tid].hash =
> bb.parse.siggen.get_taskhash(tid, procdep,
> self.rqdata.dataCaches[mc_from_tid(tid)])
> +                        origuni =
> self.rqdata.runtaskentries[tid].unihash
> +                        self.rqdata.runtaskentries[tid].unihash =
> bb.parse.siggen.get_unihash(tid)
> +                        logger.debug(1, "Task %s hash changes:
> %s->%s %s->%s" % (tid, orighash,
> self.rqdata.runtaskentries[tid].hash, origuni,
> self.rqdata.runtaskentries[tid].unihash))
> +                        next |=
> self.rqdata.runtaskentries[tid].revdeps
> +                        changed.add(tid)
> +                        total.remove(tid)
> +                        next.intersection_update(total)
> +
> +        if changed:
> +            for mc in self.rq.worker:
> +
> self.rq.worker[mc].process.stdin.write(b"<newtaskhashes>" +
> pickle.dumps(bb.parse.siggen.get_taskhashes()) + b"</newtaskhashes>")
> +            for mc in self.rq.fakeworker:
> +
> self.rq.fakeworker[mc].process.stdin.write(b"<newtaskhashes>" +
> pickle.dumps(bb.parse.siggen.get_taskhashes()) + b"</newtaskhashes>")
> +
> +            logger.debug(1, pprint.pformat("Tasks changed:\n%s" %
> (changed))) +
> +        for tid in changed:
> +            if tid not in self.rqdata.runq_setscene_tids:
> +                continue
> +            if tid in self.runq_running:
> +                continue
> +            if tid in self.scenequeue_covered:
> +                # Potentially risky, should we report this hash as a
> match?
> +                logger.info("Already covered setscene for %s so
> ignoring rehash" % (tid))
> +                continue
> +            if tid not in self.pending_migrations:
> +                self.pending_migrations.add(tid)
> +
> +        for tid in self.pending_migrations.copy():
> +            valid = True
> +            # Check no tasks this covers are running
> +            for dep in self.sqdata.sq_covered_tasks[tid]:
> +                if dep in self.runq_running and dep not in
> self.runq_complete:
> +                    logger.debug(2, "Task %s is running which blocks
> setscene for %s from running" % (dep, tid))
> +                    valid = False
> +                    break
> +            if not valid:
> +                continue
>  
> -                sq_fn.append(fn)
> -
> sq_hashfn.append(self.rqdata.dataCaches[mc].hashfn[taskfn])
> -                sq_hash.append(self.rqdata.runtaskentries[tid].hash)
> -                sq_taskname.append(taskname)
> -                sq_task.append(tid)
> +            self.pending_migrations.remove(tid)
> +            changed = True
>  
> -
> self.cooker.data.setVar("BB_SETSCENE_STAMPCURRENT_COUNT",
> len(stamppresent))
> +            if tid in self.tasks_scenequeue_done:
> +                self.tasks_scenequeue_done.remove(tid)
> +            for dep in self.sqdata.sq_covered_tasks[tid]:
> +                if dep not in self.runq_complete:
> +                    if dep in self.tasks_scenequeue_done and dep not
> in self.sqdata.unskippable:
> +                        self.tasks_scenequeue_done.remove(dep)
> +
> +            if tid in self.sq_buildable:
> +                self.sq_buildable.remove(tid)
> +            if tid in self.sq_running:
> +                self.sq_running.remove(tid)
> +            if
> self.sqdata.sq_revdeps[tid].issubset(self.scenequeue_covered |
> self.scenequeue_notcovered):
> +                if tid not in self.sq_buildable:
> +                    self.sq_buildable.add(tid)
> +            if len(self.sqdata.sq_revdeps[tid]) == 0:
> +                self.sq_buildable.add(tid)
> +
> +            if tid in self.sqdata.outrightfail:
> +                self.sqdata.outrightfail.remove(tid)
> +            if tid in self.scenequeue_notcovered:
> +                self.scenequeue_notcovered.remove(tid)
> +            if tid in self.scenequeue_covered:
> +                self.scenequeue_covered.remove(tid)
> +            if tid in self.scenequeue_notneeded:
> +                self.scenequeue_notneeded.remove(tid)
>  
> -            call = self.rq.hashvalidate + "(sq_fn, sq_task, sq_hash,
> sq_hashfn, d)"
> -            locs = { "sq_fn" : sq_fn, "sq_task" : sq_taskname,
> "sq_hash" : sq_hash, "sq_hashfn" : sq_hashfn, "d" : self.cooker.data }
> -            valid = bb.utils.better_eval(call, locs)
> +            (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> +            self.sqdata.stamps[tid] = bb.build.stampfile(taskname +
> "_setscene", self.rqdata.dataCaches[mc], taskfn, noextra=True) 
> -            self.cooker.data.delVar("BB_SETSCENE_STAMPCURRENT_COUNT")
> +            if tid in self.stampcache:
> +                del self.stampcache[tid]
>  
> -            valid_new = stamppresent
> -            for v in valid:
> -                valid_new.append(sq_task[v])
> +            if tid in self.build_stamps:
> +                del self.build_stamps[tid]
>  
> -            for tid in self.sq_revdeps:
> -                if tid not in valid_new and tid not in noexec:
> -                    logger.debug(2, 'No package found, so skipping
> setscene task %s', tid)
> -                    self.outrightfail.append(tid)
> +            logger.info("Setscene task %s now valid and being rerun"
> % tid)
> +            self.sqdone = False
> +            update_scenequeue_data([tid], self.sqdata, self.rqdata,
> self.rq, self.cooker, self.stampcache, self) 
> -        logger.info('Executing SetScene Tasks')
> +        if changed:
> +            self.holdoff_need_update = True
>  
> -        self.rq.state = runQueueSceneRun
> +    def scenequeue_updatecounters(self, task, fail=False):
>  
> -    def scenequeue_updatecounters(self, task, fail = False):
> -        for dep in self.sq_deps[task]:
> -            if fail and task in self.sq_harddeps and dep in
> self.sq_harddeps[task]:
> +        for dep in sorted(self.sqdata.sq_deps[task]):
> +            if fail and task in self.sqdata.sq_harddeps and dep in
> self.sqdata.sq_harddeps[task]: logger.debug(2, "%s was unavailable
> and is a hard dependency of %s so skipping" % (task, dep))
> -                self.scenequeue_updatecounters(dep, fail)
> +                self.sq_task_failoutright(dep)
>                  continue
> -            if task not in self.sq_revdeps2[dep]:
> -                # May already have been removed by the fail case
> above
> -                continue
> -            self.sq_revdeps2[dep].remove(task)
> -            if len(self.sq_revdeps2[dep]) == 0:
> -                self.runq_buildable.add(dep)
> +            if
> self.sqdata.sq_revdeps[dep].issubset(self.scenequeue_covered |
> self.scenequeue_notcovered):
> +                if dep not in self.sq_buildable:
> +                    self.sq_buildable.add(dep)
>  
> -    def task_completeoutright(self, task):
> +        next = set([task])
> +        while next:
> +            new = set()
> +            for t in sorted(next):
> +                self.tasks_scenequeue_done.add(t)
> +                # Look down the dependency chain for non-setscene
> things which this task depends on
> +                # and mark as 'done'
> +                for dep in self.rqdata.runtaskentries[t].depends:
> +                    if dep in self.rqdata.runq_setscene_tids or dep
> in self.tasks_scenequeue_done:
> +                        continue
> +                    if
> self.rqdata.runtaskentries[dep].revdeps.issubset(self.tasks_scenequeue_done):
> +                        new.add(dep)
> +            next = new
> +
> +        self.holdoff_need_update = True
> +
> +    def sq_task_completeoutright(self, task):
>          """
>          Mark a task as completed
>          Look at the reverse dependencies and mark any task with
> @@ -2366,7 +2406,7 @@ class
> RunQueueExecuteScenequeue(RunQueueExecute):
> self.scenequeue_covered.add(task) self.scenequeue_updatecounters(task)
>  
> -    def check_taskfail(self, task):
> +    def sq_check_taskfail(self, task):
>          if self.rqdata.setscenewhitelist is not None:
>              realtask = task.split('_setscene')[0]
>              (mc, fn, taskname, taskfn) = split_tid_mcfn(realtask)
> @@ -2375,130 +2415,34 @@ class
> RunQueueExecuteScenequeue(RunQueueExecute): logger.error('Task %s.%s
> failed' % (pn, taskname + "_setscene")) self.rq.state =
> runQueueCleanUp 
> -    def task_complete(self, task):
> -        self.stats.taskCompleted()
> -        bb.event.fire(sceneQueueTaskCompleted(task, self.stats,
> self.rq), self.cfgData)
> -        self.task_completeoutright(task)
> +    def sq_task_complete(self, task):
> +        self.sq_stats.taskCompleted()
> +        bb.event.fire(sceneQueueTaskCompleted(task, self.sq_stats,
> self.rq), self.cfgData)
> +        self.sq_task_completeoutright(task)
>  
> -    def task_fail(self, task, result):
> -        self.stats.taskFailed()
> -        bb.event.fire(sceneQueueTaskFailed(task, self.stats, result,
> self), self.cfgData)
> +    def sq_task_fail(self, task, result):
> +        self.sq_stats.taskFailed()
> +        bb.event.fire(sceneQueueTaskFailed(task, self.sq_stats,
> result, self), self.cfgData) self.scenequeue_notcovered.add(task)
>          self.scenequeue_updatecounters(task, True)
> -        self.check_taskfail(task)
> +        self.sq_check_taskfail(task)
>  
> -    def task_failoutright(self, task):
> -        self.runq_running.add(task)
> -        self.runq_buildable.add(task)
> -        self.stats.taskSkipped()
> -        self.stats.taskCompleted()
> +    def sq_task_failoutright(self, task):
> +        self.sq_running.add(task)
> +        self.sq_buildable.add(task)
> +        self.sq_stats.taskSkipped()
> +        self.sq_stats.taskCompleted()
>          self.scenequeue_notcovered.add(task)
>          self.scenequeue_updatecounters(task, True)
>  
> -    def task_skip(self, task):
> -        self.runq_running.add(task)
> -        self.runq_buildable.add(task)
> -        self.task_completeoutright(task)
> -        self.stats.taskSkipped()
> -        self.stats.taskCompleted()
> -
> -    def execute(self):
> -        """
> -        Run the tasks in a queue prepared by prepare_runqueue
> -        """
> -
> -        self.rq.read_workers()
> -
> -        task = None
> -        if self.can_start_task():
> -            # Find the next setscene to run
> -            for nexttask in self.rqdata.runq_setscene_tids:
> -                if nexttask in self.runq_buildable and nexttask not
> in self.runq_running and self.stamps[nexttask] not in
> self.build_stamps.values():
> -                    if nexttask in self.unskippable:
> -                        logger.debug(2, "Setscene task %s is
> unskippable" % nexttask)
> -                    if nexttask not in self.unskippable and
> len(self.sq_revdeps[nexttask]) > 0 and
> self.sq_revdeps[nexttask].issubset(self.scenequeue_covered) and
> self.check_dependencies(nexttask, self.sq_revdeps[nexttask], True):
> -                        fn = fn_from_tid(nexttask)
> -                        foundtarget = False
> -
> -                        if nexttask in self.rqdata.target_tids:
> -                            foundtarget = True
> -                        if not foundtarget:
> -                            logger.debug(2, "Skipping setscene for
> task %s" % nexttask)
> -                            self.task_skip(nexttask)
> -                            self.scenequeue_notneeded.add(nexttask)
> -                            return True
> -                    if nexttask in self.outrightfail:
> -                        self.task_failoutright(nexttask)
> -                        return True
> -                    task = nexttask
> -                    break
> -        if task is not None:
> -            (mc, fn, taskname, taskfn) = split_tid_mcfn(task)
> -            taskname = taskname + "_setscene"
> -            if self.rq.check_stamp_task(task,
> taskname_from_tid(task), recurse = True, cache=self.stampcache):
> -                logger.debug(2, 'Stamp for underlying task %s is
> current, so skipping setscene variant', task)
> -                self.task_failoutright(task)
> -                return True
> -
> -            if self.cooker.configuration.force:
> -                if task in self.rqdata.target_tids:
> -                    self.task_failoutright(task)
> -                    return True
> -
> -            if self.rq.check_stamp_task(task, taskname,
> cache=self.stampcache):
> -                logger.debug(2, 'Setscene stamp current task %s, so
> skip it and its dependencies', task)
> -                self.task_skip(task)
> -                return True
> -
> -            startevent = sceneQueueTaskStarted(task, self.stats,
> self.rq)
> -            bb.event.fire(startevent, self.cfgData)
> +    def sq_task_skip(self, task):
> +        self.sq_running.add(task)
> +        self.sq_buildable.add(task)
> +        self.sq_task_completeoutright(task)
> +        self.sq_stats.taskSkipped()
> +        self.sq_stats.taskCompleted()
>  
> -            taskdepdata = self.build_taskdepdata(task)
> -
> -            taskdep = self.rqdata.dataCaches[mc].task_deps[taskfn]
> -            if 'fakeroot' in taskdep and taskname in
> taskdep['fakeroot'] and not self.cooker.configuration.dry_run:
> -                if not mc in self.rq.fakeworker:
> -                    self.rq.start_fakeworker(self, mc)
> -
> self.rq.fakeworker[mc].process.stdin.write(b"<runtask>" +
> pickle.dumps((taskfn, task, taskname, True,
> self.cooker.collection.get_file_appends(taskfn), taskdepdata, False))
> + b"</runtask>")
> -                self.rq.fakeworker[mc].process.stdin.flush()
> -            else:
> -                self.rq.worker[mc].process.stdin.write(b"<runtask>"
> + pickle.dumps((taskfn, task, taskname, True,
> self.cooker.collection.get_file_appends(taskfn), taskdepdata, False))
> + b"</runtask>")
> -                self.rq.worker[mc].process.stdin.flush()
> -
> -            self.build_stamps[task] = bb.build.stampfile(taskname,
> self.rqdata.dataCaches[mc], taskfn, noextra=True)
> -            self.build_stamps2.append(self.build_stamps[task])
> -            self.runq_running.add(task)
> -            self.stats.taskActive()
> -            if self.can_start_task():
> -                return True
> -
> -        if self.stats.active > 0:
> -            self.rq.read_workers()
> -            return self.rq.active_fds()
> -
> -        #for tid in self.sq_revdeps:
> -        #    if tid not in self.runq_running:
> -        #        buildable = tid in self.runq_buildable
> -        #        revdeps = self.sq_revdeps[tid]
> -        #        bb.warn("Found we didn't run %s %s %s" % (tid,
> buildable, str(revdeps))) -
> -        self.rq.scenequeue_covered = self.scenequeue_covered
> -        self.rq.scenequeue_notcovered = self.scenequeue_notcovered
> -
> -        logger.debug(1, 'We can skip tasks %s',
> "\n".join(sorted(self.rq.scenequeue_covered))) -
> -        self.rq.state = runQueueRunInit
> -
> -        completeevent = sceneQueueComplete(self.stats, self.rq)
> -        bb.event.fire(completeevent, self.cfgData)
> -
> -        return True
> -
> -    def runqueue_process_waitpid(self, task, status):
> -        RunQueueExecute.runqueue_process_waitpid(self, task, status)
> -
> -
> -    def build_taskdepdata(self, task):
> +    def sq_build_taskdepdata(self, task):
>          def getsetscenedeps(tid):
>              deps = set()
>              (mc, fn, taskname, _) = split_tid_mcfn(tid)
> @@ -2526,7 +2470,8 @@ class
> RunQueueExecuteScenequeue(RunQueueExecute): deps =
> getsetscenedeps(revdep) provides =
> self.rqdata.dataCaches[mc].fn_provides[taskfn] taskhash =
> self.rqdata.runtaskentries[revdep].hash
> -                taskdepdata[revdep] = [pn, taskname, fn, deps,
> provides, taskhash]
> +                unihash = self.rqdata.runtaskentries[revdep].unihash
> +                taskdepdata[revdep] = [pn, taskname, fn, deps,
> provides, taskhash, unihash] for revdep2 in deps:
>                      if revdep2 not in taskdepdata:
>                          additional.append(revdep2)
> @@ -2535,6 +2480,279 @@ class
> RunQueueExecuteScenequeue(RunQueueExecute): #bb.note("Task %s: " %
> task + str(taskdepdata).replace("], ", "],\n")) return taskdepdata
>  
> +    def check_setscenewhitelist(self, tid):
> +        # Check task that is going to run against the whitelist
> +        (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> +        # Ignore covered tasks
> +        if tid in self.tasks_covered:
> +            return False
> +        # Ignore stamped tasks
> +        if self.rq.check_stamp_task(tid, taskname,
> cache=self.stampcache):
> +            return False
> +        # Ignore noexec tasks
> +        taskdep = self.rqdata.dataCaches[mc].task_deps[taskfn]
> +        if 'noexec' in taskdep and taskname in taskdep['noexec']:
> +            return False
> +
> +        pn = self.rqdata.dataCaches[mc].pkg_fn[taskfn]
> +        if not check_setscene_enforce_whitelist(pn, taskname,
> self.rqdata.setscenewhitelist):
> +            if tid in self.rqdata.runq_setscene_tids:
> +                msg = 'Task %s.%s attempted to execute unexpectedly
> and should have been setscened' % (pn, taskname)
> +            else:
> +                msg = 'Task %s.%s attempted to execute unexpectedly'
> % (pn, taskname)
> +            logger.error(msg + '\nThis is usually due to missing
> setscene tasks. Those missing in this build were: %s' %
> pprint.pformat(self.scenequeue_notcovered))
> +            return True
> +        return False
> +
> +class SQData(object):
> +    def __init__(self):
> +        # SceneQueue dependencies
> +        self.sq_deps = {}
> +        # SceneQueue reverse dependencies
> +        self.sq_revdeps = {}
> +        # Injected inter-setscene task dependencies
> +        self.sq_harddeps = {}
> +        # Cache of stamp files so duplicates can't run in parallel
> +        self.stamps = {}
> +        # Setscene tasks directly depended upon by the build
> +        self.unskippable = set()
> +        # List of setscene tasks which aren't present
> +        self.outrightfail = set()
> +        # A list of normal tasks a setscene task covers
> +        self.sq_covered_tasks = {}
> +
> +def build_scenequeue_data(sqdata, rqdata, rq, cooker, stampcache,
> sqrq): +
> +    sq_revdeps = {}
> +    sq_revdeps_squash = {}
> +    sq_collated_deps = {}
> +
> +    # We need to construct a dependency graph for the setscene
> functions. Intermediate
> +    # dependencies between the setscene tasks only complicate the
> code. This code
> +    # therefore aims to collapse the huge runqueue dependency tree
> into a smaller one
> +    # only containing the setscene functions.
> +
> +    rqdata.init_progress_reporter.next_stage()
> +
> +    # First process the chains up to the first setscene task.
> +    endpoints = {}
> +    for tid in rqdata.runtaskentries:
> +        sq_revdeps[tid] =
> copy.copy(rqdata.runtaskentries[tid].revdeps)
> +        sq_revdeps_squash[tid] = set()
> +        if (len(sq_revdeps[tid]) == 0) and tid not in
> rqdata.runq_setscene_tids:
> +            #bb.warn("Added endpoint %s" % (tid))
> +            endpoints[tid] = set()
> +
> +    rqdata.init_progress_reporter.next_stage()
> +
> +    # Secondly process the chains between setscene tasks.
> +    for tid in rqdata.runq_setscene_tids:
> +        sq_collated_deps[tid] = set()
> +        #bb.warn("Added endpoint 2 %s" % (tid))
> +        for dep in rqdata.runtaskentries[tid].depends:
> +                if tid in sq_revdeps[dep]:
> +                    sq_revdeps[dep].remove(tid)
> +                if dep not in endpoints:
> +                    endpoints[dep] = set()
> +                #bb.warn("  Added endpoint 3 %s" % (dep))
> +                endpoints[dep].add(tid)
> +
> +    rqdata.init_progress_reporter.next_stage()
> +
> +    def process_endpoints(endpoints):
> +        newendpoints = {}
> +        for point, task in endpoints.items():
> +            tasks = set()
> +            if task:
> +                tasks |= task
> +            if sq_revdeps_squash[point]:
> +                tasks |= sq_revdeps_squash[point]
> +            if point not in rqdata.runq_setscene_tids:
> +                for t in tasks:
> +                    sq_collated_deps[t].add(point)
> +            sq_revdeps_squash[point] = set()
> +            if point in rqdata.runq_setscene_tids:
> +                sq_revdeps_squash[point] = tasks
> +                tasks = set()
> +                continue
> +            for dep in rqdata.runtaskentries[point].depends:
> +                if point in sq_revdeps[dep]:
> +                    sq_revdeps[dep].remove(point)
> +                if tasks:
> +                    sq_revdeps_squash[dep] |= tasks
> +                if len(sq_revdeps[dep]) == 0 and dep not in
> rqdata.runq_setscene_tids:
> +                    newendpoints[dep] = task
> +        if len(newendpoints) != 0:
> +            process_endpoints(newendpoints)
> +
> +    process_endpoints(endpoints)
> +
> +    rqdata.init_progress_reporter.next_stage()
> +
> +    # Build a list of tasks which are "unskippable"
> +    # These are direct endpoints referenced by the build upto and
> including setscene tasks
> +    # Take the build endpoints (no revdeps) and find the sstate
> tasks they depend upon
> +    new = True
> +    for tid in rqdata.runtaskentries:
> +        if len(rqdata.runtaskentries[tid].revdeps) == 0:
> +            sqdata.unskippable.add(tid)
> +    sqdata.unskippable |= sqrq.cantskip
> +    while new:
> +        new = False
> +        orig = sqdata.unskippable.copy()
> +        for tid in sorted(orig, reverse=True):
> +            if tid in rqdata.runq_setscene_tids:
> +                continue
> +            if len(rqdata.runtaskentries[tid].depends) == 0:
> +                # These are tasks which have no setscene tasks in
> their chain, need to mark as directly buildable
> +                sqrq.setbuildable(tid)
> +            sqdata.unskippable |= rqdata.runtaskentries[tid].depends
> +            if sqdata.unskippable != orig:
> +                new = True
> +
> +    sqrq.tasks_scenequeue_done |=
> sqdata.unskippable.difference(rqdata.runq_setscene_tids) +
> +
> rqdata.init_progress_reporter.next_stage(len(rqdata.runtaskentries)) +
> +    # Sanity check all dependencies could be changed to setscene
> task references
> +    for taskcounter, tid in enumerate(rqdata.runtaskentries):
> +        if tid in rqdata.runq_setscene_tids:
> +            pass
> +        elif len(sq_revdeps_squash[tid]) != 0:
> +            bb.msg.fatal("RunQueue", "Something went badly wrong
> during scenequeue generation, aborting. Please report this problem.")
> +        else:
> +            del sq_revdeps_squash[tid]
> +        rqdata.init_progress_reporter.update(taskcounter)
> +
> +    rqdata.init_progress_reporter.next_stage()
> +
> +    # Resolve setscene inter-task dependencies
> +    # e.g. do_sometask_setscene[depends] =
> "targetname:do_someothertask_setscene"
> +    # Note that anything explicitly depended upon will have its
> reverse dependencies removed to avoid circular dependencies
> +    for tid in rqdata.runq_setscene_tids:
> +        (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> +        realtid = tid + "_setscene"
> +        idepends = rqdata.taskData[mc].taskentries[realtid].idepends
> +        sqdata.stamps[tid] = bb.build.stampfile(taskname +
> "_setscene", rqdata.dataCaches[mc], taskfn, noextra=True)
> +        for (depname, idependtask) in idepends:
> +
> +            if depname not in rqdata.taskData[mc].build_targets:
> +                continue
> +
> +            depfn = rqdata.taskData[mc].build_targets[depname][0]
> +            if depfn is None:
> +                continue
> +            deptid = depfn + ":" + idependtask.replace("_setscene",
> "")
> +            if deptid not in rqdata.runtaskentries:
> +                bb.msg.fatal("RunQueue", "Task %s depends upon
> non-existent task %s:%s" % (realtid, depfn, idependtask)) +
> +            if not deptid in sqdata.sq_harddeps:
> +                sqdata.sq_harddeps[deptid] = set()
> +            sqdata.sq_harddeps[deptid].add(tid)
> +
> +            sq_revdeps_squash[tid].add(deptid)
> +            # Have to zero this to avoid circular dependencies
> +            sq_revdeps_squash[deptid] = set()
> +
> +    rqdata.init_progress_reporter.next_stage()
> +
> +    for task in sqdata.sq_harddeps:
> +        for dep in sqdata.sq_harddeps[task]:
> +            sq_revdeps_squash[dep].add(task)
> +
> +    rqdata.init_progress_reporter.next_stage()
> +
> +    #for tid in sq_revdeps_squash:
> +    #    data = ""
> +    #    for dep in sq_revdeps_squash[tid]:
> +    #        data = data + "\n   %s" % dep
> +    #    bb.warn("Task %s_setscene: is %s " % (tid, data))
> +
> +    sqdata.sq_revdeps = sq_revdeps_squash
> +    sqdata.sq_covered_tasks = sq_collated_deps
> +
> +    # Build reverse version of revdeps to populate deps structure
> +    for tid in sqdata.sq_revdeps:
> +        sqdata.sq_deps[tid] = set()
> +    for tid in sqdata.sq_revdeps:
> +        for dep in sqdata.sq_revdeps[tid]:
> +            sqdata.sq_deps[dep].add(tid)
> +
> +    rqdata.init_progress_reporter.next_stage()
> +
> +    sqdata.multiconfigs = set()
> +    for tid in sqdata.sq_revdeps:
> +        sqdata.multiconfigs.add(mc_from_tid(tid))
> +        if len(sqdata.sq_revdeps[tid]) == 0:
> +            sqrq.sq_buildable.add(tid)
> +
> +    rqdata.init_progress_reporter.finish()
> +
> +    sqdata.noexec = set()
> +    sqdata.stamppresent = set()
> +    sqdata.valid = set()
> +
> +    update_scenequeue_data(sqdata.sq_revdeps, sqdata, rqdata, rq,
> cooker, stampcache, sqrq) +
> +def update_scenequeue_data(tids, sqdata, rqdata, rq, cooker,
> stampcache, sqrq): +
> +    tocheck = set()
> +
> +    for tid in sorted(tids):
> +        if tid in sqdata.stamppresent:
> +            sqdata.stamppresent.remove(tid)
> +        if tid in sqdata.valid:
> +            sqdata.valid.remove(tid)
> +
> +        (mc, fn, taskname, taskfn) = split_tid_mcfn(tid)
> +
> +        taskdep = rqdata.dataCaches[mc].task_deps[taskfn]
> +
> +        if 'noexec' in taskdep and taskname in taskdep['noexec']:
> +            sqdata.noexec.add(tid)
> +            sqrq.sq_task_skip(tid)
> +            bb.build.make_stamp(taskname + "_setscene",
> rqdata.dataCaches[mc], taskfn)
> +            continue
> +
> +        if rq.check_stamp_task(tid, taskname + "_setscene",
> cache=stampcache):
> +            logger.debug(2, 'Setscene stamp current for task %s',
> tid)
> +            sqdata.stamppresent.add(tid)
> +            sqrq.sq_task_skip(tid)
> +            continue
> +
> +        if rq.check_stamp_task(tid, taskname, recurse = True,
> cache=stampcache):
> +            logger.debug(2, 'Normal stamp current for task %s', tid)
> +            sqdata.stamppresent.add(tid)
> +            sqrq.sq_task_skip(tid)
> +            continue
> +
> +        tocheck.add(tid)
> +
> +    sqdata.valid |= rq.validate_hashes(tocheck, cooker.data,
> len(sqdata.stamppresent), False) +
> +    sqdata.hashes = {}
> +    for mc in sorted(sqdata.multiconfigs):
> +        for tid in sorted(sqdata.sq_revdeps):
> +            if mc_from_tid(tid) != mc:
> +                continue
> +            if tid in sqdata.stamppresent:
> +                continue
> +            if tid in sqdata.valid:
> +                continue
> +            if tid in sqdata.noexec:
> +                continue
> +            if tid in sqrq.scenequeue_notcovered:
> +                continue
> +            sqdata.outrightfail.add(tid)
> +
> +            h = pending_hash_index(tid, rqdata)
> +            if h not in sqdata.hashes:
> +                sqdata.hashes[h] = tid
> +            else:
> +                sqrq.sq_deferred[tid] = sqdata.hashes[h]
> +                bb.warn("Deferring %s after %s" % (tid,
> sqdata.hashes[h])) +
> +
>  class TaskFailure(Exception):
>      """
>      Exception raised when a task in a runqueue fails
> @@ -2641,6 +2859,15 @@ class runQueueTaskSkipped(runQueueEvent):
>          runQueueEvent.__init__(self, task, stats, rq)
>          self.reason = reason
>  
> +class taskUniHashUpdate(bb.event.Event):
> +    """
> +    Base runQueue event class
> +    """
> +    def __init__(self, task, unihash):
> +        self.taskid = task
> +        self.unihash = unihash
> +        bb.event.Event.__init__(self)
> +
>  class runQueuePipe():
>      """
>      Abstraction for a pipe between a worker thread and the server
> @@ -2683,6 +2910,8 @@ class runQueuePipe():
>                  except ValueError as e:
>                      bb.msg.fatal("RunQueue", "failed load pickle
> '%s': '%s'" % (e, self.queue[7:index]))
> bb.event.fire_from_worker(event, self.d)
> +                if isinstance(event, taskUniHashUpdate):
> +
> self.rqexec.updated_taskhash_queue.append((event.taskid,
> event.unihash)) found = True self.queue = self.queue[index+8:]
>                  index = self.queue.find(b"</event>")
> diff --git a/bitbake/lib/bb/server/__init__.py
> b/bitbake/lib/bb/server/__init__.py index 5a3fba9..b6f7513 100644
> --- a/bitbake/lib/bb/server/__init__.py
> +++ b/bitbake/lib/bb/server/__init__.py
> @@ -5,17 +5,5 @@
>  # Copyright (C) 2006 - 2008  Richard Purdie
>  # Copyright (C) 2013         Alexandru Damian
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -
> -
> diff --git a/bitbake/lib/bb/server/process.py
> b/bitbake/lib/bb/server/process.py index 4e0d9c2..69aae62 100644
> --- a/bitbake/lib/bb/server/process.py
> +++ b/bitbake/lib/bb/server/process.py
> @@ -3,18 +3,8 @@
>  #
>  # Copyright (C) 2010 Bob Foerster <robert@erafx.com>
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  """
>      This module implements a multiprocessing.Process based server
> for bitbake. @@ -130,6 +120,7 @@ class
> ProcessServer(multiprocessing.Process):
> bb.utils.set_process_name("Cooker") 
>          ready = []
> +        newconnections = []
>  
>          self.controllersock = False
>          fds = [self.sock]
> @@ -138,37 +129,48 @@ class ProcessServer(multiprocessing.Process):
>          print("Entering server connection loop")
>  
>          def disconnect_client(self, fds):
> -            if not self.haveui:
> -                return
>              print("Disconnecting Client")
> -            fds.remove(self.controllersock)
> -            fds.remove(self.command_channel)
> -            bb.event.unregister_UIHhandler(self.event_handle, True)
> -            self.command_channel_reply.writer.close()
> -            self.event_writer.writer.close()
> -            del self.event_writer
> -            self.controllersock.close()
> -            self.controllersock = False
> -            self.haveui = False
> -            self.lastui = time.time()
> -            self.cooker.clientComplete()
> -            if self.timeout is None:
> +            if self.controllersock:
> +                fds.remove(self.controllersock)
> +                self.controllersock.close()
> +                self.controllersock = False
> +            if self.haveui:
> +                fds.remove(self.command_channel)
> +                bb.event.unregister_UIHhandler(self.event_handle,
> True)
> +                self.command_channel_reply.writer.close()
> +                self.event_writer.writer.close()
> +                self.command_channel.close()
> +                self.command_channel = False
> +                del self.event_writer
> +                self.lastui = time.time()
> +                self.cooker.clientComplete()
> +                self.haveui = False
> +            ready = select.select(fds,[],[],0)[0]
> +            if newconnections:
> +                print("Starting new client")
> +                conn = newconnections.pop(-1)
> +                fds.append(conn)
> +                self.controllersock = conn
> +            elif self.timeout is None and not ready:
>                  print("No timeout, exiting.")
>                  self.quit = True
>  
>          while not self.quit:
>              if self.sock in ready:
> -                self.controllersock, address = self.sock.accept()
> -                if self.haveui:
> -                    print("Dropping connection attempt as we have a
> UI %s" % (str(ready)))
> -                    self.controllersock.close()
> -                else:
> -                    print("Accepting %s" % (str(ready)))
> -                    fds.append(self.controllersock)
> +                while select.select([self.sock],[],[],0)[0]:
> +                    controllersock, address = self.sock.accept()
> +                    if self.controllersock:
> +                        print("Queuing %s (%s)" % (str(ready),
> str(newconnections)))
> +                        newconnections.append(controllersock)
> +                    else:
> +                        print("Accepting %s (%s)" % (str(ready),
> str(newconnections)))
> +                        self.controllersock = controllersock
> +                        fds.append(controllersock)
>              if self.controllersock in ready:
>                  try:
> -                    print("Connecting Client")
> +                    print("Processing Client")
>                      ui_fds = recvfds(self.controllersock, 3)
> +                    print("Connecting Client")
>  
>                      # Where to write events to
>                      writer = ConnectionWriter(ui_fds[0])
> @@ -239,6 +241,12 @@ class ProcessServer(multiprocessing.Process):
>          while not lock:
>              with bb.utils.timeout(3):
>                  lock = bb.utils.lockfile(lockfile, shared=False,
> retry=False, block=True)
> +                if lock:
> +                    # We hold the lock so we can remove the file
> (hide stale pid data)
> +                    bb.utils.remove(lockfile)
> +                    bb.utils.unlockfile(lock)
> +                    return
> +
>                  if not lock:
>                      # Some systems may not have lsof available
>                      procs = None
> @@ -259,10 +267,6 @@ class ProcessServer(multiprocessing.Process):
>                      if procs:
>                          msg += ":\n%s" % str(procs)
>                      print(msg)
> -                    return
> -        # We hold the lock so we can remove the file (hide stale pid
> data)
> -        bb.utils.remove(lockfile)
> -        bb.utils.unlockfile(lock)
>  
>      def idle_commands(self, delay, fds=None):
>          nextsleep = delay
> @@ -398,36 +402,45 @@ class BitBakeServer(object):
>          os.close(self.readypipein)
>  
>          ready = ConnectionReader(self.readypipe)
> -        r = ready.poll(30)
> +        r = ready.poll(5)
> +        if not r:
> +            bb.note("Bitbake server didn't start within 5 seconds,
> waiting for 90")
> +            r = ready.poll(90)
>          if r:
>              try:
>                  r = ready.get()
>              except EOFError:
>                  # Trap the child exitting/closing the pipe and error
> out r = None
> -        if not r or r != "ready":
> +        if not r or r[0] != "r":
>              ready.close()
> -            bb.error("Unable to start bitbake server")
> +            bb.error("Unable to start bitbake server (%s)" % str(r))
>              if os.path.exists(logfile):
>                  logstart_re = re.compile(self.start_log_format %
> ('([0-9]+)', '([0-9-]+ [0-9:.]+)')) started = False
>                  lines = []
> +                lastlines = []
>                  with open(logfile, "r") as f:
>                      for line in f:
>                          if started:
>                              lines.append(line)
>                          else:
> +                            lastlines.append(line)
>                              res = logstart_re.match(line.rstrip())
>                              if res:
>                                  ldatetime =
> datetime.datetime.strptime(res.group(2),
> self.start_log_datetime_format) if ldatetime >= startdatetime:
> started = True lines.append(line)
> +                        if len(lastlines) > 60:
> +                            lastlines = lastlines[-60:]
>                  if lines:
> -                    if len(lines) > 10:
> -                        bb.error("Last 10 lines of server log for
> this session (%s):\n%s" % (logfile, "".join(lines[-10:])))
> +                    if len(lines) > 60:
> +                        bb.error("Last 60 lines of server log for
> this session (%s):\n%s" % (logfile, "".join(lines[-60:]))) else:
>                          bb.error("Server log for this session
> (%s):\n%s" % (logfile, "".join(lines)))
> +                elif lastlines:
> +                        bb.error("Server didn't start, last 60
> loglines (%s):\n%s" % (logfile, "".join(lastlines))) else:
>                  bb.error("%s doesn't exist" % logfile)
>  
> @@ -437,17 +450,24 @@ class BitBakeServer(object):
>  
>      def _startServer(self):
>          print(self.start_log_format % (os.getpid(),
> datetime.datetime.now().strftime(self.start_log_datetime_format)))
> +        sys.stdout.flush()
> +
>          server = ProcessServer(self.bitbake_lock, self.sock,
> self.sockname)
> self.configuration.setServerRegIdleCallback(server.register_idle_function)
> os.close(self.readypipe) writer = ConnectionWriter(self.readypipein)
> -        self.cooker = bb.cooker.BBCooker(self.configuration,
> self.featureset)
> -        writer.send("ready")
> +        try:
> +            self.cooker = bb.cooker.BBCooker(self.configuration,
> self.featureset)
> +        except bb.BBHandledException:
> +            return None
> +        writer.send("r")
>          writer.close()
>          server.cooker = self.cooker
>          server.server_timeout = self.configuration.server_timeout
>          server.xmlrpcinterface = self.configuration.xmlrpcinterface
>          print("Started bitbake server pid %d" % os.getpid())
> +        sys.stdout.flush()
> +
>          server.start()
>  
>  def connectProcessServer(sockname, featureset):
> @@ -459,10 +479,20 @@ def connectProcessServer(sockname, featureset):
>      readfd = writefd = readfd1 = writefd1 = readfd2 = writefd2 = None
>      eq = command_chan_recv = command_chan = None
>  
> +    sock.settimeout(10)
> +
>      try:
>          try:
>              os.chdir(os.path.dirname(sockname))
> -            sock.connect(os.path.basename(sockname))
> +            finished = False
> +            while not finished:
> +                try:
> +                    sock.connect(os.path.basename(sockname))
> +                    finished = True
> +                except IOError as e:
> +                    if e.errno == errno.EWOULDBLOCK:
> +                        pass
> +                    raise
>          finally:
>              os.chdir(cwd)
>  
> @@ -493,7 +523,8 @@ def connectProcessServer(sockname, featureset):
>              command_chan.close()
>          for i in [writefd, readfd1, writefd2]:
>              try:
> -                os.close(i)
> +                if i:
> +                    os.close(i)
>              except OSError:
>                  pass
>          sock.close()
> diff --git a/bitbake/lib/bb/server/xmlrpcclient.py
> b/bitbake/lib/bb/server/xmlrpcclient.py index 4661a9e..c054c3c 100644
> --- a/bitbake/lib/bb/server/xmlrpcclient.py
> +++ b/bitbake/lib/bb/server/xmlrpcclient.py
> @@ -4,18 +4,8 @@
>  # Copyright (C) 2006 - 2007  Michael 'Mickey' Lauer
>  # Copyright (C) 2006 - 2008  Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  import sys
> diff --git a/bitbake/lib/bb/server/xmlrpcserver.py
> b/bitbake/lib/bb/server/xmlrpcserver.py index 875b128..54fa32f 100644
> --- a/bitbake/lib/bb/server/xmlrpcserver.py
> +++ b/bitbake/lib/bb/server/xmlrpcserver.py
> @@ -4,18 +4,8 @@
>  # Copyright (C) 2006 - 2007  Michael 'Mickey' Lauer
>  # Copyright (C) 2006 - 2008  Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  import sys
> diff --git a/bitbake/lib/bb/siggen.py b/bitbake/lib/bb/siggen.py
> index fdbb2a3..a4bb1ff 100644
> --- a/bitbake/lib/bb/siggen.py
> +++ b/bitbake/lib/bb/siggen.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  import hashlib
>  import logging
>  import os
> @@ -8,6 +12,8 @@ import bb.data
>  import difflib
>  import simplediff
>  from bb.checksum import FileChecksumCache
> +from bb import runqueue
> +import hashserv
>  
>  logger = logging.getLogger('BitBake.SigGen')
>  
> @@ -37,12 +43,18 @@ class SignatureGenerator(object):
>          self.runtaskdeps = {}
>          self.file_checksum_values = {}
>          self.taints = {}
> +        self.unitaskhashes = {}
> +        self.setscenetasks = {}
>  
>      def finalise(self, fn, d, varient):
>          return
>  
> -    def get_taskhash(self, fn, task, deps, dataCache):
> -        return "0"
> +    def get_unihash(self, tid):
> +        return self.taskhash[tid]
> +
> +    def get_taskhash(self, tid, deps, dataCache):
> +        self.taskhash[tid] =
> hashlib.sha256(tid.encode("utf-8")).hexdigest()
> +        return self.taskhash[tid]
>  
>      def writeout_file_checksum_cache(self):
>          """Write/update the file checksum cache onto disk"""
> @@ -64,14 +76,25 @@ class SignatureGenerator(object):
>          return
>  
>      def get_taskdata(self):
> -        return (self.runtaskdeps, self.taskhash,
> self.file_checksum_values, self.taints, self.basehash)
> +        return (self.runtaskdeps, self.taskhash,
> self.file_checksum_values, self.taints, self.basehash,
> self.unitaskhashes, self.setscenetasks) def set_taskdata(self, data):
> -        self.runtaskdeps, self.taskhash, self.file_checksum_values,
> self.taints, self.basehash = data
> +        self.runtaskdeps, self.taskhash, self.file_checksum_values,
> self.taints, self.basehash, self.unitaskhashes, self.setscenetasks =
> data def reset(self, data):
>          self.__init__(data)
>  
> +    def get_taskhashes(self):
> +        return self.taskhash, self.unitaskhashes
> +
> +    def set_taskhashes(self, hashes):
> +        self.taskhash, self.unitaskhashes = hashes
> +
> +    def save_unitaskhashes(self):
> +        return
> +
> +    def set_setscene_tasks(self, setscene_tasks):
> +        return
>  
>  class SignatureGeneratorBasic(SignatureGenerator):
>      """
> @@ -87,7 +110,7 @@ class SignatureGeneratorBasic(SignatureGenerator):
>          self.taints = {}
>          self.gendeps = {}
>          self.lookupcache = {}
> -        self.pkgnameextract = re.compile("(?P<fn>.*)\..*")
> +        self.setscenetasks = {}
>          self.basewhitelist =
> set((data.getVar("BB_HASHBASE_WHITELIST") or "").split())
> self.taskwhitelist = None self.init_rundepcheck(data)
> @@ -98,6 +121,9 @@ class SignatureGeneratorBasic(SignatureGenerator):
>          else:
>              self.checksum_cache = None
>  
> +        self.unihash_cache = bb.cache.SimpleCache("1")
> +        self.unitaskhashes = self.unihash_cache.init_cache(data,
> "bb_unihashes.dat", {}) +
>      def init_rundepcheck(self, data):
>          self.taskwhitelist = data.getVar("BB_HASHTASK_WHITELIST") or
> None if self.taskwhitelist:
> @@ -113,10 +139,16 @@ class
> SignatureGeneratorBasic(SignatureGenerator): taskdeps, basehash =
> bb.data.generate_dependency_hash(tasklist, gendeps, lookupcache,
> self.basewhitelist, fn) for task in tasklist:
> -            k = fn + "." + task
> -            if not ignore_mismatch and k in self.basehash and
> self.basehash[k] != basehash[k]:
> -                bb.error("When reparsing %s, the basehash value
> changed from %s to %s. The metadata is not deterministic and this
> needs to be fixed." % (k, self.basehash[k], basehash[k]))
> -            self.basehash[k] = basehash[k]
> +            tid = fn + ":" + task
> +            if not ignore_mismatch and tid in self.basehash and
> self.basehash[tid] != basehash[tid]:
> +                bb.error("When reparsing %s, the basehash value
> changed from %s to %s. The metadata is not deterministic and this
> needs to be fixed." % (tid, self.basehash[tid], basehash[tid]))
> +                bb.error("The following commands may help:")
> +                cmd = "$ bitbake %s -c%s" % (d.getVar('PN'), task)
> +                # Make sure sigdata is dumped before run printdiff
> +                bb.error("%s -Snone" % cmd)
> +                bb.error("Then:")
> +                bb.error("%s -Sprintdiff\n" % cmd)
> +            self.basehash[tid] = basehash[tid]
>  
>          self.taskdeps[fn] = taskdeps
>          self.gendeps[fn] = gendeps
> @@ -124,6 +156,9 @@ class SignatureGeneratorBasic(SignatureGenerator):
>  
>          return taskdeps
>  
> +    def set_setscene_tasks(self, setscene_tasks):
> +        self.setscenetasks = setscene_tasks
> +
>      def finalise(self, fn, d, variant):
>  
>          mc = d.getVar("__BBMULTICONFIG", False) or ""
> @@ -143,7 +178,7 @@ class SignatureGeneratorBasic(SignatureGenerator):
>          #    self.dump_sigtask(fn, task, d.getVar("STAMP"), False)
>  
>          for task in taskdeps:
> -            d.setVar("BB_BASEHASH_task-%s" % task, self.basehash[fn
> + "." + task])
> +            d.setVar("BB_BASEHASH_task-%s" % task, self.basehash[fn
> + ":" + task]) 
>      def rundep_check(self, fn, recipename, task, dep, depname,
> dataCache): # Return True if we should keep the dependency, False to
> drop it @@ -163,31 +198,26 @@ class
> SignatureGeneratorBasic(SignatureGenerator): pass
>          return taint
>  
> -    def get_taskhash(self, fn, task, deps, dataCache):
> +    def get_taskhash(self, tid, deps, dataCache):
>  
> -        mc = ''
> -        if fn.startswith('multiconfig:'):
> -            mc = fn.split(':')[1]
> -        k = fn + "." + task
> +        (mc, _, task, fn) = bb.runqueue.split_tid_mcfn(tid)
>  
> -        data = dataCache.basetaskhash[k]
> -        self.basehash[k] = data
> -        self.runtaskdeps[k] = []
> -        self.file_checksum_values[k] = []
> +        data = dataCache.basetaskhash[tid]
> +        self.basehash[tid] = data
> +        self.runtaskdeps[tid] = []
> +        self.file_checksum_values[tid] = []
>          recipename = dataCache.pkg_fn[fn]
>          for dep in sorted(deps, key=clean_basepath):
> -            pkgname = self.pkgnameextract.search(dep).group('fn')
> -            if mc:
> -                depmc = pkgname.split(':')[1]
> -                if mc != depmc:
> -                    continue
> -            depname = dataCache.pkg_fn[pkgname]
> +            (depmc, _, deptaskname, depfn) =
> bb.runqueue.split_tid_mcfn(dep)
> +            if mc != depmc:
> +                continue
> +            depname = dataCache.pkg_fn[depfn]
>              if not self.rundep_check(fn, recipename, task, dep,
> depname, dataCache): continue
>              if dep not in self.taskhash:
>                  bb.fatal("%s is not in taskhash, caller isn't
> calling in dependency order?" % dep)
> -            data = data + self.taskhash[dep]
> -            self.runtaskdeps[k].append(dep)
> +            data = data + self.get_unihash(dep)
> +            self.runtaskdeps[tid].append(dep)
>  
>          if task in dataCache.file_checksums[fn]:
>              if self.checksum_cache:
> @@ -195,7 +225,7 @@ class SignatureGeneratorBasic(SignatureGenerator):
>              else:
>                  checksums =
> bb.fetch2.get_file_checksums(dataCache.file_checksums[fn][task],
> recipename) for (f,cs) in checksums:
> -                self.file_checksum_values[k].append((f,cs))
> +                self.file_checksum_values[tid].append((f,cs))
>                  if cs:
>                      data = data + cs
>  
> @@ -205,16 +235,16 @@ class
> SignatureGeneratorBasic(SignatureGenerator): import uuid
>              taint = str(uuid.uuid4())
>              data = data + taint
> -            self.taints[k] = "nostamp:" + taint
> +            self.taints[tid] = "nostamp:" + taint
>  
>          taint = self.read_taint(fn, task, dataCache.stamp[fn])
>          if taint:
>              data = data + taint
> -            self.taints[k] = taint
> -            logger.warning("%s is tainted from a forced run" % k)
> +            self.taints[tid] = taint
> +            logger.warning("%s is tainted from a forced run" % tid)
>  
> -        h = hashlib.md5(data.encode("utf-8")).hexdigest()
> -        self.taskhash[k] = h
> +        h = hashlib.sha256(data.encode("utf-8")).hexdigest()
> +        self.taskhash[tid] = h
>          #d.setVar("BB_TASKHASH_task-%s" % task, taskhash[task])
>          return h
>  
> @@ -227,17 +257,20 @@ class
> SignatureGeneratorBasic(SignatureGenerator):
> bb.fetch2.fetcher_parse_save() bb.fetch2.fetcher_parse_done()
>  
> +    def save_unitaskhashes(self):
> +        self.unihash_cache.save(self.unitaskhashes)
> +
>      def dump_sigtask(self, fn, task, stampbase, runtime):
>  
> -        k = fn + "." + task
> +        tid = fn + ":" + task
>          referencestamp = stampbase
>          if isinstance(runtime, str) and
> runtime.startswith("customfile"): sigfile = stampbase
>              referencestamp = runtime[11:]
> -        elif runtime and k in self.taskhash:
> -            sigfile = stampbase + "." + task + ".sigdata" + "." +
> self.taskhash[k]
> +        elif runtime and tid in self.taskhash:
> +            sigfile = stampbase + "." + task + ".sigdata" + "." +
> self.get_unihash(tid) else:
> -            sigfile = stampbase + "." + task + ".sigbasedata" + "."
> + self.basehash[k]
> +            sigfile = stampbase + "." + task + ".sigbasedata" + "."
> + self.basehash[tid] 
>          bb.utils.mkdirhier(os.path.dirname(sigfile))
>  
> @@ -246,7 +279,7 @@ class SignatureGeneratorBasic(SignatureGenerator):
>          data['basewhitelist'] = self.basewhitelist
>          data['taskwhitelist'] = self.taskwhitelist
>          data['taskdeps'] = self.taskdeps[fn][task]
> -        data['basehash'] = self.basehash[k]
> +        data['basehash'] = self.basehash[tid]
>          data['gendeps'] = {}
>          data['varvals'] = {}
>          data['varvals'][task] = self.lookupcache[fn][task]
> @@ -256,30 +289,31 @@ class
> SignatureGeneratorBasic(SignatureGenerator): data['gendeps'][dep] =
> self.gendeps[fn][dep] data['varvals'][dep] = self.lookupcache[fn][dep]
>  
> -        if runtime and k in self.taskhash:
> -            data['runtaskdeps'] = self.runtaskdeps[k]
> -            data['file_checksum_values'] = [(os.path.basename(f),
> cs) for f,cs in self.file_checksum_values[k]]
> +        if runtime and tid in self.taskhash:
> +            data['runtaskdeps'] = self.runtaskdeps[tid]
> +            data['file_checksum_values'] = [(os.path.basename(f),
> cs) for f,cs in self.file_checksum_values[tid]] data['runtaskhashes']
> = {} for dep in data['runtaskdeps']:
> -                data['runtaskhashes'][dep] = self.taskhash[dep]
> -            data['taskhash'] = self.taskhash[k]
> +                data['runtaskhashes'][dep] = self.get_unihash(dep)
> +            data['taskhash'] = self.taskhash[tid]
> +            data['unihash'] = self.get_unihash(tid)
>  
>          taint = self.read_taint(fn, task, referencestamp)
>          if taint:
>              data['taint'] = taint
>  
> -        if runtime and k in self.taints:
> -            if 'nostamp:' in self.taints[k]:
> -                data['taint'] = self.taints[k]
> +        if runtime and tid in self.taints:
> +            if 'nostamp:' in self.taints[tid]:
> +                data['taint'] = self.taints[tid]
>  
>          computed_basehash = calc_basehash(data)
> -        if computed_basehash != self.basehash[k]:
> -            bb.error("Basehash mismatch %s versus %s for %s" %
> (computed_basehash, self.basehash[k], k))
> -        if runtime and k in self.taskhash:
> +        if computed_basehash != self.basehash[tid]:
> +            bb.error("Basehash mismatch %s versus %s for %s" %
> (computed_basehash, self.basehash[tid], tid))
> +        if runtime and tid in self.taskhash:
>              computed_taskhash = calc_taskhash(data)
> -            if computed_taskhash != self.taskhash[k]:
> -                bb.error("Taskhash mismatch %s versus %s for %s" %
> (computed_taskhash, self.taskhash[k], k))
> -                sigfile = sigfile.replace(self.taskhash[k],
> computed_taskhash)
> +            if computed_taskhash != self.taskhash[tid]:
> +                bb.error("Taskhash mismatch %s versus %s for %s" %
> (computed_taskhash, self.taskhash[tid], tid))
> +                sigfile = sigfile.replace(self.taskhash[tid],
> computed_taskhash) 
>          fd, tmpfile = tempfile.mkstemp(dir=os.path.dirname(sigfile),
> prefix="sigtask.") try:
> @@ -299,30 +333,34 @@ class
> SignatureGeneratorBasic(SignatureGenerator): if fn in self.taskdeps:
>              for task in self.taskdeps[fn]:
>                  tid = fn + ":" + task
> -                (mc, _, _) = bb.runqueue.split_tid(tid)
> -                k = fn + "." + task
> -                if k not in self.taskhash:
> +                mc = bb.runqueue.mc_from_tid(tid)
> +                if tid not in self.taskhash:
>                      continue
> -                if dataCaches[mc].basetaskhash[k] !=
> self.basehash[k]:
> -                    bb.error("Bitbake's cached basehash does not
> match the one we just generated (%s)!" % k)
> -                    bb.error("The mismatched hashes were %s and %s"
> % (dataCaches[mc].basetaskhash[k], self.basehash[k]))
> +                if dataCaches[mc].basetaskhash[tid] !=
> self.basehash[tid]:
> +                    bb.error("Bitbake's cached basehash does not
> match the one we just generated (%s)!" % tid)
> +                    bb.error("The mismatched hashes were %s and %s"
> % (dataCaches[mc].basetaskhash[tid], self.basehash[tid]))
> self.dump_sigtask(fn, task, dataCaches[mc].stamp[fn], True) 
>  class SignatureGeneratorBasicHash(SignatureGeneratorBasic):
>      name = "basichash"
>  
> +    def get_stampfile_hash(self, tid):
> +        if tid in self.taskhash:
> +            return self.taskhash[tid]
> +
> +        # If task is not in basehash, then error
> +        return self.basehash[tid]
> +
>      def stampfile(self, stampbase, fn, taskname, extrainfo,
> clean=False): if taskname != "do_setscene" and
> taskname.endswith("_setscene"):
> -            k = fn + "." + taskname[:-9]
> +            tid = fn + ":" + taskname[:-9]
>          else:
> -            k = fn + "." + taskname
> +            tid = fn + ":" + taskname
>          if clean:
>              h = "*"
> -        elif k in self.taskhash:
> -            h = self.taskhash[k]
>          else:
> -            # If k is not in basehash, then error
> -            h = self.basehash[k]
> +            h = self.get_stampfile_hash(tid)
> +
>          return ("%s.%s.%s.%s" % (stampbase, taskname, h,
> extrainfo)).rstrip('.') 
>      def stampcleanmask(self, stampbase, fn, taskname, extrainfo):
> @@ -332,6 +370,172 @@ class
> SignatureGeneratorBasicHash(SignatureGeneratorBasic):
> bb.note("Tainting hash to force rebuild of task %s, %s" % (fn, task))
> bb.build.write_taint(task, d, fn) 
> +class SignatureGeneratorUniHashMixIn(object):
> +    def get_taskdata(self):
> +        return (self.server, self.method) + super().get_taskdata()
> +
> +    def set_taskdata(self, data):
> +        self.server, self.method = data[:2]
> +        super().set_taskdata(data[2:])
> +
> +    def client(self):
> +        if getattr(self, '_client', None) is None:
> +            self._client = hashserv.create_client(self.server)
> +        return self._client
> +
> +    def __get_task_unihash_key(self, tid):
> +        # TODO: The key only *needs* to be the taskhash, the tid is
> just
> +        # convenient
> +        return '%s:%s' % (tid.rsplit("/", 1)[1], self.taskhash[tid])
> +
> +    def get_stampfile_hash(self, tid):
> +        if tid in self.taskhash:
> +            # If a unique hash is reported, use it as the stampfile
> hash. This
> +            # ensures that if a task won't be re-run if the taskhash
> changes,
> +            # but it would result in the same output hash
> +            unihash =
> self.unitaskhashes.get(self.__get_task_unihash_key(tid), None)
> +            if unihash is not None:
> +                return unihash
> +
> +        return super().get_stampfile_hash(tid)
> +
> +    def set_unihash(self, tid, unihash):
> +        self.unitaskhashes[self.__get_task_unihash_key(tid)] =
> unihash +
> +    def get_unihash(self, tid):
> +        taskhash = self.taskhash[tid]
> +
> +        # If its not a setscene task we can return
> +        if self.setscenetasks and tid not in self.setscenetasks:
> +            return taskhash
> +
> +        key = self.__get_task_unihash_key(tid)
> +
> +        # TODO: This cache can grow unbounded. It probably only
> needs to keep
> +        # for each task
> +        unihash = self.unitaskhashes.get(key, None)
> +        if unihash is not None:
> +            return unihash
> +
> +        # In the absence of being able to discover a unique hash
> from the
> +        # server, make it be equivalent to the taskhash. The unique
> "hash" only
> +        # really needs to be a unique string (not even necessarily a
> hash), but
> +        # making it match the taskhash has a few advantages:
> +        #
> +        # 1) All of the sstate code that assumes hashes can be the
> same
> +        # 2) It provides maximal compatibility with builders that
> don't use
> +        #    an equivalency server
> +        # 3) The value is easy for multiple independent builders to
> derive the
> +        #    same unique hash from the same input. This means that
> if the
> +        #    independent builders find the same taskhash, but it
> isn't reported
> +        #    to the server, there is a better chance that they will
> agree on
> +        #    the unique hash.
> +        unihash = taskhash
> +
> +        try:
> +            data = self.client().get_unihash(self.method,
> self.taskhash[tid])
> +            if data:
> +                unihash = data
> +                # A unique hash equal to the taskhash is not very
> interesting,
> +                # so it is reported it at debug level 2. If they
> differ, that
> +                # is much more interesting, so it is reported at
> debug level 1
> +                bb.debug((1, 2)[unihash == taskhash], 'Found unihash
> %s in place of %s for %s from %s' % (unihash, taskhash, tid,
> self.server))
> +            else:
> +                bb.debug(2, 'No reported unihash for %s:%s from %s'
> % (tid, taskhash, self.server))
> +        except hashserv.client.HashConnectionError as e:
> +            bb.warn('Error contacting Hash Equivalence Server %s:
> %s' % (self.server, str(e))) +
> +        self.unitaskhashes[key] = unihash
> +        return unihash
> +
> +    def report_unihash(self, path, task, d):
> +        import importlib
> +
> +        taskhash = d.getVar('BB_TASKHASH')
> +        unihash = d.getVar('BB_UNIHASH')
> +        report_taskdata =
> d.getVar('SSTATE_HASHEQUIV_REPORT_TASKDATA') == '1'
> +        tempdir = d.getVar('T')
> +        fn = d.getVar('BB_FILENAME')
> +        tid = fn + ':do_' + task
> +        key = tid.rsplit("/", 1)[1] + ':' + taskhash
> +
> +        if self.setscenetasks and tid not in self.setscenetasks:
> +            return
> +
> +        # Sanity checks
> +        cache_unihash = self.unitaskhashes.get(key, None)
> +        if cache_unihash is None:
> +            bb.fatal('%s not in unihash cache. Please report this
> error' % key) +
> +        if cache_unihash != unihash:
> +            bb.fatal("Cache unihash %s doesn't match BB_UNIHASH %s"
> % (cache_unihash, unihash)) +
> +        sigfile = None
> +        sigfile_name = "depsig.do_%s.%d" % (task, os.getpid())
> +        sigfile_link = "depsig.do_%s" % task
> +
> +        try:
> +            sigfile = open(os.path.join(tempdir, sigfile_name),
> 'w+b') +
> +            locs = {'path': path, 'sigfile': sigfile, 'task': task,
> 'd': d} +
> +            if "." in self.method:
> +                (module, method) = self.method.rsplit('.', 1)
> +                locs['method'] =
> getattr(importlib.import_module(module), method)
> +                outhash = bb.utils.better_eval('method(path,
> sigfile, task, d)', locs)
> +            else:
> +                outhash = bb.utils.better_eval(self.method + '(path,
> sigfile, task, d)', locs) +
> +            try:
> +                extra_data = {}
> +
> +                owner = d.getVar('SSTATE_HASHEQUIV_OWNER')
> +                if owner:
> +                    extra_data['owner'] = owner
> +
> +                if report_taskdata:
> +                    sigfile.seek(0)
> +
> +                    extra_data['PN'] = d.getVar('PN')
> +                    extra_data['PV'] = d.getVar('PV')
> +                    extra_data['PR'] = d.getVar('PR')
> +                    extra_data['task'] = task
> +                    extra_data['outhash_siginfo'] =
> sigfile.read().decode('utf-8') +
> +                data = self.client().report_unihash(taskhash,
> self.method, outhash, unihash, extra_data)
> +                new_unihash = data['unihash']
> +
> +                if new_unihash != unihash:
> +                    bb.debug(1, 'Task %s unihash changed %s -> %s by
> server %s' % (taskhash, unihash, new_unihash, self.server))
> +                    bb.event.fire(bb.runqueue.taskUniHashUpdate(fn +
> ':do_' + task, new_unihash), d)
> +                else:
> +                    bb.debug(1, 'Reported task %s as unihash %s to
> %s' % (taskhash, unihash, self.server))
> +            except hashserv.client.HashConnectionError as e:
> +                bb.warn('Error contacting Hash Equivalence Server
> %s: %s' % (self.server, str(e)))
> +        finally:
> +            if sigfile:
> +                sigfile.close()
> +
> +                sigfile_link_path = os.path.join(tempdir,
> sigfile_link)
> +                bb.utils.remove(sigfile_link_path)
> +
> +                try:
> +                    os.symlink(sigfile_name, sigfile_link_path)
> +                except OSError:
> +                    pass
> +
> +
> +#
> +# Dummy class used for bitbake-selftest
> +#
> +class
> SignatureGeneratorTestEquivHash(SignatureGeneratorUniHashMixIn,
> SignatureGeneratorBasicHash):
> +    name = "TestEquivHash"
> +    def init_rundepcheck(self, data):
> +        super().init_rundepcheck(data)
> +        self.server = data.getVar('BB_HASHSERVE')
> +        self.method = "sstate_output_hash"
> +
> +
>  def dump_this_task(outfile, d):
>      import bb.parse
>      fn = d.getVar("BB_FILENAME")
> @@ -392,13 +596,13 @@ def list_inline_diff(oldlist, newlist,
> colors=None): 
>  def clean_basepath(a):
>      mc = None
> -    if a.startswith("multiconfig:"):
> +    if a.startswith("mc:"):
>          _, mc, a = a.split(":", 2)
>      b = a.rsplit("/", 2)[1] + '/' + a.rsplit("/", 2)[2]
>      if a.startswith("virtual:"):
>          b = b + ":" + a.rsplit(":", 1)[0]
>      if mc:
> -        b = b + ":multiconfig:" + mc
> +        b = b + ":mc:" + mc
>      return b
>  
>  def clean_basepaths(a):
> @@ -623,6 +827,10 @@ def compare_sigfiles(a, b, recursecb=None,
> color=False, collapsed=False): a_taint = a_data.get('taint', None)
>      b_taint = b_data.get('taint', None)
>      if a_taint != b_taint:
> +        if a_taint and a_taint.startswith('nostamp:'):
> +            a_taint = a_taint.replace('nostamp:', 'nostamp(uuid4):')
> +        if b_taint and b_taint.startswith('nostamp:'):
> +            b_taint = b_taint.replace('nostamp:', 'nostamp(uuid4):')
>          output.append(color_format("{color_title}Taint (by
> forced/invalidated task) changed{color_default} from %s to %s") %
> (a_taint, b_taint)) return output
> @@ -642,7 +850,7 @@ def calc_basehash(sigdata):
>          if val is not None:
>              basedata = basedata + str(val)
>  
> -    return hashlib.md5(basedata.encode("utf-8")).hexdigest()
> +    return hashlib.sha256(basedata.encode("utf-8")).hexdigest()
>  
>  def calc_taskhash(sigdata):
>      data = sigdata['basehash']
> @@ -660,7 +868,7 @@ def calc_taskhash(sigdata):
>          else:
>              data = data + sigdata['taint']
>  
> -    return hashlib.md5(data.encode("utf-8")).hexdigest()
> +    return hashlib.sha256(data.encode("utf-8")).hexdigest()
>  
>  
>  def dump_sigfile(a):
> @@ -695,7 +903,11 @@ def dump_sigfile(a):
>              output.append("Hash for dependent task %s is %s" % (dep,
> a_data['runtaskhashes'][dep])) 
>      if 'taint' in a_data:
> -        output.append("Tainted (by forced/invalidated task): %s" %
> a_data['taint'])
> +        if a_data['taint'].startswith('nostamp:'):
> +            msg = a_data['taint'].replace('nostamp:',
> 'nostamp(uuid4):')
> +        else:
> +            msg = a_data['taint']
> +        output.append("Tainted (by forced/invalidated task): %s" %
> msg) 
>      if 'task' in a_data:
>          computed_basehash = calc_basehash(a_data)
> diff --git a/bitbake/lib/bb/taskdata.py b/bitbake/lib/bb/taskdata.py
> index 94e822c..8c25e09 100644
> --- a/bitbake/lib/bb/taskdata.py
> +++ b/bitbake/lib/bb/taskdata.py
> @@ -1,6 +1,3 @@
> -#!/usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake 'TaskData' implementation
>  
> @@ -10,18 +7,8 @@ Task data collection and handling
>  
>  # Copyright (C) 2006  Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import logging
>  import re
> @@ -93,7 +80,7 @@ class TaskData:
>          def add_mcdepends(task):
>              for dep in task_deps['mcdepends'][task].split():
>                  if len(dep.split(':')) != 5:
> -                    bb.msg.fatal("TaskData", "Error for %s:%s[%s],
> multiconfig dependency %s does not contain exactly four  ':'
> characters.\n Task '%s' should be specified in the form
> 'multiconfig:fromMC:toMC:packagename:task'" % (fn, task, 'mcdepends',
> dep, 'mcdepends'))
> +                    bb.msg.fatal("TaskData", "Error for %s:%s[%s],
> multiconfig dependency %s does not contain exactly four  ':'
> characters.\n Task '%s' should be specified in the form
> 'mc:fromMC:toMC:packagename:task'" % (fn, task, 'mcdepends', dep,
> 'mcdepends')) if dep not in self.mcdepends:
> self.mcdepends.append(dep) diff --git
> a/bitbake/lib/bb/tests/codeparser.py
> b/bitbake/lib/bb/tests/codeparser.py index e30e78c..826a2d2 100644
> --- a/bitbake/lib/bb/tests/codeparser.py +++
> b/bitbake/lib/bb/tests/codeparser.py @@ -1,23 +1,10 @@ -#
> ex:ts=4:sw=4:sts=4:et -# -*- tab-width: 4; c-basic-offset: 4;
> indent-tabs-mode: nil -*- #
>  # BitBake Test for codeparser.py
>  #
>  # Copyright (C) 2010 Chris Larson
>  # Copyright (C) 2012 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  
>  import unittest
> @@ -123,6 +110,13 @@ ${D}${libdir}/pkgconfig/*.pc
>          self.parseExpression("sed -i -e 's:IP{:I${:g' $pc")
>          self.assertExecs(set(["sed"]))
>  
> +    def test_parameter_expansion_modifiers(self):
> +        # - and + are also valid modifiers for parameter expansion,
> but are
> +        # valid characters in bitbake variable names, so are not
> included here
> +        for i in ('=', ':-', ':=', '?', ':?', ':+', '#', '%', '##',
> '%%'):
> +            name = "foo%sbar" % i
> +            self.parseExpression("${%s}" % name)
> +            self.assertNotIn(name, self.references)
>  
>      def test_until(self):
>          self.parseExpression("until false; do echo true; done")
> diff --git a/bitbake/lib/bb/tests/cooker.py
> b/bitbake/lib/bb/tests/cooker.py index 2b44236..090916e 100644
> --- a/bitbake/lib/bb/tests/cooker.py
> +++ b/bitbake/lib/bb/tests/cooker.py
> @@ -1,20 +1,7 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # BitBake Tests for cooker.py
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  
>  import unittest
> diff --git a/bitbake/lib/bb/tests/cow.py b/bitbake/lib/bb/tests/cow.py
> index d149d84..b4af4bb 100644
> --- a/bitbake/lib/bb/tests/cow.py
> +++ b/bitbake/lib/bb/tests/cow.py
> @@ -1,22 +1,9 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # BitBake Tests for Copy-on-Write (cow.py)
>  #
> -# Copyright 2006 Holger Freyther <freyther@handhelds.org>
> -#
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# Copyright 2006 Holger Freyther
> <freyther@handhelds.org> #
>  
>  import unittest
> diff --git a/bitbake/lib/bb/tests/data.py
> b/bitbake/lib/bb/tests/data.py index db3e201..3e49984 100644
> --- a/bitbake/lib/bb/tests/data.py
> +++ b/bitbake/lib/bb/tests/data.py
> @@ -1,23 +1,10 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # BitBake Tests for the Data Store (data.py/data_smart.py)
>  #
>  # Copyright (C) 2010 Chris Larson
>  # Copyright (C) 2012 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  
>  import unittest
> @@ -394,6 +381,28 @@ class TestOverrides(unittest.TestCase):
>          self.d.setVar("OVERRIDES", "foo:bar:some_val")
>          self.assertEqual(self.d.getVar("TEST"), " testvalue5")
>  
> +    def test_append_and_override_1(self):
> +        self.d.setVar("TEST_append", "testvalue2")
> +        self.d.setVar("TEST_bar", "testvalue3")
> +        self.assertEqual(self.d.getVar("TEST"),
> "testvalue3testvalue2") +
> +    def test_append_and_override_2(self):
> +        self.d.setVar("TEST_append_bar", "testvalue2")
> +        self.assertEqual(self.d.getVar("TEST"),
> "testvaluetestvalue2") +
> +    def test_append_and_override_3(self):
> +        self.d.setVar("TEST_bar_append", "testvalue2")
> +        self.assertEqual(self.d.getVar("TEST"), "testvalue2")
> +
> +    # Test an override with _<numeric> in it based on a real world
> OE issue
> +    def test_underscore_override(self):
> +        self.d.setVar("TARGET_ARCH", "x86_64")
> +        self.d.setVar("PN", "test-${TARGET_ARCH}")
> +        self.d.setVar("VERSION", "1")
> +        self.d.setVar("VERSION_pn-test-${TARGET_ARCH}", "2")
> +        self.d.setVar("OVERRIDES", "pn-${PN}")
> +        bb.data.expandKeys(self.d)
> +        self.assertEqual(self.d.getVar("VERSION"), "2")
>  
>  class TestKeyExpansion(unittest.TestCase):
>      def setUp(self):
> @@ -470,7 +479,7 @@ class TaskHash(unittest.TestCase):
>              tasklist, gendeps, lookupcache =
> bb.data.generate_dependencies(d) taskdeps, basehash =
> bb.data.generate_dependency_hash(tasklist, gendeps, lookupcache,
> set(), "somefile") bb.warn(str(lookupcache))
> -            return basehash["somefile." + taskname]
> +            return basehash["somefile:" + taskname]
>  
>          d = bb.data.init()
>          d.setVar("__BBTASKS", ["mytask"])
> diff --git a/bitbake/lib/bb/tests/event.py
> b/bitbake/lib/bb/tests/event.py index d3a5f62..9229b63 100644
> --- a/bitbake/lib/bb/tests/event.py
> +++ b/bitbake/lib/bb/tests/event.py
> @@ -1,22 +1,9 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # BitBake Tests for the Event implementation (event.py)
>  #
>  # Copyright (C) 2017 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  
>  import unittest
> @@ -574,14 +561,6 @@ class EventClassesTest(unittest.TestCase):
>          self.assertEqual(event.fn(1), callback(1))
>          self.assertEqual(event.pid, EventClassesTest._worker_pid)
>  
> -    def test_StampUpdate(self):
> -        targets = ["foo", "bar"]
> -        stampfns = [lambda:"foobar"]
> -        event = bb.event.StampUpdate(targets, stampfns)
> -        self.assertEqual(event.targets, targets)
> -        self.assertEqual(event.stampPrefix, stampfns)
> -        self.assertEqual(event.pid, EventClassesTest._worker_pid)
> -
>      def test_BuildBase(self):
>          """ Test base class for bitbake build events """
>          name = "foo"
> diff --git a/bitbake/lib/bb/tests/fetch.py
> b/bitbake/lib/bb/tests/fetch.py index 6848095..a0b656b 100644
> --- a/bitbake/lib/bb/tests/fetch.py
> +++ b/bitbake/lib/bb/tests/fetch.py
> @@ -1,22 +1,9 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # BitBake Tests for the Fetcher (fetch2/)
>  #
>  # Copyright (C) 2012 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  
>  import unittest
> @@ -893,12 +880,201 @@ class FetcherNetworkTest(FetcherTest):
>  
>      @skipIfNoNetwork()
>      def test_git_submodule(self):
> -        fetcher =
> bb.fetch.Fetch(["gitsm://git.yoctoproject.org/git-submodule-test;rev=f12e57f2edf0aa534cf1616fa983d165a92b0842"],
> self.d)
> +        # URL with ssh submodules
> +        url =
> "gitsm://git.yoctoproject.org/git-submodule-test;branch=ssh-gitsm-tests;rev=049da4a6cb198d7c0302e9e8b243a1443cb809a7"
> +        # Original URL (comment this if you have ssh access to
> git.yoctoproject.org)
> +        url =
> "gitsm://git.yoctoproject.org/git-submodule-test;branch=master;rev=a2885dd7d25380d23627e7544b7bbb55014b16ee"
> +        fetcher = bb.fetch.Fetch([url], self.d)
> +        fetcher.download()
> +        # Previous cwd has been deleted
> +        os.chdir(os.path.dirname(self.unpackdir))
> +        fetcher.unpack(self.unpackdir)
> +
> +        repo_path = os.path.join(self.tempdir, 'unpacked', 'git')
> +        self.assertTrue(os.path.exists(repo_path), msg='Unpacked
> repository missing')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'bitbake')), msg='bitbake submodule missing')
> +        self.assertFalse(os.path.exists(os.path.join(repo_path,
> 'na')), msg='uninitialized submodule present') +
> +        # Only when we're running the extended test with a
> submodule's submodule, can we check this.
> +        if os.path.exists(os.path.join(repo_path,
> 'bitbake-gitsm-test1')):
> +            self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'bitbake-gitsm-test1', 'bitbake')), msg='submodule of submodule
> missing') +
> +    @skipIfNoNetwork()
> +    def test_git_submodule_dbus_broker(self):
> +        # The following external repositories have show failures in
> fetch and unpack operations
> +        # We want to avoid regressions!
> +        url =
> "gitsm://github.com/bus1/dbus-broker;protocol=git;rev=fc874afa0992d0c75ec25acb43d344679f0ee7d2"
> +        fetcher = bb.fetch.Fetch([url], self.d)
> +        fetcher.download()
> +        # Previous cwd has been deleted
> +        os.chdir(os.path.dirname(self.unpackdir))
> +        fetcher.unpack(self.unpackdir)
> +
> +        repo_path = os.path.join(self.tempdir, 'unpacked', 'git')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/subprojects/c-dvar/config')), msg='Missing submodule
> config "subprojects/c-dvar"')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/subprojects/c-list/config')), msg='Missing submodule
> config "subprojects/c-list"')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/subprojects/c-rbtree/config')), msg='Missing submodule
> config "subprojects/c-rbtree"')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/subprojects/c-sundry/config')), msg='Missing submodule
> config "subprojects/c-sundry"')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/subprojects/c-utf8/config')), msg='Missing submodule
> config "subprojects/c-utf8"') +
> +    @skipIfNoNetwork()
> +    def test_git_submodule_CLI11(self):
> +        url =
> "gitsm://github.com/CLIUtils/CLI11;protocol=git;rev=bd4dc911847d0cde7a6b41dfa626a85aab213baf"
> +        fetcher = bb.fetch.Fetch([url], self.d)
> +        fetcher.download()
> +        # Previous cwd has been deleted
> +        os.chdir(os.path.dirname(self.unpackdir))
> +        fetcher.unpack(self.unpackdir)
> +
> +        repo_path = os.path.join(self.tempdir, 'unpacked', 'git')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/extern/googletest/config')), msg='Missing submodule
> config "extern/googletest"')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/extern/json/config')), msg='Missing submodule config
> "extern/json"')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/extern/sanitizers/config')), msg='Missing submodule
> config "extern/sanitizers"') +
> +    @skipIfNoNetwork()
> +    def test_git_submodule_update_CLI11(self):
> +        """ Prevent regression on update detection not finding
> missing submodule, or modules without needed commits """
> +        url =
> "gitsm://github.com/CLIUtils/CLI11;protocol=git;rev=cf6a99fa69aaefe477cc52e3ef4a7d2d7fa40714"
> +        fetcher = bb.fetch.Fetch([url], self.d)
> +        fetcher.download()
> +
> +        # CLI11 that pulls in a newer nlohmann-json
> +        url =
> "gitsm://github.com/CLIUtils/CLI11;protocol=git;rev=49ac989a9527ee9bb496de9ded7b4872c2e0e5ca"
> +        fetcher = bb.fetch.Fetch([url], self.d)
> +        fetcher.download()
> +        # Previous cwd has been deleted
> +        os.chdir(os.path.dirname(self.unpackdir))
> +        fetcher.unpack(self.unpackdir)
> +
> +        repo_path = os.path.join(self.tempdir, 'unpacked', 'git')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/extern/googletest/config')), msg='Missing submodule
> config "extern/googletest"')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/extern/json/config')), msg='Missing submodule config
> "extern/json"')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/extern/sanitizers/config')), msg='Missing submodule
> config "extern/sanitizers"') +
> +    @skipIfNoNetwork()
> +    def test_git_submodule_aktualizr(self):
> +        url =
> "gitsm://github.com/advancedtelematic/aktualizr;branch=master;protocol=git;rev=d00d1a04cc2366d1a5f143b84b9f507f8bd32c44"
> +        fetcher = bb.fetch.Fetch([url], self.d)
>          fetcher.download()
>          # Previous cwd has been deleted
>          os.chdir(os.path.dirname(self.unpackdir))
>          fetcher.unpack(self.unpackdir)
>  
> +        repo_path = os.path.join(self.tempdir, 'unpacked', 'git')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/partial/extern/isotp-c/config')), msg='Missing
> submodule config "partial/extern/isotp-c/config"')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/partial/extern/isotp-c/modules/deps/bitfield-c/config')),
> msg='Missing submodule config
> "partial/extern/isotp-c/modules/deps/bitfield-c/config"')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'partial/extern/isotp-c/deps/bitfield-c/.git')), msg="Submodule of
> submodule isotp-c did not unpack properly")
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/tests/tuf-test-vectors/config')), msg='Missing
> submodule config "tests/tuf-test-vectors/config"')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/third_party/googletest/config')), msg='Missing
> submodule config "third_party/googletest/config"')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> '.git/modules/third_party/HdrHistogram_c/config')), msg='Missing
> submodule config "third_party/HdrHistogram_c/config"') +
> +    @skipIfNoNetwork()
> +    def test_git_submodule_iotedge(self):
> +        """ Prevent regression on deeply nested submodules not being
> checked out properly, even though they were fetched. """ +
> +        # This repository also has submodules where the module
> (name), path and url do not align
> +        url =
> "gitsm://github.com/azure/iotedge.git;protocol=git;rev=d76e0316c6f324345d77c48a83ce836d09392699"
> +        fetcher = bb.fetch.Fetch([url], self.d)
> +        fetcher.download()
> +        # Previous cwd has been deleted
> +        os.chdir(os.path.dirname(self.unpackdir))
> +        fetcher.unpack(self.unpackdir)
> +
> +        repo_path = os.path.join(self.tempdir, 'unpacked', 'git')
> +
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'edgelet/hsm-sys/azure-iot-hsm-c/deps/c-shared/README.md')),
> msg='Missing submodule checkout')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'edgelet/hsm-sys/azure-iot-hsm-c/deps/c-shared/testtools/ctest/README.md')),
> msg='Missing submodule checkout')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'edgelet/hsm-sys/azure-iot-hsm-c/deps/c-shared/testtools/testrunner/readme.md')),
> msg='Missing submodule checkout')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'edgelet/hsm-sys/azure-iot-hsm-c/deps/c-shared/testtools/umock-c/readme.md')),
> msg='Missing submodule checkout')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'edgelet/hsm-sys/azure-iot-hsm-c/deps/c-shared/testtools/umock-c/deps/ctest/README.md')),
> msg='Missing submodule checkout')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'edgelet/hsm-sys/azure-iot-hsm-c/deps/c-shared/testtools/umock-c/deps/testrunner/readme.md')),
> msg='Missing submodule checkout')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'edgelet/hsm-sys/azure-iot-hsm-c/deps/utpm/README.md')), msg='Missing
> submodule checkout')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'edgelet/hsm-sys/azure-iot-hsm-c/deps/utpm/deps/c-utility/README.md')),
> msg='Missing submodule checkout')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'edgelet/hsm-sys/azure-iot-hsm-c/deps/utpm/deps/c-utility/testtools/ctest/README.md')),
> msg='Missing submodule checkout')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'edgelet/hsm-sys/azure-iot-hsm-c/deps/utpm/deps/c-utility/testtools/testrunner/readme.md')),
> msg='Missing submodule checkout')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'edgelet/hsm-sys/azure-iot-hsm-c/deps/utpm/deps/c-utility/testtools/umock-c/readme.md')),
> msg='Missing submodule checkout')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'edgelet/hsm-sys/azure-iot-hsm-c/deps/utpm/deps/c-utility/testtools/umock-c/deps/ctest/README.md')),
> msg='Missing submodule checkout')
> +        self.assertTrue(os.path.exists(os.path.join(repo_path,
> 'edgelet/hsm-sys/azure-iot-hsm-c/deps/utpm/deps/c-utility/testtools/umock-c/deps/testrunner/readme.md')),
> msg='Missing submodule checkout') + +class SVNTest(FetcherTest):
> +    def skipIfNoSvn():
> +        import shutil
> +        if not shutil.which("svn"):
> +            return unittest.skip("svn not installed,  tests being
> skipped") +
> +        if not shutil.which("svnadmin"):
> +            return unittest.skip("svnadmin not installed,  tests
> being skipped") +
> +        return lambda f: f
> +
> +    @skipIfNoSvn()
> +    def setUp(self):
> +        """ Create a local repository """
> +
> +        super(SVNTest, self).setUp()
> +
> +        # Create something we can fetch
> +        src_dir = tempfile.mkdtemp(dir=self.tempdir,
> +                                   prefix='svnfetch_srcdir_')
> +        src_dir = os.path.abspath(src_dir)
> +        bb.process.run("echo readme > README.md", cwd=src_dir)
> +
> +        # Store it in a local SVN repository
> +        repo_dir = tempfile.mkdtemp(dir=self.tempdir,
> +                                   prefix='svnfetch_localrepo_')
> +        repo_dir = os.path.abspath(repo_dir)
> +        bb.process.run("svnadmin create project", cwd=repo_dir)
> +
> +        self.repo_url = "file://%s/project" % repo_dir
> +        bb.process.run("svn import --non-interactive -m 'Initial
> import' %s %s/trunk" % (src_dir, self.repo_url),
> +                       cwd=repo_dir)
> +
> +        bb.process.run("svn co %s svnfetch_co" % self.repo_url,
> cwd=self.tempdir)
> +        # Github will emulate SVN.  Use this to check if we're
> downloding...
> +        bb.process.run("svn propset svn:externals 'bitbake
> http://github.com/openembedded/bitbake' .",
> +                       cwd=os.path.join(self.tempdir, 'svnfetch_co',
> 'trunk'))
> +        bb.process.run("svn commit --non-interactive -m 'Add
> external'",
> +                       cwd=os.path.join(self.tempdir, 'svnfetch_co',
> 'trunk')) +
> +        self.src_dir = src_dir
> +        self.repo_dir = repo_dir
> +
> +    @skipIfNoSvn()
> +    def tearDown(self):
> +        os.chdir(self.origdir)
> +        if os.environ.get("BB_TMPDIR_NOCLEAN") == "yes":
> +            print("Not cleaning up %s. Please remove manually." %
> self.tempdir)
> +        else:
> +            bb.utils.prunedir(self.tempdir)
> +
> +    @skipIfNoSvn()
> +    @skipIfNoNetwork()
> +    def test_noexternal_svn(self):
> +        # Always match the rev count from setUp (currently rev 2)
> +        url = "svn://%s;module=trunk;protocol=file;rev=2" %
> self.repo_url.replace('file://', '')
> +        fetcher = bb.fetch.Fetch([url], self.d)
> +        fetcher.download()
> +        os.chdir(os.path.dirname(self.unpackdir))
> +        fetcher.unpack(self.unpackdir)
> +
> +        self.assertTrue(os.path.exists(os.path.join(self.unpackdir,
> 'trunk')), msg="Missing trunk")
> +        self.assertTrue(os.path.exists(os.path.join(self.unpackdir,
> 'trunk', 'README.md')), msg="Missing contents")
> +        self.assertFalse(os.path.exists(os.path.join(self.unpackdir,
> 'trunk/bitbake/trunk')), msg="External dir should NOT exist")
> +        self.assertFalse(os.path.exists(os.path.join(self.unpackdir,
> 'trunk/bitbake/trunk', 'README')), msg="External README should NOT
> exit") +
> +    @skipIfNoSvn()
> +    def test_external_svn(self):
> +        # Always match the rev count from setUp (currently rev 2)
> +        url =
> "svn://%s;module=trunk;protocol=file;externals=allowed;rev=2" %
> self.repo_url.replace('file://', '')
> +        fetcher = bb.fetch.Fetch([url], self.d)
> +        fetcher.download()
> +        os.chdir(os.path.dirname(self.unpackdir))
> +        fetcher.unpack(self.unpackdir)
> +
> +        self.assertTrue(os.path.exists(os.path.join(self.unpackdir,
> 'trunk')), msg="Missing trunk")
> +        self.assertTrue(os.path.exists(os.path.join(self.unpackdir,
> 'trunk', 'README.md')), msg="Missing contents")
> +        self.assertTrue(os.path.exists(os.path.join(self.unpackdir,
> 'trunk/bitbake/trunk')), msg="External dir should exist")
> +        self.assertTrue(os.path.exists(os.path.join(self.unpackdir,
> 'trunk/bitbake/trunk', 'README')), msg="External README should exit") 
>  class TrustedNetworksTest(FetcherTest):
>      def test_trusted_network(self):
> @@ -1024,8 +1200,8 @@ class FetchLatestVersionTest(FetcherTest):
>          # packages with valid UPSTREAM_CHECK_URI and
> UPSTREAM_CHECK_REGEX ("cups",
> "http://www.cups.org/software/1.7.2/cups-1.7.2-source.tar.bz2",
> "https://github.com/apple/cups/releases",
> "(?P<name>cups\-)(?P<pver>((\d+[\.\-_]*)+))\-source\.tar\.gz") :
> "2.0.0",
> -        ("db",
> "http://download.oracle.com/berkeley-db/db-5.3.21.tar.gz",
> "http://www.oracle.com/technetwork/products/berkeleydb/downloads/index-082944.html",
> "http://download.oracle.com/otn/berkeley-db/(?P<name>db-)(?P<pver>((\d+[\.\-_]*)+))\.tar\.gz")
> -            : "6.1.19",
> +        ("db",
> "http://download.oracle.com/berkeley-db/db-5.3.21.tar.gz",
> "http://ftp.debian.org/debian/pool/main/d/db5.3/",
> "(?P<name>db5\.3_)(?P<pver>\d+(\.\d+)+).+\.orig\.tar\.xz")
> +            : "5.3.10",
>      }
>  
>      @skipIfNoNetwork()
> @@ -1280,7 +1456,7 @@ class GitShallowTest(FetcherTest):
>  
>      def fetch(self, uri=None):
>          if uri is None:
> -            uris = self.d.getVar('SRC_URI', True).split()
> +            uris = self.d.getVar('SRC_URI').split()
>              uri = uris[0]
>              d = self.d
>          else:
> @@ -1312,6 +1488,7 @@ class GitShallowTest(FetcherTest):
>          # fetch and unpack, from the shallow tarball
>          bb.utils.remove(self.gitdir, recurse=True)
>          bb.utils.remove(ud.clonedir, recurse=True)
> +        bb.utils.remove(ud.clonedir.replace('gitsource',
> 'gitsubmodule'), recurse=True) 
>          # confirm that the unpacked repo is used when no git clone
> or git # mirror tarball is available
> @@ -1338,7 +1515,7 @@ class GitShallowTest(FetcherTest):
>  
>          srcrev = self.git('rev-parse HEAD', cwd=self.srcdir).strip()
>          self.d.setVar('SRCREV', srcrev)
> -        uri = self.d.getVar('SRC_URI', True).split()[0]
> +        uri = self.d.getVar('SRC_URI').split()[0]
>          uri = '%s;nobranch=1;bare=1' % uri
>  
>          self.fetch_shallow(uri)
> @@ -1466,6 +1643,7 @@ class GitShallowTest(FetcherTest):
>          self.git('config --add remote.origin.url "%s"' % smdir,
> cwd=smdir) self.git('config --add remote.origin.fetch
> "+refs/heads/*:refs/remotes/origin/*"', cwd=smdir)
> self.add_empty_file('asub', cwd=smdir)
> +        self.add_empty_file('bsub', cwd=smdir)
>  
>          self.git('submodule init', cwd=self.srcdir)
>          self.git('submodule add file://%s' % smdir, cwd=self.srcdir)
> @@ -1475,10 +1653,16 @@ class GitShallowTest(FetcherTest):
>          uri = 'gitsm://%s;protocol=file;subdir=${S}' % self.srcdir
>          fetcher, ud = self.fetch_shallow(uri)
>  
> +        # Verify the main repository is shallow
>          self.assertRevCount(1)
> -        assert './.git/modules/' in bb.process.run('tar -tzf %s' %
> os.path.join(self.dldir, ud.mirrortarballs[0]))[0] +
> +        # Verify the gitsubmodule directory is present
>          assert os.listdir(os.path.join(self.gitdir, 'gitsubmodule'))
>  
> +        # Verify the submodule is also shallow
> +        self.assertRevCount(1, cwd=os.path.join(self.gitdir,
> 'gitsubmodule')) +
> +
>      if any(os.path.exists(os.path.join(p, 'git-annex')) for p in
> os.environ.get('PATH').split(':')): def test_shallow_annex(self):
>              self.add_empty_file('a')
> @@ -1510,7 +1694,7 @@ class GitShallowTest(FetcherTest):
>          self.add_empty_file('f')
>          self.assertRevCount(7, cwd=self.srcdir)
>  
> -        uri = self.d.getVar('SRC_URI', True).split()[0]
> +        uri = self.d.getVar('SRC_URI').split()[0]
>          uri = '%s;branch=master,a_branch;name=master,a_branch' % uri
>  
>          self.d.setVar('BB_GIT_SHALLOW_DEPTH', '0')
> @@ -1536,7 +1720,7 @@ class GitShallowTest(FetcherTest):
>          self.add_empty_file('f')
>          self.assertRevCount(7, cwd=self.srcdir)
>  
> -        uri = self.d.getVar('SRC_URI', True).split()[0]
> +        uri = self.d.getVar('SRC_URI').split()[0]
>          uri = '%s;branch=master,a_branch;name=master,a_branch' % uri
>  
>          self.d.setVar('BB_GIT_SHALLOW_DEPTH', '0')
> @@ -1724,3 +1908,83 @@ class GitShallowTest(FetcherTest):
>  
>          dir = os.listdir(self.unpackdir + "/git/")
>          self.assertIn("fstests.doap", dir)
> +
> +class GitLfsTest(FetcherTest):
> +    def setUp(self):
> +        FetcherTest.setUp(self)
> +
> +        self.gitdir = os.path.join(self.tempdir, 'git')
> +        self.srcdir = os.path.join(self.tempdir, 'gitsource')
> +        
> +        self.d.setVar('WORKDIR', self.tempdir)
> +        self.d.setVar('S', self.gitdir)
> +        self.d.delVar('PREMIRRORS')
> +        self.d.delVar('MIRRORS')
> +
> +        self.d.setVar('SRCREV', '${AUTOREV}')
> +        self.d.setVar('AUTOREV', '${@bb.fetch2.get_autorev(d)}')
> +
> +        bb.utils.mkdirhier(self.srcdir)
> +        self.git('init', cwd=self.srcdir)
> +        with open(os.path.join(self.srcdir, '.gitattributes'), 'wt')
> as attrs:
> +            attrs.write('*.mp3 filter=lfs -text')
> +        self.git(['add', '.gitattributes'], cwd=self.srcdir)
> +        self.git(['commit', '-m', "attributes", '.gitattributes'],
> cwd=self.srcdir) +
> +    def git(self, cmd, cwd=None):
> +        if isinstance(cmd, str):
> +            cmd = 'git ' + cmd
> +        else:
> +            cmd = ['git'] + cmd
> +        if cwd is None:
> +            cwd = self.gitdir
> +        return bb.process.run(cmd, cwd=cwd)[0]
> +
> +    def fetch(self, uri=None):
> +        uris = self.d.getVar('SRC_URI').split()
> +        uri = uris[0]
> +        d = self.d
> +
> +        fetcher = bb.fetch2.Fetch(uris, d)
> +        fetcher.download()
> +        ud = fetcher.ud[uri]
> +        return fetcher, ud
> +
> +    def test_lfs_enabled(self):
> +        import shutil
> +
> +        uri = 'git://%s;protocol=file;subdir=${S};lfs=1' %
> self.srcdir
> +        self.d.setVar('SRC_URI', uri)
> +
> +        fetcher, ud = self.fetch()
> +        self.assertIsNotNone(ud.method._find_git_lfs)
> +
> +        # If git-lfs can be found, the unpack should be successful
> +        ud.method._find_git_lfs = lambda d: True
> +        shutil.rmtree(self.gitdir, ignore_errors=True)
> +        fetcher.unpack(self.d.getVar('WORKDIR'))
> +
> +        # If git-lfs cannot be found, the unpack should throw an
> error
> +        with self.assertRaises(bb.fetch2.FetchError):
> +            ud.method._find_git_lfs = lambda d: False
> +            shutil.rmtree(self.gitdir, ignore_errors=True)
> +            fetcher.unpack(self.d.getVar('WORKDIR'))
> +
> +    def test_lfs_disabled(self):
> +        import shutil
> +
> +        uri = 'git://%s;protocol=file;subdir=${S};lfs=0' %
> self.srcdir
> +        self.d.setVar('SRC_URI', uri)
> +
> +        fetcher, ud = self.fetch()
> +        self.assertIsNotNone(ud.method._find_git_lfs)
> +
> +        # If git-lfs can be found, the unpack should be successful
> +        ud.method._find_git_lfs = lambda d: True
> +        shutil.rmtree(self.gitdir, ignore_errors=True)
> +        fetcher.unpack(self.d.getVar('WORKDIR'))
> +
> +        # If git-lfs cannot be found, the unpack should be successful
> +        ud.method._find_git_lfs = lambda d: False
> +        shutil.rmtree(self.gitdir, ignore_errors=True)
> +        fetcher.unpack(self.d.getVar('WORKDIR'))
> diff --git a/bitbake/lib/bb/tests/parse.py
> b/bitbake/lib/bb/tests/parse.py index 1bc4740..9afd1b2 100644
> --- a/bitbake/lib/bb/tests/parse.py
> +++ b/bitbake/lib/bb/tests/parse.py
> @@ -1,22 +1,9 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # BitBake Test for lib/bb/parse/
>  #
>  # Copyright (C) 2015 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  
>  import unittest
> @@ -187,3 +174,21 @@ python () {
>          self.assertEqual(d1.getVar("VAR_var"), "B")
>          self.assertEqual(d2.getVar("VAR_var"), None)
>  
> +    addtask_deltask = """
> +addtask do_patch after do_foo after do_unpack before do_configure
> before do_compile +addtask do_fetch do_patch
> +
> +deltask do_fetch do_patch
> +"""
> +    def test_parse_addtask_deltask(self):
> +        import sys
> +        f = self.parsehelper(self.addtask_deltask)
> +        d = bb.parse.handle(f.name, self.d)['']
> +
> +        stdout = sys.stdout.getvalue()
> +        self.assertTrue("addtask contained multiple 'before'
> keywords" in stdout)
> +        self.assertTrue("addtask contained multiple 'after'
> keywords" in stdout)
> +        self.assertTrue('addtask ignored: " do_patch"' in stdout)
> +        self.assertTrue('deltask ignored: " do_patch"' in stdout)
> +        #self.assertTrue('dependent task do_foo for do_patch does
> not exist' in stdout) +
> diff --git a/bitbake/lib/bb/tests/persist_data.py
> b/bitbake/lib/bb/tests/persist_data.py new file mode 100644
> index 0000000..f641b5a
> --- /dev/null
> +++ b/bitbake/lib/bb/tests/persist_data.py
> @@ -0,0 +1,129 @@
> +#
> +# BitBake Test for lib/bb/persist_data/
> +#
> +# Copyright (C) 2018 Garmin Ltd.
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
> +import unittest
> +import bb.data
> +import bb.persist_data
> +import tempfile
> +import threading
> +
> +class PersistDataTest(unittest.TestCase):
> +    def _create_data(self):
> +        return bb.persist_data.persist('TEST_PERSIST_DATA', self.d)
> +
> +    def setUp(self):
> +        self.d = bb.data.init()
> +        self.tempdir = tempfile.TemporaryDirectory()
> +        self.d['PERSISTENT_DIR'] = self.tempdir.name
> +        self.data = self._create_data()
> +        self.items = {
> +                'A1': '1',
> +                'B1': '2',
> +                'C2': '3'
> +                }
> +        self.stress_count = 10000
> +        self.thread_count = 5
> +
> +        for k,v in self.items.items():
> +            self.data[k] = v
> +
> +    def tearDown(self):
> +        self.tempdir.cleanup()
> +
> +    def _iter_helper(self, seen, iterator):
> +        with iter(iterator):
> +            for v in iterator:
> +                self.assertTrue(v in seen)
> +                seen.remove(v)
> +        self.assertEqual(len(seen), 0, '%s not seen' % seen)
> +
> +    def test_get(self):
> +        for k, v in self.items.items():
> +            self.assertEqual(self.data[k], v)
> +
> +        self.assertIsNone(self.data.get('D'))
> +        with self.assertRaises(KeyError):
> +            self.data['D']
> +
> +    def test_set(self):
> +        for k, v in self.items.items():
> +            self.data[k] += '-foo'
> +
> +        for k, v in self.items.items():
> +            self.assertEqual(self.data[k], v + '-foo')
> +
> +    def test_delete(self):
> +        self.data['D'] = '4'
> +        self.assertEqual(self.data['D'], '4')
> +        del self.data['D']
> +        self.assertIsNone(self.data.get('D'))
> +        with self.assertRaises(KeyError):
> +            self.data['D']
> +
> +    def test_contains(self):
> +        for k in self.items:
> +            self.assertTrue(k in self.data)
> +            self.assertTrue(self.data.has_key(k))
> +        self.assertFalse('NotFound' in self.data)
> +        self.assertFalse(self.data.has_key('NotFound'))
> +
> +    def test_len(self):
> +        self.assertEqual(len(self.data), len(self.items))
> +
> +    def test_iter(self):
> +        self._iter_helper(set(self.items.keys()), self.data)
> +
> +    def test_itervalues(self):
> +        self._iter_helper(set(self.items.values()),
> self.data.itervalues()) +
> +    def test_iteritems(self):
> +        self._iter_helper(set(self.items.items()),
> self.data.iteritems()) +
> +    def test_get_by_pattern(self):
> +        self._iter_helper({'1', '2'}, self.data.get_by_pattern('_1'))
> +
> +    def _stress_read(self, data):
> +        for i in range(self.stress_count):
> +            for k in self.items:
> +                data[k]
> +
> +    def _stress_write(self, data):
> +        for i in range(self.stress_count):
> +            for k, v in self.items.items():
> +                data[k] = v + str(i)
> +
> +    def _validate_stress(self):
> +        for k, v in self.items.items():
> +            self.assertEqual(self.data[k], v + str(self.stress_count
> - 1)) +
> +    def test_stress(self):
> +        self._stress_read(self.data)
> +        self._stress_write(self.data)
> +        self._validate_stress()
> +
> +    def test_stress_threads(self):
> +        def read_thread():
> +            data = self._create_data()
> +            self._stress_read(data)
> +
> +        def write_thread():
> +            data = self._create_data()
> +            self._stress_write(data)
> +
> +        threads = []
> +        for i in range(self.thread_count):
> +            threads.append(threading.Thread(target=read_thread))
> +            threads.append(threading.Thread(target=write_thread))
> +
> +        for t in threads:
> +            t.start()
> +        self._stress_read(self.data)
> +        for t in threads:
> +            t.join()
> +        self._validate_stress()
> +
> diff --git a/bitbake/lib/bb/tests/runqueue-tests/classes/base.bbclass
> b/bitbake/lib/bb/tests/runqueue-tests/classes/base.bbclass new file
> mode 100644 index 0000000..b57650d
> --- /dev/null
> +++ b/bitbake/lib/bb/tests/runqueue-tests/classes/base.bbclass
> @@ -0,0 +1,262 @@
> +SLOWTASKS ??= ""
> +SSTATEVALID ??= ""
> +
> +def stamptask(d):
> +    import time
> +
> +    thistask = d.expand("${PN}:${BB_CURRENTTASK}")
> +    stampname = d.expand("${TOPDIR}/%s.run" % thistask)
> +    with open(stampname, "a+") as f:
> +        f.write(d.getVar("BB_UNIHASH") + "\n")
> +
> +    if d.getVar("BB_CURRENT_MC") != "default":
> +        thistask =
> d.expand("${BB_CURRENT_MC}:${PN}:${BB_CURRENTTASK}")
> +    if thistask in d.getVar("SLOWTASKS").split():
> +        bb.note("Slowing task %s" % thistask)
> +        time.sleep(0.5)
> +    if d.getVar("BB_HASHSERVE"):
> +        task = d.getVar("BB_CURRENTTASK")
> +        if task in ['package', 'package_qa', 'packagedata',
> 'package_write_ipk', 'package_write_rpm', 'populate_lic',
> 'populate_sysroot']:
> +            bb.parse.siggen.report_unihash(os.getcwd(),
> d.getVar("BB_CURRENTTASK"), d) +
> +    with open(d.expand("${TOPDIR}/task.log"), "a+") as f:
> +        f.write(thistask + "\n")
> +
> +
> +def sstate_output_hash(path, sigfile, task, d):
> +    import hashlib
> +    h = hashlib.sha256()
> +    h.update(d.expand("${PN}:${BB_CURRENTTASK}").encode('utf-8'))
> +    return h.hexdigest()
> +
> +python do_fetch() {
> +    # fetch
> +    stamptask(d)
> +}
> +python do_unpack() {
> +    # unpack
> +    stamptask(d)
> +}
> +python do_patch() {
> +    # patch
> +    stamptask(d)
> +}
> +python do_populate_lic() {
> +    # populate_lic
> +    stamptask(d)
> +}
> +python do_prepare_recipe_sysroot() {
> +    # prepare_recipe_sysroot
> +    stamptask(d)
> +}
> +python do_configure() {
> +    # configure
> +    stamptask(d)
> +}
> +python do_compile() {
> +    # compile
> +    stamptask(d)
> +}
> +python do_install() {
> +    # install
> +    stamptask(d)
> +}
> +python do_populate_sysroot() {
> +    # populate_sysroot
> +    stamptask(d)
> +}
> +python do_package() {
> +    # package
> +    stamptask(d)
> +}
> +python do_package_write_ipk() {
> +    # package_write_ipk
> +    stamptask(d)
> +}
> +python do_package_write_rpm() {
> +    # package_write_rpm
> +    stamptask(d)
> +}
> +python do_packagedata() {
> +    # packagedata
> +    stamptask(d)
> +}
> +python do_package_qa() {
> +    # package_qa
> +    stamptask(d)
> +}
> +python do_build() {
> +    # build
> +    stamptask(d)
> +}
> +do_prepare_recipe_sysroot[deptask] = "do_populate_sysroot"
> +do_package[deptask] += "do_packagedata"
> +do_build[recrdeptask] += "do_deploy"
> +do_build[recrdeptask] += "do_package_write_ipk"
> +do_build[recrdeptask] += "do_package_write_rpm"
> +do_package_qa[rdeptask] = "do_packagedata"
> +do_populate_lic_deploy[recrdeptask] += "do_populate_lic do_deploy"
> +
> +DEBIANRDEP = "do_packagedata"
> +oo_package_write_ipk[rdeptask] = "${DEBIANRDEP}"
> +do_package_write_rpm[rdeptask] = "${DEBIANRDEP}"
> +
> +addtask fetch
> +addtask unpack after do_fetch
> +addtask patch after do_unpack
> +addtask prepare_recipe_sysroot after do_patch
> +addtask configure after do_prepare_recipe_sysroot
> +addtask compile after do_configure
> +addtask install after do_compile
> +addtask populate_sysroot after do_install
> +addtask package after do_install
> +addtask package_write_ipk after do_packagedata do_package
> +addtask package_write_rpm after do_packagedata do_package
> +addtask packagedata after do_package
> +addtask package_qa after do_package
> +addtask build after do_package_qa do_package_write_rpm
> do_package_write_ipk do_populate_sysroot +
> +python do_package_setscene() {
> +    stamptask(d)
> +}
> +python do_package_qa_setscene() {
> +    stamptask(d)
> +}
> +python do_package_write_ipk_setscene() {
> +    stamptask(d)
> +}
> +python do_package_write_rpm_setscene() {
> +    stamptask(d)
> +}
> +python do_packagedata_setscene() {
> +    stamptask(d)
> +}
> +python do_populate_lic_setscene() {
> +    stamptask(d)
> +}
> +python do_populate_sysroot_setscene() {
> +    stamptask(d)
> +}
> +
> +addtask package_setscene
> +addtask package_qa_setscene
> +addtask package_write_ipk_setscene
> +addtask package_write_rpm_setscene
> +addtask packagedata_setscene
> +addtask populate_lic_setscene
> +addtask populate_sysroot_setscene
> +
> +BB_SETSCENE_DEPVALID = "setscene_depvalid"
> +
> +def setscene_depvalid(task, taskdependees, notneeded, d, log=None):
> +    # taskdependees is a dict of tasks which depend on task, each
> being a 3 item list of [PN, TASKNAME, FILENAME]
> +    # task is included in taskdependees too
> +    # Return - False - We need this dependency
> +    #        - True - We can skip this dependency
> +    import re
> +
> +    def logit(msg, log):
> +        if log is not None:
> +            log.append(msg)
> +        else:
> +            bb.debug(2, msg)
> +
> +    logit("Considering setscene task: %s" %
> (str(taskdependees[task])), log) +
> +    def isNativeCross(x):
> +        return x.endswith("-native") or "-cross-" in x or
> "-crosssdk" in x or x.endswith("-cross") +
> +    # We only need to trigger populate_lic through direct
> dependencies
> +    if taskdependees[task][1] == "do_populate_lic":
> +        return True
> +
> +    # We only need to trigger packagedata through direct dependencies
> +    # but need to preserve packagedata on packagedata links
> +    if taskdependees[task][1] == "do_packagedata":
> +        for dep in taskdependees:
> +            if taskdependees[dep][1] == "do_packagedata":
> +                return False
> +        return True
> +
> +    for dep in taskdependees:
> +        logit("  considering dependency: %s" %
> (str(taskdependees[dep])), log)
> +        if task == dep:
> +            continue
> +        if dep in notneeded:
> +            continue
> +        # do_package_write_* and do_package doesn't need do_package
> +        if taskdependees[task][1] == "do_package" and
> taskdependees[dep][1] in ['do_package', 'do_package_write_ipk',
> 'do_package_write_rpm', 'do_packagedata', 'do_package_qa']:
> +            continue
> +        # do_package_write_* need do_populate_sysroot as they're
> mainly postinstall dependencies
> +        if taskdependees[task][1] == "do_populate_sysroot" and
> taskdependees[dep][1] in ['do_package_write_ipk',
> 'do_package_write_rpm']:
> +            return False
> +        # do_package/packagedata/package_qa don't need
> do_populate_sysroot
> +        if taskdependees[task][1] == "do_populate_sysroot" and
> taskdependees[dep][1] in ['do_package', 'do_packagedata',
> 'do_package_qa']:
> +            continue
> +        # Native/Cross packages don't exist and are noexec anyway
> +        if isNativeCross(taskdependees[dep][0]) and
> taskdependees[dep][1] in ['do_package_write_ipk',
> 'do_package_write_rpm', 'do_packagedata', 'do_package',
> 'do_package_qa']:
> +            continue
> +
> +        # This is due to the [depends] in useradd.bbclass
> complicating matters
> +        # The logic *is* reversed here due to the way hard setscene
> dependencies are injected
> +        if (taskdependees[task][1] == 'do_package' or
> taskdependees[task][1] == 'do_populate_sysroot') and
> taskdependees[dep][0].endswith(('shadow-native', 'shadow-sysroot',
> 'base-passwd', 'pseudo-native')) and taskdependees[dep][1] ==
> 'do_populate_sysroot':
> +            continue
> +
> +        # Consider sysroot depending on sysroot tasks
> +        if taskdependees[task][1] == 'do_populate_sysroot' and
> taskdependees[dep][1] == 'do_populate_sysroot':
> +            # Native/Cross populate_sysroot need their dependencies
> +            if isNativeCross(taskdependees[task][0]) and
> isNativeCross(taskdependees[dep][0]):
> +                return False
> +            # Target populate_sysroot depended on by cross tools
> need to be installed
> +            if isNativeCross(taskdependees[dep][0]):
> +                return False
> +            # Native/cross tools depended upon by target sysroot are
> not needed
> +            # Add an exception for shadow-native as required by
> useradd.bbclass
> +            if isNativeCross(taskdependees[task][0]) and
> taskdependees[task][0] != 'shadow-native':
> +                continue
> +            # Target populate_sysroot need their dependencies
> +            return False
> +
> +
> +        if taskdependees[dep][1] == "do_populate_lic":
> +            continue
> +
> +        # Safe fallthrough default
> +        logit(" Default setscene dependency fall through due to
> dependency: %s" % (str(taskdependees[dep])), log)
> +        return False
> +    return True
> +
> +BB_HASHCHECK_FUNCTION = "sstate_checkhashes"
> +
> +def sstate_checkhashes(sq_data, d, siginfo=False, currentcount=0,
> **kwargs): +
> +    found = set()
> +    missed = set()
> +
> +    valid = d.getVar("SSTATEVALID").split()
> +
> +    for tid in sorted(sq_data['hash']):
> +        n =
> os.path.basename(bb.runqueue.fn_from_tid(tid)).split(".")[0] + ":do_"
> + bb.runqueue.taskname_from_tid(tid)[3:]
> +        print(n)
> +        stampfile = d.expand("${TOPDIR}/%s.run" % n.replace("do_",
> ""))
> +        if n in valid:
> +            bb.note("SState: Found valid sstate for %s" % n)
> +            found.add(tid)
> +        elif n + ":" + sq_data['hash'][tid] in valid:
> +            bb.note("SState: Found valid sstate for %s" % n)
> +            found.add(tid)
> +        elif os.path.exists(stampfile):
> +            with open(stampfile, "r") as f:
> +                hash = f.readline().strip()
> +            if hash == sq_data['hash'][tid]:
> +                bb.note("SState: Found valid sstate for %s (already
> run)" % n)
> +                found.add(tid)
> +            else:
> +                bb.note("SState: sstate hash didn't match previous
> run for %s (%s vs %s)" % (n, sq_data['hash'][tid], hash))
> +                missed.add(tid)
> +        else:
> +            missed.add(tid)
> +            bb.note("SState: Found no valid sstate for %s (%s)" %
> (n, sq_data['hash'][tid])) +
> +    return found
> +
> diff --git
> a/bitbake/lib/bb/tests/runqueue-tests/classes/image.bbclass
> b/bitbake/lib/bb/tests/runqueue-tests/classes/image.bbclass new file
> mode 100644 index 0000000..da9ff11 --- /dev/null
> +++ b/bitbake/lib/bb/tests/runqueue-tests/classes/image.bbclass
> @@ -0,0 +1,5 @@
> +do_rootfs[recrdeptask] += "do_package_write_deb do_package_qa"
> +do_rootfs[recrdeptask] += "do_package_write_ipk do_package_qa"
> +do_rootfs[recrdeptask] += "do_package_write_rpm do_package_qa
> +do_rootfs[recrdeptask] += "do_packagedata"
> +do_rootfs[recrdeptask] += "do_populate_lic"
> diff --git
> a/bitbake/lib/bb/tests/runqueue-tests/classes/native.bbclass
> b/bitbake/lib/bb/tests/runqueue-tests/classes/native.bbclass new file
> mode 100644 index 0000000..7eaaee5 --- /dev/null
> +++ b/bitbake/lib/bb/tests/runqueue-tests/classes/native.bbclass
> @@ -0,0 +1,2 @@
> +RECIPERDEPTASK = "do_populate_sysroot"
> +do_populate_sysroot[rdeptask] = "${RECIPERDEPTASK}"
> diff --git a/bitbake/lib/bb/tests/runqueue-tests/conf/bitbake.conf
> b/bitbake/lib/bb/tests/runqueue-tests/conf/bitbake.conf new file mode
> 100644 index 0000000..5e451fc
> --- /dev/null
> +++ b/bitbake/lib/bb/tests/runqueue-tests/conf/bitbake.conf
> @@ -0,0 +1,16 @@
> +CACHE = "${TOPDIR}/cache"
> +THISDIR = "${@os.path.dirname(d.getVar('FILE'))}"
> +COREBASE :=
> "${@os.path.normpath(os.path.dirname(d.getVar('FILE')+'/../../'))}"
> +BBFILES = "${COREBASE}/recipes/*.bb" +PROVIDES = "${PN}"
> +PN = "${@bb.parse.vars_from_file(d.getVar('FILE', False),d)[0]}"
> +PF = "${BB_CURRENT_MC}:${PN}"
> +export PATH
> +TMPDIR ??= "${TOPDIR}"
> +STAMP = "${TMPDIR}/stamps/${PN}"
> +T = "${TMPDIR}/workdir/${PN}/temp"
> +BB_NUMBER_THREADS = "4"
> +
> +BB_HASHBASE_WHITELIST = "BB_CURRENT_MC BB_HASHSERVE TMPDIR TOPDIR
> SLOWTASKS SSTATEVALID FILE" +
> +include conf/multiconfig/${BB_CURRENT_MC}.conf
> diff --git
> a/bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc1.conf
> b/bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc1.conf new
> file mode 100644 index 0000000..ecf23e1 --- /dev/null
> +++ b/bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc1.conf
> @@ -0,0 +1 @@
> +TMPDIR = "${TOPDIR}/mc1/"
> diff --git
> a/bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc2.conf
> b/bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc2.conf new
> file mode 100644 index 0000000..eef338e --- /dev/null
> +++ b/bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc2.conf
> @@ -0,0 +1 @@
> +TMPDIR = "${TOPDIR}/mc2/"
> diff --git a/bitbake/lib/bb/tests/runqueue-tests/recipes/a1.bb
> b/bitbake/lib/bb/tests/runqueue-tests/recipes/a1.bb new file mode
> 100644 index 0000000..e69de29
> diff --git a/bitbake/lib/bb/tests/runqueue-tests/recipes/b1.bb
> b/bitbake/lib/bb/tests/runqueue-tests/recipes/b1.bb new file mode
> 100644 index 0000000..c0b288e
> --- /dev/null
> +++ b/bitbake/lib/bb/tests/runqueue-tests/recipes/b1.bb
> @@ -0,0 +1 @@
> +DEPENDS = "a1"
> \ No newline at end of file
> diff --git a/bitbake/lib/bb/tests/runqueue-tests/recipes/c1.bb
> b/bitbake/lib/bb/tests/runqueue-tests/recipes/c1.bb new file mode
> 100644 index 0000000..e69de29
> diff --git a/bitbake/lib/bb/tests/runqueue-tests/recipes/d1.bb
> b/bitbake/lib/bb/tests/runqueue-tests/recipes/d1.bb new file mode
> 100644 index 0000000..5ba1975
> --- /dev/null
> +++ b/bitbake/lib/bb/tests/runqueue-tests/recipes/d1.bb
> @@ -0,0 +1,3 @@
> +DEPENDS = "a1"
> +
> +do_package_setscene[depends] = "a1:do_populate_sysroot_setscene"
> diff --git a/bitbake/lib/bb/tests/runqueue-tests/recipes/e1.bb
> b/bitbake/lib/bb/tests/runqueue-tests/recipes/e1.bb new file mode
> 100644 index 0000000..1588bc8
> --- /dev/null
> +++ b/bitbake/lib/bb/tests/runqueue-tests/recipes/e1.bb
> @@ -0,0 +1 @@
> +DEPENDS = "b1"
> \ No newline at end of file
> diff --git a/bitbake/lib/bb/tests/runqueue.py
> b/bitbake/lib/bb/tests/runqueue.py new file mode 100644
> index 0000000..5e64391
> --- /dev/null
> +++ b/bitbake/lib/bb/tests/runqueue.py
> @@ -0,0 +1,323 @@
> +#
> +# BitBake Tests for runqueue task processing
> +#
> +# Copyright (C) 2019 Richard Purdie
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
> +import unittest
> +import bb
> +import os
> +import tempfile
> +import subprocess
> +import sys
> +import time
> +
> +#
> +# TODO:
> +# Add tests on task ordering (X happens before Y after Z)
> +#
> +
> +class RunQueueTests(unittest.TestCase):
> +
> +    alltasks = ['package', 'fetch', 'unpack', 'patch',
> 'prepare_recipe_sysroot', 'configure',
> +                'compile', 'install', 'packagedata', 'package_qa',
> 'package_write_rpm', 'package_write_ipk',
> +                'populate_sysroot', 'build']
> +    a1_sstatevalid = "a1:do_package a1:do_package_qa
> a1:do_packagedata a1:do_package_write_ipk a1:do_package_write_rpm
> a1:do_populate_lic a1:do_populate_sysroot"
> +    b1_sstatevalid = "b1:do_package b1:do_package_qa
> b1:do_packagedata b1:do_package_write_ipk b1:do_package_write_rpm
> b1:do_populate_lic b1:do_populate_sysroot" +
> +    def run_bitbakecmd(self, cmd, builddir, sstatevalid="",
> slowtasks="", extraenv=None, cleanup=False):
> +        env = os.environ.copy()
> +        env["BBPATH"] =
> os.path.realpath(os.path.join(os.path.dirname(__file__),
> "runqueue-tests"))
> +        env["BB_ENV_EXTRAWHITE"] = "SSTATEVALID SLOWTASKS"
> +        env["SSTATEVALID"] = sstatevalid
> +        env["SLOWTASKS"] = slowtasks
> +        if extraenv:
> +            for k in extraenv:
> +                env[k] = extraenv[k]
> +                env["BB_ENV_EXTRAWHITE"] = env["BB_ENV_EXTRAWHITE"]
> + " " + k
> +        try:
> +            output = subprocess.check_output(cmd, env=env,
> stderr=subprocess.STDOUT,universal_newlines=True, cwd=builddir)
> +            print(output)
> +        except subprocess.CalledProcessError as e:
> +            self.fail("Command %s failed with %s" % (cmd, e.output))
> +        tasks = []
> +        tasklog = builddir + "/task.log"
> +        if os.path.exists(tasklog):
> +            with open(tasklog, "r") as f:
> +                tasks = [line.rstrip() for line in f]
> +            if cleanup:
> +                os.remove(tasklog)
> +        return tasks
> +
> +    def test_no_setscenevalid(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "a1"]
> +            sstatevalid = ""
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['a1:' + x for x in self.alltasks]
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    def test_single_setscenevalid(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "a1"]
> +            sstatevalid = "a1:do_package"
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['a1:package_setscene', 'a1:fetch',
> 'a1:unpack', 'a1:patch', 'a1:prepare_recipe_sysroot', 'a1:configure',
> +                        'a1:compile', 'a1:install',
> 'a1:packagedata', 'a1:package_qa', 'a1:package_write_rpm',
> 'a1:package_write_ipk',
> +                        'a1:populate_sysroot', 'a1:build']
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    def test_intermediate_setscenevalid(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "a1"]
> +            sstatevalid = "a1:do_package a1:do_populate_sysroot"
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['a1:package_setscene', 'a1:packagedata',
> 'a1:package_qa', 'a1:package_write_rpm', 'a1:package_write_ipk',
> +                        'a1:populate_sysroot_setscene', 'a1:build']
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    def test_intermediate_notcovered(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "a1"]
> +            sstatevalid = "a1:do_package_qa a1:do_packagedata
> a1:do_package_write_ipk a1:do_package_write_rpm a1:do_populate_lic
> a1:do_populate_sysroot"
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['a1:package_write_ipk_setscene',
> 'a1:package_write_rpm_setscene', 'a1:packagedata_setscene',
> +                        'a1:package_qa_setscene', 'a1:build',
> 'a1:populate_sysroot_setscene']
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    def test_all_setscenevalid(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "a1"]
> +            sstatevalid = self.a1_sstatevalid
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['a1:package_write_ipk_setscene',
> 'a1:package_write_rpm_setscene', 'a1:packagedata_setscene',
> +                        'a1:package_qa_setscene', 'a1:build',
> 'a1:populate_sysroot_setscene']
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    def test_no_settasks(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "a1", "-c", "patch"]
> +            sstatevalid = self.a1_sstatevalid
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['a1:fetch', 'a1:unpack', 'a1:patch']
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    def test_mix_covered_notcovered(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "a1:do_patch",
> "a1:do_populate_sysroot"]
> +            sstatevalid = self.a1_sstatevalid
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['a1:fetch', 'a1:unpack', 'a1:patch',
> 'a1:populate_sysroot_setscene']
> +            self.assertEqual(set(tasks), set(expected))
> +
> +
> +    # Test targets with intermediate setscene tasks alongside a
> target with no intermediate setscene tasks
> +    def test_mixed_direct_tasks_setscene_tasks(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "c1:do_patch", "a1"]
> +            sstatevalid = self.a1_sstatevalid
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['c1:fetch', 'c1:unpack', 'c1:patch',
> 'a1:package_write_ipk_setscene', 'a1:package_write_rpm_setscene',
> 'a1:packagedata_setscene',
> +                        'a1:package_qa_setscene', 'a1:build',
> 'a1:populate_sysroot_setscene']
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    # This test slows down the execution of do_package_setscene
> until after other real tasks have
> +    # started running which tests for a bug where tasks were being
> lost from the buildable list of real
> +    # tasks if they weren't in tasks_covered or tasks_notcovered
> +    def test_slow_setscene(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "a1"]
> +            sstatevalid = "a1:do_package"
> +            slowtasks = "a1:package_setscene"
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid,
> slowtasks)
> +            expected = ['a1:package_setscene', 'a1:fetch',
> 'a1:unpack', 'a1:patch', 'a1:prepare_recipe_sysroot', 'a1:configure',
> +                        'a1:compile', 'a1:install',
> 'a1:packagedata', 'a1:package_qa', 'a1:package_write_rpm',
> 'a1:package_write_ipk',
> +                        'a1:populate_sysroot', 'a1:build']
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    def test_setscenewhitelist(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "a1"]
> +            extraenv = {
> +                "BB_SETSCENE_ENFORCE" : "1",
> +                "BB_SETSCENE_ENFORCE_WHITELIST" :
> "a1:do_package_write_rpm a1:do_build"
> +            }
> +            sstatevalid = "a1:do_package a1:do_package_qa
> a1:do_packagedata a1:do_package_write_ipk a1:do_populate_lic
> a1:do_populate_sysroot"
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid,
> extraenv=extraenv)
> +            expected = ['a1:packagedata_setscene',
> 'a1:package_qa_setscene', 'a1:package_write_ipk_setscene',
> +                        'a1:populate_sysroot_setscene',
> 'a1:package_setscene']
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    # Tests for problems with dependencies between setscene tasks
> +    def test_no_setscenevalid_harddeps(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "d1"]
> +            sstatevalid = ""
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['a1:package', 'a1:fetch', 'a1:unpack',
> 'a1:patch', 'a1:prepare_recipe_sysroot', 'a1:configure',
> +                        'a1:compile', 'a1:install',
> 'a1:packagedata', 'a1:package_write_rpm', 'a1:package_write_ipk',
> +                        'a1:populate_sysroot', 'd1:package',
> 'd1:fetch', 'd1:unpack', 'd1:patch', 'd1:prepare_recipe_sysroot',
> 'd1:configure',
> +                        'd1:compile', 'd1:install',
> 'd1:packagedata', 'd1:package_qa', 'd1:package_write_rpm',
> 'd1:package_write_ipk',
> +                        'd1:populate_sysroot', 'd1:build']
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    def test_no_setscenevalid_withdeps(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "b1"]
> +            sstatevalid = ""
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['a1:' + x for x in self.alltasks] + ['b1:' +
> x for x in self.alltasks]
> +            expected.remove('a1:build')
> +            expected.remove('a1:package_qa')
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    def test_single_a1_setscenevalid_withdeps(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "b1"]
> +            sstatevalid = "a1:do_package"
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['a1:package_setscene', 'a1:fetch',
> 'a1:unpack', 'a1:patch', 'a1:prepare_recipe_sysroot', 'a1:configure',
> +                        'a1:compile', 'a1:install',
> 'a1:packagedata', 'a1:package_write_rpm', 'a1:package_write_ipk',
> +                        'a1:populate_sysroot'] + ['b1:' + x for x in
> self.alltasks]
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    def test_single_b1_setscenevalid_withdeps(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "b1"]
> +            sstatevalid = "b1:do_package"
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['a1:package', 'a1:fetch', 'a1:unpack',
> 'a1:patch', 'a1:prepare_recipe_sysroot', 'a1:configure',
> +                        'a1:compile', 'a1:install',
> 'a1:packagedata', 'a1:package_write_rpm', 'a1:package_write_ipk',
> +                        'a1:populate_sysroot',
> 'b1:package_setscene'] + ['b1:' + x for x in self.alltasks]
> +            expected.remove('b1:package')
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    def test_intermediate_setscenevalid_withdeps(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "b1"]
> +            sstatevalid = "a1:do_package a1:do_populate_sysroot
> b1:do_package"
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['a1:package_setscene', 'a1:packagedata',
> 'a1:package_write_rpm', 'a1:package_write_ipk',
> +                        'a1:populate_sysroot_setscene',
> 'b1:package_setscene'] + ['b1:' + x for x in self.alltasks]
> +            expected.remove('b1:package')
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    def test_all_setscenevalid_withdeps(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            cmd = ["bitbake", "b1"]
> +            sstatevalid = self.a1_sstatevalid + " " +
> self.b1_sstatevalid
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid)
> +            expected = ['a1:package_write_ipk_setscene',
> 'a1:package_write_rpm_setscene', 'a1:packagedata_setscene',
> +                        'b1:build', 'a1:populate_sysroot_setscene',
> 'b1:package_write_ipk_setscene', 'b1:package_write_rpm_setscene',
> +                        'b1:packagedata_setscene',
> 'b1:package_qa_setscene', 'b1:populate_sysroot_setscene']
> +            self.assertEqual(set(tasks), set(expected))
> +
> +    def test_multiconfig_setscene_optimise(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            extraenv = {
> +                "BBMULTICONFIG" : "mc1 mc2",
> +                "BB_SIGNATURE_HANDLER" : "basic"
> +            }
> +            cmd = ["bitbake", "b1", "mc:mc1:b1", "mc:mc2:b1"]
> +            setscenetasks = ['package_write_ipk_setscene',
> 'package_write_rpm_setscene', 'packagedata_setscene',
> +                             'populate_sysroot_setscene',
> 'package_qa_setscene']
> +            sstatevalid = ""
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid,
> extraenv=extraenv)
> +            expected = ['a1:' + x for x in self.alltasks] + ['b1:' +
> x for x in self.alltasks] + \
> +                       ['mc1:b1:' + x for x in setscenetasks] +
> ['mc1:a1:' + x for x in setscenetasks] + \
> +                       ['mc2:b1:' + x for x in setscenetasks] +
> ['mc2:a1:' + x for x in setscenetasks] + \
> +                       ['mc1:b1:build', 'mc2:b1:build']
> +            for x in ['mc1:a1:package_qa_setscene',
> 'mc2:a1:package_qa_setscene', 'a1:build', 'a1:package_qa']:
> +                expected.remove(x)
> +            self.assertEqual(set(tasks), set(expected))
> +
> +
> +    @unittest.skipIf(sys.version_info < (3, 5, 0), 'Python 3.5 or
> later required')
> +    def test_hashserv_single(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            extraenv = {
> +                "BB_HASHSERVE" : "auto",
> +                "BB_SIGNATURE_HANDLER" : "TestEquivHash"
> +            }
> +            cmd = ["bitbake", "a1", "b1"]
> +            setscenetasks = ['package_write_ipk_setscene',
> 'package_write_rpm_setscene', 'packagedata_setscene',
> +                             'populate_sysroot_setscene',
> 'package_qa_setscene']
> +            sstatevalid = ""
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid,
> extraenv=extraenv, cleanup=True)
> +            expected = ['a1:' + x for x in self.alltasks] + ['b1:' +
> x for x in self.alltasks]
> +            self.assertEqual(set(tasks), set(expected))
> +            cmd = ["bitbake", "a1", "-c", "install", "-f"]
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid,
> extraenv=extraenv, cleanup=True)
> +            expected = ['a1:install']
> +            self.assertEqual(set(tasks), set(expected))
> +            cmd = ["bitbake", "a1", "b1"]
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid,
> extraenv=extraenv, cleanup=True)
> +            expected = ['a1:populate_sysroot', 'a1:package',
> 'a1:package_write_rpm_setscene', 'a1:packagedata_setscene',
> +                        'a1:package_write_ipk_setscene',
> 'a1:package_qa_setscene']
> +            self.assertEqual(set(tasks), set(expected))
> +
> +            self.shutdown(tempdir)
> +
> +    @unittest.skipIf(sys.version_info < (3, 5, 0), 'Python 3.5 or
> later required')
> +    def test_hashserv_double(self):
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            extraenv = {
> +                "BB_HASHSERVE" : "auto",
> +                "BB_SIGNATURE_HANDLER" : "TestEquivHash"
> +            }
> +            cmd = ["bitbake", "a1", "b1", "e1"]
> +            setscenetasks = ['package_write_ipk_setscene',
> 'package_write_rpm_setscene', 'packagedata_setscene',
> +                             'populate_sysroot_setscene',
> 'package_qa_setscene']
> +            sstatevalid = ""
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid,
> extraenv=extraenv, cleanup=True)
> +            expected = ['a1:' + x for x in self.alltasks] + ['b1:' +
> x for x in self.alltasks] + ['e1:' + x for x in self.alltasks]
> +            self.assertEqual(set(tasks), set(expected))
> +            cmd = ["bitbake", "a1", "b1", "-c", "install", "-fn"]
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid,
> extraenv=extraenv, cleanup=True)
> +            cmd = ["bitbake", "e1"]
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid,
> extraenv=extraenv, cleanup=True)
> +            expected = ['a1:package', 'a1:install', 'b1:package',
> 'b1:install', 'a1:populate_sysroot', 'b1:populate_sysroot',
> +                        'a1:package_write_ipk_setscene',
> 'b1:packagedata_setscene', 'b1:package_write_rpm_setscene',
> +                        'a1:package_write_rpm_setscene',
> 'b1:package_write_ipk_setscene', 'a1:packagedata_setscene']
> +            self.assertEqual(set(tasks), set(expected))
> +
> +            self.shutdown(tempdir)
> +
> +    @unittest.skipIf(sys.version_info < (3, 5, 0), 'Python 3.5 or
> later required')
> +    def test_hashserv_multiple_setscene(self):
> +        # Runs e1:do_package_setscene twice
> +        with tempfile.TemporaryDirectory(prefix="runqueuetest") as
> tempdir:
> +            extraenv = {
> +                "BB_HASHSERVE" : "auto",
> +                "BB_SIGNATURE_HANDLER" : "TestEquivHash"
> +            }
> +            cmd = ["bitbake", "a1", "b1", "e1"]
> +            setscenetasks = ['package_write_ipk_setscene',
> 'package_write_rpm_setscene', 'packagedata_setscene',
> +                             'populate_sysroot_setscene',
> 'package_qa_setscene']
> +            sstatevalid = ""
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid,
> extraenv=extraenv, cleanup=True)
> +            expected = ['a1:' + x for x in self.alltasks] + ['b1:' +
> x for x in self.alltasks] + ['e1:' + x for x in self.alltasks]
> +            self.assertEqual(set(tasks), set(expected))
> +            cmd = ["bitbake", "a1", "b1", "-c", "install", "-fn"]
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid,
> extraenv=extraenv, cleanup=True)
> +            cmd = ["bitbake", "e1"]
> +            sstatevalid = "e1:do_package"
> +            tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid,
> extraenv=extraenv, cleanup=True, slowtasks="a1:populate_sysroot
> b1:populate_sysroot")
> +            expected = ['a1:package', 'a1:install', 'b1:package',
> 'b1:install', 'a1:populate_sysroot', 'b1:populate_sysroot',
> +                        'a1:package_write_ipk_setscene',
> 'b1:packagedata_setscene', 'b1:package_write_rpm_setscene',
> +                        'a1:package_write_rpm_setscene',
> 'b1:package_write_ipk_setscene', 'a1:packagedata_setscene',
> +                        'e1:package_setscene']
> +            self.assertEqual(set(tasks), set(expected))
> +            for i in expected:
> +                self.assertEqual(tasks.count(i), 1, "%s not in task
> list once" % i) +
> +            self.shutdown(tempdir)
> +
> +    def shutdown(self, tempdir):
> +        # Wait for the hashserve socket to disappear else we'll see
> races with the tempdir cleanup
> +        while os.path.exists(tempdir + "/hashserve.sock"):
> +            time.sleep(0.5)
> +
> +
> diff --git a/bitbake/lib/bb/tests/utils.py
> b/bitbake/lib/bb/tests/utils.py index 2f4ccf3..f4adf1d 100644
> --- a/bitbake/lib/bb/tests/utils.py
> +++ b/bitbake/lib/bb/tests/utils.py
> @@ -1,22 +1,9 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # BitBake Tests for utils.py
>  #
>  # Copyright (C) 2012 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  
>  import unittest
> @@ -42,6 +29,14 @@ class VerCmpString(unittest.TestCase):
>          self.assertTrue(result < 0)
>          result = bb.utils.vercmp_string('1.1', '1.0+1.1-beta1')
>          self.assertTrue(result > 0)
> +        result = bb.utils.vercmp_string('1a', '1a1')
> +        self.assertTrue(result < 0)
> +        result = bb.utils.vercmp_string('1a1', '1a')
> +        self.assertTrue(result > 0)
> +        result = bb.utils.vercmp_string('1.', '1.1')
> +        self.assertTrue(result < 0)
> +        result = bb.utils.vercmp_string('1.1', '1.')
> +        self.assertTrue(result > 0)
>  
>      def test_explode_dep_versions(self):
>          correctresult = {"foo" : ["= 1.10"]}
> diff --git a/bitbake/lib/bb/tinfoil.py b/bitbake/lib/bb/tinfoil.py
> index 368264f..0a1b913 100644
> --- a/bitbake/lib/bb/tinfoil.py
> +++ b/bitbake/lib/bb/tinfoil.py
> @@ -4,18 +4,8 @@
>  # Copyright (C) 2011 Mentor Graphics Corporation
>  # Copyright (C) 2006-2012 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import logging
>  import os
> diff --git a/bitbake/lib/bb/ui/__init__.py
> b/bitbake/lib/bb/ui/__init__.py index a4805ed..4b7ac36 100644
> --- a/bitbake/lib/bb/ui/__init__.py
> +++ b/bitbake/lib/bb/ui/__init__.py
> @@ -3,15 +3,5 @@
>  #
>  # Copyright (C) 2006-2007 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. diff --git a/bitbake/lib/bb/ui/buildinfohelper.py
> b/bitbake/lib/bb/ui/buildinfohelper.py index 31323d2..5cbca97 100644
> --- a/bitbake/lib/bb/ui/buildinfohelper.py
> +++ b/bitbake/lib/bb/ui/buildinfohelper.py
> @@ -3,18 +3,8 @@
>  #
>  # Copyright (C) 2013        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import sys
>  import bb
> @@ -656,6 +646,9 @@ class ORMWrapper(object):
>                  Target_Installed_Package.objects.create(target =
> target_obj, package = packagedict[p]['object']) 
>          packagedeps_objs = []
> +        pattern_so = re.compile(r'.*\.so(\.\d*)?$')
> +        pattern_lib = re.compile(r'.*\-suffix(\d*)?$')
> +        pattern_ko = re.compile(r'^kernel-module-.*')
>          for p in packagedict:
>              for (px,deptype) in packagedict[p]['depends']:
>                  if deptype == 'depends':
> @@ -664,6 +657,13 @@ class ORMWrapper(object):
>                      tdeptype = Package_Dependency.TYPE_TRECOMMENDS
>  
>                  try:
> +                    # Skip known non-package objects like libraries
> and kernel modules
> +                    if pattern_so.match(px) or pattern_lib.match(px):
> +                        logger.info("Toaster does not add library
> file dependencies to packages (%s,%s)", p, px)
> +                        continue
> +                    if pattern_ko.match(px):
> +                        logger.info("Toaster does not add kernel
> module dependencies to packages (%s,%s)", p, px)
> +                        continue
>                      packagedeps_objs.append(Package_Dependency(
>                          package = packagedict[p]['object'],
>                          depends_on = packagedict[px]['object'],
> diff --git a/bitbake/lib/bb/ui/knotty.py b/bitbake/lib/bb/ui/knotty.py
> index fa88e6c..35736ad 100644
> --- a/bitbake/lib/bb/ui/knotty.py
> +++ b/bitbake/lib/bb/ui/knotty.py
> @@ -5,18 +5,8 @@
>  #
>  # Copyright (C) 2006-2012 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from __future__ import division
>  
> @@ -222,6 +212,23 @@ class TerminalFilter(object):
>              sys.stdout.flush()
>          self.footer_present = False
>  
> +    def elapsed(self, sec):
> +        hrs = int(sec / 3600.0)
> +        sec -= hrs * 3600
> +        min = int(sec / 60.0)
> +        sec -= min * 60
> +        if hrs > 0:
> +            return "%dh%dm%ds" % (hrs, min, sec)
> +        elif min > 0:
> +            return "%dm%ds" % (min, sec)
> +        else:
> +            return "%ds" % (sec)
> +
> +    def keepAlive(self, t):
> +        if not self.cuu:
> +            print("Bitbake still alive (%ds)" % t)
> +            sys.stdout.flush()
> +
>      def updateFooter(self):
>          if not self.cuu:
>              return
> @@ -258,7 +265,7 @@ class TerminalFilter(object):
>              else:
>                  start_time = activetasks[t].get("starttime", None)
>                  if start_time:
> -                    tasks.append("%s - %ds (pid %s)" %
> (activetasks[t]["title"], currenttime - start_time, t))
> +                    tasks.append("%s - %s (pid %s)" %
> (activetasks[t]["title"], self.elapsed(currenttime - start_time), t))
> else: tasks.append("%s (pid %s)" % (activetasks[t]["title"], t))
>  
> @@ -293,8 +300,8 @@ class TerminalFilter(object):
>                          if start_time:
>                              pbar.start_time = start_time
>                      pbar.setmessage('%s:%s' % (tasknum,
> pbar.msg.split(':', 1)[1]))
> +                    pbar.setextra(rate)
>                      if progress > -1:
> -                        pbar.setextra(rate)
>                          content = pbar.update(progress)
>                      else:
>                          content = pbar.update(1)
> @@ -455,11 +462,17 @@ def main(server, eventHandler, params, tf =
> TerminalFilter): warnings = 0
>      taskfailures = []
>  
> +    printinterval = 5000
> +    lastprint = time.time()
> +
>      termfilter = tf(main, helper, console, errconsole, format,
> params.options.quiet) atexit.register(termfilter.finish)
>  
>      while True:
>          try:
> +            if (lastprint + printinterval) <= time.time():
> +                termfilter.keepAlive(printinterval)
> +                printinterval += 5000
>              event = eventHandler.waitEvent(0)
>              if event is None:
>                  if main.shutdown > 1:
> @@ -488,6 +501,8 @@ def main(server, eventHandler, params, tf =
> TerminalFilter): continue
>  
>              if isinstance(event, logging.LogRecord):
> +                lastprint = time.time()
> +                printinterval = 5000
>                  if event.levelno >= format.ERROR:
>                      errors = errors + 1
>                      return_value = 1
> @@ -645,7 +660,6 @@ def main(server, eventHandler, params, tf =
> TerminalFilter): # ignore
>              if isinstance(event, (bb.event.BuildBase,
>                                    bb.event.MetadataEvent,
> -                                  bb.event.StampUpdate,
>                                    bb.event.ConfigParsed,
>                                    bb.event.MultiConfigParsed,
>                                    bb.event.RecipeParsed,
> @@ -675,17 +689,27 @@ def main(server, eventHandler, params, tf =
> TerminalFilter): if params.observe_only:
>                  print("\nKeyboard Interrupt, exiting observer...")
>                  main.shutdown = 2
> -            if not params.observe_only and main.shutdown == 1:
> +
> +            def state_force_shutdown():
>                  print("\nSecond Keyboard Interrupt, stopping...\n")
>                  _, error = server.runCommand(["stateForceShutdown"])
>                  if error:
>                      logger.error("Unable to cleanly stop: %s" %
> error) +
> +            if not params.observe_only and main.shutdown == 1:
> +                state_force_shutdown()
> +
>              if not params.observe_only and main.shutdown == 0:
>                  print("\nKeyboard Interrupt, closing down...\n")
>                  interrupted = True
> -                _, error = server.runCommand(["stateShutdown"])
> -                if error:
> -                    logger.error("Unable to cleanly shutdown: %s" %
> error)
> +                # Capture the second KeyboardInterrupt during
> stateShutdown is running
> +                try:
> +                    _, error = server.runCommand(["stateShutdown"])
> +                    if error:
> +                        logger.error("Unable to cleanly shutdown:
> %s" % error)
> +                except KeyboardInterrupt:
> +                    state_force_shutdown()
> +
>              main.shutdown = main.shutdown + 1
>              pass
>          except Exception as e:
> diff --git a/bitbake/lib/bb/ui/ncurses.py
> b/bitbake/lib/bb/ui/ncurses.py index 8690c52..c422732 100644
> --- a/bitbake/lib/bb/ui/ncurses.py
> +++ b/bitbake/lib/bb/ui/ncurses.py
> @@ -6,18 +6,8 @@
>  # Copyright (C) 2006 Michael 'Mickey' Lauer
>  # Copyright (C) 2006-2007 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  """
>      We have the following windows:
> diff --git a/bitbake/lib/bb/ui/taskexp.py
> b/bitbake/lib/bb/ui/taskexp.py index 8305d70..50a943c 100644
> --- a/bitbake/lib/bb/ui/taskexp.py
> +++ b/bitbake/lib/bb/ui/taskexp.py
> @@ -4,18 +4,8 @@
>  # Copyright (C) 2007        Ross Burton
>  # Copyright (C) 2007 - 2008 Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import sys
>  import gi
> diff --git a/bitbake/lib/bb/ui/toasterui.py
> b/bitbake/lib/bb/ui/toasterui.py index 88cec37..51892c9 100644
> --- a/bitbake/lib/bb/ui/toasterui.py
> +++ b/bitbake/lib/bb/ui/toasterui.py
> @@ -7,18 +7,8 @@
>  # Copyright (C) 2006-2012 Richard Purdie
>  # Copyright (C) 2013      Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from __future__ import division
>  import time
> diff --git a/bitbake/lib/bb/ui/uievent.py
> b/bitbake/lib/bb/ui/uievent.py index 9542b91..fedb050 100644
> --- a/bitbake/lib/bb/ui/uievent.py
> +++ b/bitbake/lib/bb/ui/uievent.py
> @@ -1,22 +1,9 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # Copyright (C) 2006 - 2007  Michael 'Mickey' Lauer
>  # Copyright (C) 2006 - 2007  Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -
>  
>  """
>  Use this class to fork off a thread to recieve event callbacks from
> the bitbake diff --git a/bitbake/lib/bb/ui/uihelper.py
> b/bitbake/lib/bb/ui/uihelper.py index 963c1ea..c8dd7df 100644
> --- a/bitbake/lib/bb/ui/uihelper.py
> +++ b/bitbake/lib/bb/ui/uihelper.py
> @@ -1,21 +1,9 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
>  # Copyright (C) 2006 - 2007  Michael 'Mickey' Lauer
>  # Copyright (C) 2006 - 2007  Richard Purdie
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import bb.build
>  import time
> @@ -52,7 +40,7 @@ class BBUIHelper:
>              self.running_pids.remove(event.pid)
>              self.failed_tasks.append( { 'title' : "%s %s" %
> (event._package, event._task)}) self.needUpdate = True
> -        elif isinstance(event, bb.runqueue.runQueueTaskStarted) or
> isinstance(event, bb.runqueue.sceneQueueTaskStarted):
> +        elif isinstance(event, bb.runqueue.runQueueTaskStarted):
>              self.tasknumber_current = event.stats.completed +
> event.stats.active + event.stats.failed + 1 self.tasknumber_total =
> event.stats.total self.needUpdate = True
> diff --git a/bitbake/lib/bb/utils.py b/bitbake/lib/bb/utils.py
> index 13bb5f2..d035949 100644
> --- a/bitbake/lib/bb/utils.py
> +++ b/bitbake/lib/bb/utils.py
> @@ -1,23 +1,11 @@
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  """
>  BitBake Utility Functions
>  """
>  
>  # Copyright (C) 2004 Michael Lauer
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import re, fcntl, os, string, stat, shutil, time
>  import sys
> @@ -121,6 +109,10 @@ def vercmp_part(a, b):
>              return -1
>          elif oa > ob:
>              return 1
> +        elif ca is None:
> +            return -1
> +        elif cb is None:
> +            return 1
>          elif ca < cb:
>              return -1
>          elif ca > cb:
> @@ -402,7 +394,7 @@ def better_exec(code, context, text = None,
> realfile = "<code>", pythonexception code = better_compile(code,
> realfile, realfile) try:
>          exec(code, get_context(), context)
> -    except (bb.BBHandledException, bb.parse.SkipRecipe,
> bb.build.FuncFailed, bb.data_smart.ExpansionError):
> +    except (bb.BBHandledException, bb.parse.SkipRecipe,
> bb.data_smart.ExpansionError): # Error already shown so passthrough,
> no need for traceback raise
>      except Exception as e:
> @@ -685,7 +677,7 @@ def _check_unsafe_delete_path(path):
>          return True
>      return False
>  
> -def remove(path, recurse=False):
> +def remove(path, recurse=False, ionice=False):
>      """Equivalent to rm -f or rm -rf"""
>      if not path:
>          return
> @@ -694,7 +686,10 @@ def remove(path, recurse=False):
>              if _check_unsafe_delete_path(path):
>                  raise Exception('bb.utils.remove: called with
> dangerous path "%s" and recurse=True, refusing to delete!' % path) #
> shutil.rmtree(name) would be ideal but its too slow
> -        subprocess.check_call(['rm', '-rf'] + glob.glob(path))
> +        cmd = []
> +        if ionice:
> +            cmd = ['ionice', '-c', '3']
> +        subprocess.check_call(cmd + ['rm', '-rf'] + glob.glob(path))
>          return
>      for name in glob.glob(path):
>          try:
> @@ -703,20 +698,12 @@ def remove(path, recurse=False):
>              if exc.errno != errno.ENOENT:
>                  raise
>  
> -def prunedir(topdir):
> +def prunedir(topdir, ionice=False):
>      # Delete everything reachable from the directory named in
> 'topdir'. # CAUTION:  This is dangerous!
>      if _check_unsafe_delete_path(topdir):
>          raise Exception('bb.utils.prunedir: called with dangerous
> path "%s", refusing to delete!' % topdir)
> -    for root, dirs, files in os.walk(topdir, topdown = False):
> -        for name in files:
> -            os.remove(os.path.join(root, name))
> -        for name in dirs:
> -            if os.path.islink(os.path.join(root, name)):
> -                os.remove(os.path.join(root, name))
> -            else:
> -                os.rmdir(os.path.join(root, name))
> -    os.rmdir(topdir)
> +    remove(topdir, recurse=True, ionice=ionice)
>  
>  #
>  # Could also use return re.compile("(%s)" % "|".join(map(re.escape,
> suffixes))).sub(lambda mo: "", var) @@ -726,8 +713,8 @@ def
> prune_suffix(var, suffixes, d): # See if var ends with any of the
> suffixes listed and # remove it if found
>      for suffix in suffixes:
> -        if var.endswith(suffix):
> -            return var.replace(suffix, "")
> +        if suffix and var.endswith(suffix):
> +            return var[:-len(suffix)]
>      return var
>  
>  def mkdirhier(directory):
> @@ -738,7 +725,7 @@ def mkdirhier(directory):
>      try:
>          os.makedirs(directory)
>      except OSError as e:
> -        if e.errno != errno.EEXIST:
> +        if e.errno != errno.EEXIST or not os.path.isdir(directory):
>              raise e
>  
>  def movefile(src, dest, newmtime = None, sstat = None):
> @@ -796,7 +783,7 @@ def movefile(src, dest, newmtime = None, sstat =
> None): os.rename(src, destpath)
>              renamefailed = 0
>          except Exception as e:
> -            if e[0] != errno.EXDEV:
> +            if e.errno != errno.EXDEV:
>                  # Some random error.
>                  print("movefile: Failed to move", src, "to", dest, e)
>                  return None
> @@ -1505,6 +1492,8 @@ def ioprio_set(who, cls, value):
>        NR_ioprio_set = 251
>      elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
>        NR_ioprio_set = 289
> +    elif _unamearch == "aarch64":
> +      NR_ioprio_set = 30
>  
>      if NR_ioprio_set:
>          ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
> diff --git a/bitbake/lib/bblayers/__init__.py
> b/bitbake/lib/bblayers/__init__.py index 3ad9513..4e7c09d 100644
> --- a/bitbake/lib/bblayers/__init__.py
> +++ b/bitbake/lib/bblayers/__init__.py
> @@ -1,2 +1,6 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  from pkgutil import extend_path
>  __path__ = extend_path(__path__, __name__)
> diff --git a/bitbake/lib/bblayers/action.py
> b/bitbake/lib/bblayers/action.py index a3f658f..d6459d6 100644
> --- a/bitbake/lib/bblayers/action.py
> +++ b/bitbake/lib/bblayers/action.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  import fnmatch
>  import logging
>  import os
> diff --git a/bitbake/lib/bblayers/common.py
> b/bitbake/lib/bblayers/common.py index 98515ce..c5657d3 100644
> --- a/bitbake/lib/bblayers/common.py
> +++ b/bitbake/lib/bblayers/common.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  import argparse
>  import logging
>  import os
> diff --git a/bitbake/lib/bblayers/layerindex.py
> b/bitbake/lib/bblayers/layerindex.py index 9f02a9d..57cd902 100644
> --- a/bitbake/lib/bblayers/layerindex.py
> +++ b/bitbake/lib/bblayers/layerindex.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  import layerindexlib
>  
>  import argparse
> @@ -28,7 +32,7 @@ class LayerIndexPlugin(ActionPlugin):
>          layerdir = os.path.join(repodir, subdir)
>          if not os.path.exists(repodir):
>              if fetch_layer:
> -                result = subprocess.call('git clone %s %s' % (url,
> repodir), shell = True)
> +                result = subprocess.call(['git', 'clone', url,
> repodir]) if result:
>                      logger.error("Failed to download %s" % url)
>                      return None, None, None
> diff --git a/bitbake/lib/bblayers/query.py
> b/bitbake/lib/bblayers/query.py index 9294dfa..7db49c8 100644
> --- a/bitbake/lib/bblayers/query.py
> +++ b/bitbake/lib/bblayers/query.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  import collections
>  import fnmatch
>  import logging
> @@ -42,7 +46,7 @@ layer, with the preferred version first. Note that
> skipped recipes that are overlayed will also be listed, with a
> " (skipped)" suffix. """
>  
> -        items_listed = self.list_recipes('Overlayed recipes', None,
> True, args.same_version, args.filenames, True, None)
> +        items_listed = self.list_recipes('Overlayed recipes', None,
> True, args.same_version, args.filenames, False, True, None, False,
> None) # Check for overlayed .bbclass files
>          classes = collections.defaultdict(list)
> @@ -108,9 +112,9 @@ skipped recipes will also be listed, with a
> " (skipped)" suffix. title = 'Matching recipes:'
>          else:
>              title = 'Available recipes:'
> -        self.list_recipes(title, args.pnspec, False, False,
> args.filenames, args.multiple, inheritlist)
> +        self.list_recipes(title, args.pnspec, False, False,
> args.filenames, args.recipes_only, args.multiple, args.layer,
> args.bare, inheritlist) 
> -    def list_recipes(self, title, pnspec, show_overlayed_only,
> show_same_ver_only, show_filenames, show_multi_provider_only,
> inherits):
> +    def list_recipes(self, title, pnspec, show_overlayed_only,
> show_same_ver_only, show_filenames, show_recipes_only,
> show_multi_provider_only, selected_layer, bare, inherits): if
> inherits: bbpath = str(self.tinfoil.config_data.getVar('BBPATH')) for
> classname in inherits: @@ -140,24 +144,30 @@ skipped recipes will
> also be listed, with a " (skipped)" suffix. preferred_versions[p] =
> (ver, fn) 
>          def print_item(f, pn, ver, layer, ispref):
> -            if f in skiplist:
> -                skipped = ' (skipped)'
> -            else:
> -                skipped = ''
> -            if show_filenames:
> -                if ispref:
> -                    logger.plain("%s%s", f, skipped)
> +            if not selected_layer or layer == selected_layer:
> +                if not bare and f in skiplist:
> +                    skipped = ' (skipped)'
>                  else:
> -                    logger.plain("  %s%s", f, skipped)
> -            else:
> -                if ispref:
> -                    logger.plain("%s:", pn)
> -                logger.plain("  %s %s%s", layer.ljust(20), ver,
> skipped)
> +                    skipped = ''
> +                if show_filenames:
> +                    if ispref:
> +                        logger.plain("%s%s", f, skipped)
> +                    else:
> +                        logger.plain("  %s%s", f, skipped)
> +                elif show_recipes_only:
> +                    if pn not in show_unique_pn:
> +                        show_unique_pn.append(pn)
> +                        logger.plain("%s%s", pn, skipped)
> +                else:
> +                    if ispref:
> +                        logger.plain("%s:", pn)
> +                    logger.plain("  %s %s%s", layer.ljust(20), ver,
> skipped) 
>          global_inherit = (self.tinfoil.config_data.getVar('INHERIT')
> or "").split() cls_re = re.compile('classes/')
>  
>          preffiles = []
> +        show_unique_pn = []
>          items_listed = False
>          for p in sorted(pkg_pn):
>              if pnspec:
> @@ -489,8 +499,11 @@ NOTE: .bbappend files can impact the
> dependencies. 
>          parser_show_recipes = self.add_command(sp, 'show-recipes',
> self.do_show_recipes) parser_show_recipes.add_argument('-f',
> '--filenames', help='instead of the default formatting, list
> filenames of higher priority recipes with the ones they overlay
> indented underneath', action='store_true')
> +        parser_show_recipes.add_argument('-r', '--recipes-only',
> help='instead of the default formatting, list recipes only',
> action='store_true') parser_show_recipes.add_argument('-m',
> '--multiple', help='only list where multiple recipes (in the same
> layer or different layers) exist for the same recipe name',
> action='store_true') parser_show_recipes.add_argument('-i',
> '--inherits', help='only list recipes that inherit the named
> class(es) - separate multiple classes using , (without spaces)',
> metavar='CLASS', default='')
> +        parser_show_recipes.add_argument('-l', '--layer', help='only
> list recipes from the selected layer', default='')
> +        parser_show_recipes.add_argument('-b', '--bare',
> help='output just names without the "(skipped)" marker',
> action='store_true') parser_show_recipes.add_argument('pnspec',
> nargs='*', help='optional recipe name specification (wildcards
> allowed, enclose in quotes to avoid shell expansion)')
> parser_show_appends = self.add_command(sp, 'show-appends',
> self.do_show_appends) diff --git a/bitbake/lib/bs4/dammit.py
> b/bitbake/lib/bs4/dammit.py index 68d419f..805aa90 100644 ---
> a/bitbake/lib/bs4/dammit.py +++ b/bitbake/lib/bs4/dammit.py @@ -45,9
> +45,9 @@ except ImportError: pass
>  
>  xml_encoding_re = re.compile(
> -    '^<\?.*encoding=[\'"](.*?)[\'"].*\?>'.encode(), re.I)
> +    r'^<\?.*encoding=[\'"](.*?)[\'"].*\?>'.encode(), re.I)
>  html_meta_re = re.compile(
> -    '<\s*meta[^>]+charset\s*=\s*["\']?([^>]*?)[ /;\'">]'.encode(),
> re.I)
> +    r'<\s*meta[^>]+charset\s*=\s*["\']?([^>]*?)[ /;\'">]'.encode(),
> re.I) 
>  class EntitySubstitution(object):
>  
> @@ -80,11 +80,11 @@ class EntitySubstitution(object):
>          ">": "gt",  
>          }
>  
> -    BARE_AMPERSAND_OR_BRACKET = re.compile("([<>]|"
> -
> "&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)"
> -                                           ")")
> +    BARE_AMPERSAND_OR_BRACKET = re.compile(r"([<>]|"
> +
> r"&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)"
> +                                           r")")
>  
> -    AMPERSAND_OR_BRACKET = re.compile("([<>&])")
> +    AMPERSAND_OR_BRACKET = re.compile(r"([<>&])")
>  
>      @classmethod
>      def _substitute_html_entity(cls, matchobj):
> diff --git a/bitbake/lib/bs4/element.py b/bitbake/lib/bs4/element.py
> index 0e62c2e..3775a60 100644
> --- a/bitbake/lib/bs4/element.py
> +++ b/bitbake/lib/bs4/element.py
> @@ -1,7 +1,7 @@
>  __license__ = "MIT"
>  
>  from pdb import set_trace
> -import collections
> +import collections.abc
>  import re
>  import sys
>  import warnings
> @@ -10,7 +10,7 @@ from bs4.dammit import EntitySubstitution
>  DEFAULT_OUTPUT_ENCODING = "utf-8"
>  PY3K = (sys.version_info[0] > 2)
>  
> -whitespace_re = re.compile("\s+")
> +whitespace_re = re.compile(r"\s+")
>  
>  def _alias(attr):
>      """Alias one attribute name to another for backward
> compatibility""" @@ -67,7 +67,7 @@ class
> ContentMetaAttributeValue(AttributeValueWithCharsetSubstitution): The
> value of the 'content' attribute will be one of these objects. """
>  
> -    CHARSET_RE = re.compile("((^|;)\s*charset=)([^;]*)", re.M)
> +    CHARSET_RE = re.compile(r"((^|;)\s*charset=)([^;]*)", re.M)
>  
>      def __new__(cls, original_value):
>          match = cls.CHARSET_RE.search(original_value)
> @@ -155,7 +155,7 @@ class PageElement(object):
>  
>      def format_string(self, s, formatter='minimal'):
>          """Format the given string using the given formatter."""
> -        if not isinstance(formatter, collections.Callable):
> +        if not isinstance(formatter, collections.abc.Callable):
>              formatter = self._formatter_for_name(formatter)
>          if formatter is None:
>              output = s
> @@ -580,7 +580,7 @@ class PageElement(object):
>  
>      # Methods for supporting CSS selectors.
>  
> -    tag_name_re = re.compile('^[a-zA-Z0-9][-.a-zA-Z0-9:_]*$')
> +    tag_name_re = re.compile(r'^[a-zA-Z0-9][-.a-zA-Z0-9:_]*$')
>  
>      # /^([a-zA-Z0-9][-.a-zA-Z0-9:_]*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
>      #   \---------------------------/  \---/\-------------/
> \-------/ @@ -1077,7 +1077,7 @@ class Tag(PageElement):
>  
>          # First off, turn a string formatter into a function. This
>          # will stop the lookup from happening over and over again.
> -        if not isinstance(formatter, collections.Callable):
> +        if not isinstance(formatter, collections.abc.Callable):
>              formatter = self._formatter_for_name(formatter)
>  
>          attrs = []
> @@ -1181,7 +1181,7 @@ class Tag(PageElement):
>          """
>          # First off, turn a string formatter into a function. This
>          # will stop the lookup from happening over and over again.
> -        if not isinstance(formatter, collections.Callable):
> +        if not isinstance(formatter, collections.abc.Callable):
>              formatter = self._formatter_for_name(formatter)
>  
>          pretty_print = (indent_level is not None)
> @@ -1364,7 +1364,7 @@ class Tag(PageElement):
>                  if tag_name == '':
>                      raise ValueError(
>                          "A pseudo-class must be prefixed with a tag
> name.")
> -                pseudo_attributes =
> re.match('([a-zA-Z\d-]+)\(([a-zA-Z\d]+)\)', pseudo)
> +                pseudo_attributes =
> re.match(r'([a-zA-Z\d-]+)\(([a-zA-Z\d]+)\)', pseudo) found = []
>                  if pseudo_attributes is None:
>                      pseudo_type = pseudo
> @@ -1562,7 +1562,7 @@ class SoupStrainer(object):
>      def _normalize_search_value(self, value):
>          # Leave it alone if it's a Unicode string, a callable, a
>          # regular expression, a boolean, or None.
> -        if (isinstance(value, str) or isinstance(value,
> collections.Callable) or hasattr(value, 'match')
> +        if (isinstance(value, str) or isinstance(value,
> collections.abc.Callable) or hasattr(value, 'match') or
> isinstance(value, bool) or value is None): return value
>  
> @@ -1602,7 +1602,7 @@ class SoupStrainer(object):
>              markup = markup_name
>              markup_attrs = markup
>          call_function_with_tag_data = (
> -            isinstance(self.name, collections.Callable)
> +            isinstance(self.name, collections.abc.Callable)
>              and not isinstance(markup_name, Tag))
>  
>          if ((not self.name)
> @@ -1688,7 +1688,7 @@ class SoupStrainer(object):
>              # True matches any non-None value.
>              return markup is not None
>  
> -        if isinstance(match_against, collections.Callable):
> +        if isinstance(match_against, collections.abc.Callable):
>              return match_against(markup)
>  
>          # Custom callables take the tag as an argument, but all
> diff --git a/bitbake/lib/hashserv/__init__.py
> b/bitbake/lib/hashserv/__init__.py new file mode 100644
> index 0000000..c331862
> --- /dev/null
> +++ b/bitbake/lib/hashserv/__init__.py
> @@ -0,0 +1,93 @@
> +# Copyright (C) 2018-2019 Garmin Ltd.
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
> +from contextlib import closing
> +import re
> +import sqlite3
> +
> +UNIX_PREFIX = "unix://"
> +
> +ADDR_TYPE_UNIX = 0
> +ADDR_TYPE_TCP = 1
> +
> +
> +def setup_database(database, sync=True):
> +    db = sqlite3.connect(database)
> +    db.row_factory = sqlite3.Row
> +
> +    with closing(db.cursor()) as cursor:
> +        cursor.execute('''
> +            CREATE TABLE IF NOT EXISTS tasks_v2 (
> +                id INTEGER PRIMARY KEY AUTOINCREMENT,
> +                method TEXT NOT NULL,
> +                outhash TEXT NOT NULL,
> +                taskhash TEXT NOT NULL,
> +                unihash TEXT NOT NULL,
> +                created DATETIME,
> +
> +                -- Optional fields
> +                owner TEXT,
> +                PN TEXT,
> +                PV TEXT,
> +                PR TEXT,
> +                task TEXT,
> +                outhash_siginfo TEXT,
> +
> +                UNIQUE(method, outhash, taskhash)
> +                )
> +            ''')
> +        cursor.execute('PRAGMA journal_mode = WAL')
> +        cursor.execute('PRAGMA synchronous = %s' % ('NORMAL' if sync
> else 'OFF')) +
> +        # Drop old indexes
> +        cursor.execute('DROP INDEX IF EXISTS taskhash_lookup')
> +        cursor.execute('DROP INDEX IF EXISTS outhash_lookup')
> +
> +        # Create new indexes
> +        cursor.execute('CREATE INDEX IF NOT EXISTS
> taskhash_lookup_v2 ON tasks_v2 (method, taskhash, created)')
> +        cursor.execute('CREATE INDEX IF NOT EXISTS outhash_lookup_v2
> ON tasks_v2 (method, outhash)') +
> +    return db
> +
> +
> +def parse_address(addr):
> +    if addr.startswith(UNIX_PREFIX):
> +        return (ADDR_TYPE_UNIX, (addr[len(UNIX_PREFIX):],))
> +    else:
> +        m = re.match(r'\[(?P<host>[^\]]*)\]:(?P<port>\d+)$', addr)
> +        if m is not None:
> +            host = m.group('host')
> +            port = m.group('port')
> +        else:
> +            host, port = addr.split(':')
> +
> +        return (ADDR_TYPE_TCP, (host, int(port)))
> +
> +
> +def create_server(addr, dbname, *, sync=True):
> +    from . import server
> +    db = setup_database(dbname, sync=sync)
> +    s = server.Server(db)
> +
> +    (typ, a) = parse_address(addr)
> +    if typ == ADDR_TYPE_UNIX:
> +        s.start_unix_server(*a)
> +    else:
> +        s.start_tcp_server(*a)
> +
> +    return s
> +
> +
> +def create_client(addr):
> +    from . import client
> +    c = client.Client()
> +
> +    (typ, a) = parse_address(addr)
> +    if typ == ADDR_TYPE_UNIX:
> +        c.connect_unix(*a)
> +    else:
> +        c.connect_tcp(*a)
> +
> +    return c
> diff --git a/bitbake/lib/hashserv/client.py
> b/bitbake/lib/hashserv/client.py new file mode 100644
> index 0000000..f659566
> --- /dev/null
> +++ b/bitbake/lib/hashserv/client.py
> @@ -0,0 +1,157 @@
> +# Copyright (C) 2019 Garmin Ltd.
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
> +from contextlib import closing
> +import json
> +import logging
> +import socket
> +import os
> +
> +
> +logger = logging.getLogger('hashserv.client')
> +
> +
> +class HashConnectionError(Exception):
> +    pass
> +
> +
> +class Client(object):
> +    MODE_NORMAL = 0
> +    MODE_GET_STREAM = 1
> +
> +    def __init__(self):
> +        self._socket = None
> +        self.reader = None
> +        self.writer = None
> +        self.mode = self.MODE_NORMAL
> +
> +    def connect_tcp(self, address, port):
> +        def connect_sock():
> +            s = socket.create_connection((address, port))
> +
> +            s.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
> +            s.setsockopt(socket.SOL_TCP, socket.TCP_QUICKACK, 1)
> +            s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
> +            return s
> +
> +        self._connect_sock = connect_sock
> +
> +    def connect_unix(self, path):
> +        def connect_sock():
> +            s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
> +            # AF_UNIX has path length issues so chdir here to
> workaround
> +            cwd = os.getcwd()
> +            try:
> +                os.chdir(os.path.dirname(path))
> +                s.connect(os.path.basename(path))
> +            finally:
> +                os.chdir(cwd)
> +            return s
> +
> +        self._connect_sock = connect_sock
> +
> +    def connect(self):
> +        if self._socket is None:
> +            self._socket = self._connect_sock()
> +
> +            self.reader = self._socket.makefile('r',
> encoding='utf-8')
> +            self.writer = self._socket.makefile('w',
> encoding='utf-8') +
> +            self.writer.write('OEHASHEQUIV 1.0\n\n')
> +            self.writer.flush()
> +
> +            # Restore mode if the socket is being re-created
> +            cur_mode = self.mode
> +            self.mode = self.MODE_NORMAL
> +            self._set_mode(cur_mode)
> +
> +        return self._socket
> +
> +    def close(self):
> +        if self._socket is not None:
> +            self._socket.close()
> +            self._socket = None
> +            self.reader = None
> +            self.writer = None
> +
> +    def _send_wrapper(self, proc):
> +        count = 0
> +        while True:
> +            try:
> +                self.connect()
> +                return proc()
> +            except (OSError, HashConnectionError,
> json.JSONDecodeError, UnicodeDecodeError) as e:
> +                logger.warning('Error talking to server: %s' % e)
> +                if count >= 3:
> +                    if not isinstance(e, HashConnectionError):
> +                        raise HashConnectionError(str(e))
> +                    raise e
> +                self.close()
> +                count += 1
> +
> +    def send_message(self, msg):
> +        def proc():
> +            self.writer.write('%s\n' % json.dumps(msg))
> +            self.writer.flush()
> +
> +            l = self.reader.readline()
> +            if not l:
> +                raise HashConnectionError('Connection closed')
> +
> +            if not l.endswith('\n'):
> +                raise HashConnectionError('Bad message %r' % message)
> +
> +            return json.loads(l)
> +
> +        return self._send_wrapper(proc)
> +
> +    def send_stream(self, msg):
> +        def proc():
> +            self.writer.write("%s\n" % msg)
> +            self.writer.flush()
> +            l = self.reader.readline()
> +            if not l:
> +                raise HashConnectionError('Connection closed')
> +            return l.rstrip()
> +
> +        return self._send_wrapper(proc)
> +
> +    def _set_mode(self, new_mode):
> +        if new_mode == self.MODE_NORMAL and self.mode ==
> self.MODE_GET_STREAM:
> +            r = self.send_stream('END')
> +            if r != 'ok':
> +                raise HashConnectionError('Bad response from server
> %r' % r)
> +        elif new_mode == self.MODE_GET_STREAM and self.mode ==
> self.MODE_NORMAL:
> +            r = self.send_message({'get-stream': None})
> +            if r != 'ok':
> +                raise HashConnectionError('Bad response from server
> %r' % r)
> +        elif new_mode != self.mode:
> +            raise Exception('Undefined mode transition %r -> %r' %
> (self.mode, new_mode)) +
> +        self.mode = new_mode
> +
> +    def get_unihash(self, method, taskhash):
> +        self._set_mode(self.MODE_GET_STREAM)
> +        r = self.send_stream('%s %s' % (method, taskhash))
> +        if not r:
> +            return None
> +        return r
> +
> +    def report_unihash(self, taskhash, method, outhash, unihash,
> extra={}):
> +        self._set_mode(self.MODE_NORMAL)
> +        m = extra.copy()
> +        m['taskhash'] = taskhash
> +        m['method'] = method
> +        m['outhash'] = outhash
> +        m['unihash'] = unihash
> +        return self.send_message({'report': m})
> +
> +    def get_stats(self):
> +        self._set_mode(self.MODE_NORMAL)
> +        return self.send_message({'get-stats': None})
> +
> +    def reset_stats(self):
> +        self._set_mode(self.MODE_NORMAL)
> +        return self.send_message({'reset-stats': None})
> diff --git a/bitbake/lib/hashserv/server.py
> b/bitbake/lib/hashserv/server.py new file mode 100644
> index 0000000..0aff776
> --- /dev/null
> +++ b/bitbake/lib/hashserv/server.py
> @@ -0,0 +1,414 @@
> +# Copyright (C) 2019 Garmin Ltd.
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
> +from contextlib import closing
> +from datetime import datetime
> +import asyncio
> +import json
> +import logging
> +import math
> +import os
> +import signal
> +import socket
> +import time
> +
> +logger = logging.getLogger('hashserv.server')
> +
> +
> +class Measurement(object):
> +    def __init__(self, sample):
> +        self.sample = sample
> +
> +    def start(self):
> +        self.start_time = time.perf_counter()
> +
> +    def end(self):
> +        self.sample.add(time.perf_counter() - self.start_time)
> +
> +    def __enter__(self):
> +        self.start()
> +        return self
> +
> +    def __exit__(self, *args, **kwargs):
> +        self.end()
> +
> +
> +class Sample(object):
> +    def __init__(self, stats):
> +        self.stats = stats
> +        self.num_samples = 0
> +        self.elapsed = 0
> +
> +    def measure(self):
> +        return Measurement(self)
> +
> +    def __enter__(self):
> +        return self
> +
> +    def __exit__(self, *args, **kwargs):
> +        self.end()
> +
> +    def add(self, elapsed):
> +        self.num_samples += 1
> +        self.elapsed += elapsed
> +
> +    def end(self):
> +        if self.num_samples:
> +            self.stats.add(self.elapsed)
> +            self.num_samples = 0
> +            self.elapsed = 0
> +
> +
> +class Stats(object):
> +    def __init__(self):
> +        self.reset()
> +
> +    def reset(self):
> +        self.num = 0
> +        self.total_time = 0
> +        self.max_time = 0
> +        self.m = 0
> +        self.s = 0
> +        self.current_elapsed = None
> +
> +    def add(self, elapsed):
> +        self.num += 1
> +        if self.num == 1:
> +            self.m = elapsed
> +            self.s = 0
> +        else:
> +            last_m = self.m
> +            self.m = last_m + (elapsed - last_m) / self.num
> +            self.s = self.s + (elapsed - last_m) * (elapsed - self.m)
> +
> +        self.total_time += elapsed
> +
> +        if self.max_time < elapsed:
> +            self.max_time = elapsed
> +
> +    def start_sample(self):
> +        return Sample(self)
> +
> +    @property
> +    def average(self):
> +        if self.num == 0:
> +            return 0
> +        return self.total_time / self.num
> +
> +    @property
> +    def stdev(self):
> +        if self.num <= 1:
> +            return 0
> +        return math.sqrt(self.s / (self.num - 1))
> +
> +    def todict(self):
> +        return {k: getattr(self, k) for k in ('num', 'total_time',
> 'max_time', 'average', 'stdev')} +
> +
> +class ServerClient(object):
> +    def __init__(self, reader, writer, db, request_stats):
> +        self.reader = reader
> +        self.writer = writer
> +        self.db = db
> +        self.request_stats = request_stats
> +
> +    async def process_requests(self):
> +        try:
> +            self.addr = self.writer.get_extra_info('peername')
> +            logger.debug('Client %r connected' % (self.addr,))
> +
> +            # Read protocol and version
> +            protocol = await self.reader.readline()
> +            if protocol is None:
> +                return
> +
> +            (proto_name, proto_version) =
> protocol.decode('utf-8').rstrip().split()
> +            if proto_name != 'OEHASHEQUIV' or proto_version != '1.0':
> +                return
> +
> +            # Read headers. Currently, no headers are implemented,
> so look for
> +            # an empty line to signal the end of the headers
> +            while True:
> +                line = await self.reader.readline()
> +                if line is None:
> +                    return
> +
> +                line = line.decode('utf-8').rstrip()
> +                if not line:
> +                    break
> +
> +            # Handle messages
> +            handlers = {
> +                'get': self.handle_get,
> +                'report': self.handle_report,
> +                'get-stream': self.handle_get_stream,
> +                'get-stats': self.handle_get_stats,
> +                'reset-stats': self.handle_reset_stats,
> +            }
> +
> +            while True:
> +                d = await self.read_message()
> +                if d is None:
> +                    break
> +
> +                for k in handlers.keys():
> +                    if k in d:
> +                        logger.debug('Handling %s' % k)
> +                        if 'stream' in k:
> +                            await handlers[k](d[k])
> +                        else:
> +                            with self.request_stats.start_sample()
> as self.request_sample, \
> +                                    self.request_sample.measure():
> +                                await handlers[k](d[k])
> +                        break
> +                else:
> +                    logger.warning("Unrecognized command %r" % d)
> +                    break
> +
> +                await self.writer.drain()
> +        finally:
> +            self.writer.close()
> +
> +    def write_message(self, msg):
> +        self.writer.write(('%s\n' % json.dumps(msg)).encode('utf-8'))
> +
> +    async def read_message(self):
> +        l = await self.reader.readline()
> +        if not l:
> +            return None
> +
> +        try:
> +            message = l.decode('utf-8')
> +
> +            if not message.endswith('\n'):
> +                return None
> +
> +            return json.loads(message)
> +        except (json.JSONDecodeError, UnicodeDecodeError) as e:
> +            logger.error('Bad message from client: %r' % message)
> +            raise e
> +
> +    async def handle_get(self, request):
> +        method = request['method']
> +        taskhash = request['taskhash']
> +
> +        row = self.query_equivalent(method, taskhash)
> +        if row is not None:
> +            logger.debug('Found equivalent task %s -> %s',
> (row['taskhash'], row['unihash']))
> +            d = {k: row[k] for k in ('taskhash', 'method',
> 'unihash')} +
> +            self.write_message(d)
> +        else:
> +            self.write_message(None)
> +
> +    async def handle_get_stream(self, request):
> +        self.write_message('ok')
> +
> +        while True:
> +            l = await self.reader.readline()
> +            if not l:
> +                return
> +
> +            try:
> +                # This inner loop is very sensitive and must be as
> fast as
> +                # possible (which is why the request sample is
> handled manually
> +                # instead of using 'with', and also why logging
> statements are
> +                # commented out.
> +                self.request_sample =
> self.request_stats.start_sample()
> +                request_measure = self.request_sample.measure()
> +                request_measure.start()
> +
> +                l = l.decode('utf-8').rstrip()
> +                if l == 'END':
> +                    self.writer.write('ok\n'.encode('utf-8'))
> +                    return
> +
> +                (method, taskhash) = l.split()
> +                #logger.debug('Looking up %s %s' % (method,
> taskhash))
> +                row = self.query_equivalent(method, taskhash)
> +                if row is not None:
> +                    msg = ('%s\n' % row['unihash']).encode('utf-8')
> +                    #logger.debug('Found equivalent task %s -> %s',
> (row['taskhash'], row['unihash']))
> +                else:
> +                    msg = '\n'.encode('utf-8')
> +
> +                self.writer.write(msg)
> +            finally:
> +                request_measure.end()
> +                self.request_sample.end()
> +
> +            await self.writer.drain()
> +
> +    async def handle_report(self, data):
> +        with closing(self.db.cursor()) as cursor:
> +            cursor.execute('''
> +                -- Find tasks with a matching outhash (that is,
> tasks that
> +                -- are equivalent)
> +                SELECT taskhash, method, unihash FROM tasks_v2 WHERE
> method=:method AND outhash=:outhash +
> +                -- If there is an exact match on the taskhash,
> return it.
> +                -- Otherwise return the oldest matching outhash of
> any
> +                -- taskhash
> +                ORDER BY CASE WHEN taskhash=:taskhash THEN 1 ELSE 2
> END,
> +                    created ASC
> +
> +                -- Only return one row
> +                LIMIT 1
> +                ''', {k: data[k] for k in ('method', 'outhash',
> 'taskhash')}) +
> +            row = cursor.fetchone()
> +
> +            # If no matching outhash was found, or one *was* found
> but it
> +            # wasn't an exact match on the taskhash, a new entry for
> this
> +            # taskhash should be added
> +            if row is None or row['taskhash'] != data['taskhash']:
> +                # If a row matching the outhash was found, the
> unihash for
> +                # the new taskhash should be the same as that one.
> +                # Otherwise the caller provided unihash is used.
> +                unihash = data['unihash']
> +                if row is not None:
> +                    unihash = row['unihash']
> +
> +                insert_data = {
> +                    'method': data['method'],
> +                    'outhash': data['outhash'],
> +                    'taskhash': data['taskhash'],
> +                    'unihash': unihash,
> +                    'created': datetime.now()
> +                }
> +
> +                for k in ('owner', 'PN', 'PV', 'PR', 'task',
> 'outhash_siginfo'):
> +                    if k in data:
> +                        insert_data[k] = data[k]
> +
> +                cursor.execute('''INSERT INTO tasks_v2 (%s) VALUES
> (%s)''' % (
> +                    ', '.join(sorted(insert_data.keys())),
> +                    ', '.join(':' + k for k in
> sorted(insert_data.keys()))),
> +                    insert_data)
> +
> +                self.db.commit()
> +
> +                logger.info('Adding taskhash %s with unihash %s',
> +                            data['taskhash'], unihash)
> +
> +                d = {
> +                    'taskhash': data['taskhash'],
> +                    'method': data['method'],
> +                    'unihash': unihash
> +                }
> +            else:
> +                d = {k: row[k] for k in ('taskhash', 'method',
> 'unihash')} +
> +        self.write_message(d)
> +
> +    async def handle_get_stats(self, request):
> +        d = {
> +            'requests': self.request_stats.todict(),
> +        }
> +
> +        self.write_message(d)
> +
> +    async def handle_reset_stats(self, request):
> +        d = {
> +            'requests': self.request_stats.todict(),
> +        }
> +
> +        self.request_stats.reset()
> +        self.write_message(d)
> +
> +    def query_equivalent(self, method, taskhash):
> +        # This is part of the inner loop and must be as fast as
> possible
> +        try:
> +            cursor = self.db.cursor()
> +            cursor.execute('SELECT taskhash, method, unihash FROM
> tasks_v2 WHERE method=:method AND taskhash=:taskhash ORDER BY created
> ASC LIMIT 1',
> +                           {'method': method, 'taskhash': taskhash})
> +            return cursor.fetchone()
> +        except:
> +            cursor.close()
> +
> +
> +class Server(object):
> +    def __init__(self, db, loop=None):
> +        self.request_stats = Stats()
> +        self.db = db
> +
> +        if loop is None:
> +            self.loop = asyncio.new_event_loop()
> +            self.close_loop = True
> +        else:
> +            self.loop = loop
> +            self.close_loop = False
> +
> +        self._cleanup_socket = None
> +
> +    def start_tcp_server(self, host, port):
> +        self.server = self.loop.run_until_complete(
> +            asyncio.start_server(self.handle_client, host, port,
> loop=self.loop)
> +        )
> +
> +        for s in self.server.sockets:
> +            logger.info('Listening on %r' % (s.getsockname(),))
> +            # Newer python does this automatically. Do it manually
> here for
> +            # maximum compatibility
> +            s.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
> +            s.setsockopt(socket.SOL_TCP, socket.TCP_QUICKACK, 1)
> +
> +        name = self.server.sockets[0].getsockname()
> +        if self.server.sockets[0].family == socket.AF_INET6:
> +            self.address = "[%s]:%d" % (name[0], name[1])
> +        else:
> +            self.address = "%s:%d" % (name[0], name[1])
> +
> +    def start_unix_server(self, path):
> +        def cleanup():
> +            os.unlink(path)
> +
> +        cwd = os.getcwd()
> +        try:
> +            # Work around path length limits in AF_UNIX
> +            os.chdir(os.path.dirname(path))
> +            self.server = self.loop.run_until_complete(
> +                asyncio.start_unix_server(self.handle_client,
> os.path.basename(path), loop=self.loop)
> +            )
> +        finally:
> +            os.chdir(cwd)
> +
> +        logger.info('Listening on %r' % path)
> +
> +        self._cleanup_socket = cleanup
> +        self.address = "unix://%s" % os.path.abspath(path)
> +
> +    async def handle_client(self, reader, writer):
> +        # writer.transport.set_write_buffer_limits(0)
> +        try:
> +            client = ServerClient(reader, writer, self.db,
> self.request_stats)
> +            await client.process_requests()
> +        except Exception as e:
> +            import traceback
> +            logger.error('Error from client: %s' % str(e),
> exc_info=True)
> +            traceback.print_exc()
> +            writer.close()
> +        logger.info('Client disconnected')
> +
> +    def serve_forever(self):
> +        def signal_handler():
> +            self.loop.stop()
> +
> +        self.loop.add_signal_handler(signal.SIGTERM, signal_handler)
> +
> +        try:
> +            self.loop.run_forever()
> +        except KeyboardInterrupt:
> +            pass
> +
> +        self.server.close()
> +        self.loop.run_until_complete(self.server.wait_closed())
> +        logger.info('Server shutting down')
> +
> +        if self.close_loop:
> +            self.loop.close()
> +
> +        if self._cleanup_socket is not None:
> +            self._cleanup_socket()
> diff --git a/bitbake/lib/hashserv/tests.py
> b/bitbake/lib/hashserv/tests.py new file mode 100644
> index 0000000..a5472a9
> --- /dev/null
> +++ b/bitbake/lib/hashserv/tests.py
> @@ -0,0 +1,142 @@
> +#! /usr/bin/env python3
> +#
> +# Copyright (C) 2018-2019 Garmin Ltd.
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
> +from . import create_server, create_client
> +import hashlib
> +import logging
> +import multiprocessing
> +import sys
> +import tempfile
> +import threading
> +import unittest
> +
> +
> +class TestHashEquivalenceServer(object):
> +    METHOD = 'TestMethod'
> +
> +    def _run_server(self):
> +        # logging.basicConfig(level=logging.DEBUG,
> filename='bbhashserv.log', filemode='w',
> +        #                     format='%(levelname)s
> %(filename)s:%(lineno)d %(message)s')
> +        self.server.serve_forever()
> +
> +    def setUp(self):
> +        if sys.version_info < (3, 5, 0):
> +            self.skipTest('Python 3.5 or later required')
> +
> +        self.temp_dir =
> tempfile.TemporaryDirectory(prefix='bb-hashserv')
> +        self.dbfile = os.path.join(self.temp_dir.name, 'db.sqlite')
> +
> +        self.server = create_server(self.get_server_addr(),
> self.dbfile)
> +        self.server_thread =
> multiprocessing.Process(target=self._run_server)
> +        self.server_thread.start()
> +        self.client = create_client(self.server.address)
> +
> +    def tearDown(self):
> +        # Shutdown server
> +        s = getattr(self, 'server', None)
> +        if s is not None:
> +            self.server_thread.terminate()
> +            self.server_thread.join()
> +        self.client.close()
> +        self.temp_dir.cleanup()
> +
> +    def test_create_hash(self):
> +        # Simple test that hashes can be created
> +        taskhash = '35788efcb8dfb0a02659d81cf2bfd695fb30faf9'
> +        outhash =
> '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f'
> +        unihash = 'f46d3fbb439bd9b921095da657a4de906510d2cd'
> +
> +        result = self.client.get_unihash(self.METHOD, taskhash)
> +        self.assertIsNone(result, msg='Found unexpected task, %r' %
> result) +
> +        result = self.client.report_unihash(taskhash, self.METHOD,
> outhash, unihash)
> +        self.assertEqual(result['unihash'], unihash, 'Server
> returned bad unihash') +
> +    def test_create_equivalent(self):
> +        # Tests that a second reported task with the same outhash
> will be
> +        # assigned the same unihash
> +        taskhash = '53b8dce672cb6d0c73170be43f540460bfc347b4'
> +        outhash =
> '5a9cb1649625f0bf41fc7791b635cd9c2d7118c7f021ba87dcd03f72b67ce7a8'
> +        unihash = 'f37918cc02eb5a520b1aff86faacbc0a38124646'
> +
> +        result = self.client.report_unihash(taskhash, self.METHOD,
> outhash, unihash)
> +        self.assertEqual(result['unihash'], unihash, 'Server
> returned bad unihash') +
> +        # Report a different task with the same outhash. The
> returned unihash
> +        # should match the first task
> +        taskhash2 = '3bf6f1e89d26205aec90da04854fbdbf73afe6b4'
> +        unihash2 = 'af36b199320e611fbb16f1f277d3ee1d619ca58b'
> +        result = self.client.report_unihash(taskhash2, self.METHOD,
> outhash, unihash2)
> +        self.assertEqual(result['unihash'], unihash, 'Server
> returned bad unihash') +
> +    def test_duplicate_taskhash(self):
> +        # Tests that duplicate reports of the same taskhash with
> different
> +        # outhash & unihash always return the unihash from the first
> reported
> +        # taskhash
> +        taskhash = '8aa96fcffb5831b3c2c0cb75f0431e3f8b20554a'
> +        outhash =
> 'afe240a439959ce86f5e322f8c208e1fedefea9e813f2140c81af866cc9edf7e'
> +        unihash = '218e57509998197d570e2c98512d0105985dffc9'
> +        self.client.report_unihash(taskhash, self.METHOD, outhash,
> unihash) +
> +        result = self.client.get_unihash(self.METHOD, taskhash)
> +        self.assertEqual(result, unihash)
> +
> +        outhash2 =
> '0904a7fe3dc712d9fd8a74a616ddca2a825a8ee97adf0bd3fc86082c7639914d'
> +        unihash2 = 'ae9a7d252735f0dafcdb10e2e02561ca3a47314c'
> +        self.client.report_unihash(taskhash, self.METHOD, outhash2,
> unihash2) +
> +        result = self.client.get_unihash(self.METHOD, taskhash)
> +        self.assertEqual(result, unihash)
> +
> +        outhash3 =
> '77623a549b5b1a31e3732dfa8fe61d7ce5d44b3370f253c5360e136b852967b4'
> +        unihash3 = '9217a7d6398518e5dc002ed58f2cbbbc78696603'
> +        self.client.report_unihash(taskhash, self.METHOD, outhash3,
> unihash3) +
> +        result = self.client.get_unihash(self.METHOD, taskhash)
> +        self.assertEqual(result, unihash)
> +
> +    def test_stress(self):
> +        def query_server(failures):
> +            client = Client(self.server.address)
> +            try:
> +                for i in range(1000):
> +                    taskhash = hashlib.sha256()
> +                    taskhash.update(str(i).encode('utf-8'))
> +                    taskhash = taskhash.hexdigest()
> +                    result = client.get_unihash(self.METHOD,
> taskhash)
> +                    if result != taskhash:
> +                        failures.append("taskhash mismatch: %s !=
> %s" % (result, taskhash))
> +            finally:
> +                client.close()
> +
> +        # Report hashes
> +        for i in range(1000):
> +            taskhash = hashlib.sha256()
> +            taskhash.update(str(i).encode('utf-8'))
> +            taskhash = taskhash.hexdigest()
> +            self.client.report_unihash(taskhash, self.METHOD,
> taskhash, taskhash) +
> +        failures = []
> +        threads = [threading.Thread(target=query_server,
> args=(failures,)) for t in range(100)] +
> +        for t in threads:
> +            t.start()
> +
> +        for t in threads:
> +            t.join()
> +
> +        self.assertFalse(failures)
> +
> +
> +class TestHashEquivalenceUnixServer(TestHashEquivalenceServer,
> unittest.TestCase):
> +    def get_server_addr(self):
> +        return "unix://" + os.path.join(self.temp_dir.name, 'sock')
> +
> +
> +class TestHashEquivalenceTCPServer(TestHashEquivalenceServer,
> unittest.TestCase):
> +    def get_server_addr(self):
> +        return "localhost:0"
> diff --git a/bitbake/lib/layerindexlib/__init__.py
> b/bitbake/lib/layerindexlib/__init__.py index cb79cb3..77196b4 100644
> --- a/bitbake/lib/layerindexlib/__init__.py
> +++ b/bitbake/lib/layerindexlib/__init__.py
> @@ -1,17 +1,7 @@
>  # Copyright (C) 2016-2018 Wind River Systems, Inc.
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> -# See the GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> -# along with this program; if not, write to the Free Software
> -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
> 02111-1307 USA 
>  import datetime
>  
> @@ -386,7 +376,7 @@ layerBranches set.  If not, they are effectively
> blank.''' invalid.append(name)
>  
>  
> -        def _resolve_dependencies(layerbranches, ignores,
> dependencies, invalid):
> +        def _resolve_dependencies(layerbranches, ignores,
> dependencies, invalid, processed=None): for layerbranch in
> layerbranches: if ignores and layerbranch.layer.name in ignores:
>                      continue
> @@ -398,6 +388,13 @@ layerBranches set.  If not, they are effectively
> blank.''' if ignores and deplayerbranch.layer.name in ignores:
>                          continue
>  
> +                    # Since this is depth first, we need to know
> what we're currently processing
> +                    # in order to avoid infinite recursion on a loop.
> +                    if processed and deplayerbranch.layer.name in
> processed:
> +                        # We have found a recursion...
> +                        logger.warning('Circular layer dependency
> found: %s -> %s' % (processed, deplayerbranch.layer.name))
> +                        continue
> +
>                      # This little block is why we can't re-use the
> LayerIndexObj version, # we must be able to satisfy each dependencies
> across layer indexes and # use the layer index order for priority.
> (r stands for replacement below) @@ -421,7 +418,17 @@ layerBranches
> set.  If not, they are effectively blank.''' 
>                      # New dependency, we need to resolve it now...
> depth-first if deplayerbranch.layer.name not in dependencies:
> -                        (dependencies, invalid) =
> _resolve_dependencies([deplayerbranch], ignores, dependencies,
> invalid)
> +                        # Avoid recursion on this branch.
> +                        # We copy so we don't end up polluting the
> depth-first branch with other
> +                        # branches.  Duplication between individual
> branches IS expected and
> +                        # handled by 'dependencies' processing.
> +                        if not processed:
> +                            local_processed = []
> +                        else:
> +                            local_processed = processed.copy()
> +
> local_processed.append(deplayerbranch.layer.name) +
> +                        (dependencies, invalid) =
> _resolve_dependencies([deplayerbranch], ignores, dependencies,
> invalid, local_processed) if deplayerbranch.layer.name not in
> dependencies: dependencies[deplayerbranch.layer.name] =
> [deplayerbranch, layerdependency] diff --git
> a/bitbake/lib/layerindexlib/cooker.py
> b/bitbake/lib/layerindexlib/cooker.py index 848f0e2..604a961 100644
> --- a/bitbake/lib/layerindexlib/cooker.py +++
> b/bitbake/lib/layerindexlib/cooker.py @@ -1,17 +1,7 @@
>  # Copyright (C) 2016-2018 Wind River Systems, Inc.
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> -# See the GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> -# along with this program; if not, write to the Free Software
> -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
> 02111-1307 USA 
>  import logging
>  import json
> diff --git a/bitbake/lib/layerindexlib/plugin.py
> b/bitbake/lib/layerindexlib/plugin.py index 92a2e97..7015a1a 100644
> --- a/bitbake/lib/layerindexlib/plugin.py
> +++ b/bitbake/lib/layerindexlib/plugin.py
> @@ -1,18 +1,7 @@
>  # Copyright (C) 2016-2018 Wind River Systems, Inc.
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> -# See the GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> -# along with this program; if not, write to the Free Software
> -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
> 02111-1307 USA -
>  # The file contains:
>  #   LayerIndex exceptions
>  #   Plugin base class
> diff --git a/bitbake/lib/layerindexlib/restapi.py
> b/bitbake/lib/layerindexlib/restapi.py index d08eb20..21fd144 100644
> --- a/bitbake/lib/layerindexlib/restapi.py
> +++ b/bitbake/lib/layerindexlib/restapi.py
> @@ -1,17 +1,7 @@
>  # Copyright (C) 2016-2018 Wind River Systems, Inc.
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> -# See the GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> -# along with this program; if not, write to the Free Software
> -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
> 02111-1307 USA 
>  import logging
>  import json
> diff --git a/bitbake/lib/layerindexlib/tests/common.py
> b/bitbake/lib/layerindexlib/tests/common.py index 22a5458..077382f
> 100644 --- a/bitbake/lib/layerindexlib/tests/common.py
> +++ b/bitbake/lib/layerindexlib/tests/common.py
> @@ -1,17 +1,7 @@
>  # Copyright (C) 2017-2018 Wind River Systems, Inc.
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> -# See the GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> -# along with this program; if not, write to the Free Software
> -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
> 02111-1307 USA 
>  import unittest
>  import tempfile
> diff --git a/bitbake/lib/layerindexlib/tests/cooker.py
> b/bitbake/lib/layerindexlib/tests/cooker.py index fdbf091..1fa102e
> 100644 --- a/bitbake/lib/layerindexlib/tests/cooker.py
> +++ b/bitbake/lib/layerindexlib/tests/cooker.py
> @@ -1,17 +1,7 @@
>  # Copyright (C) 2018 Wind River Systems, Inc.
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> -# See the GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> -# along with this program; if not, write to the Free Software
> -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
> 02111-1307 USA 
>  import unittest
>  import tempfile
> diff --git a/bitbake/lib/layerindexlib/tests/layerindexobj.py
> b/bitbake/lib/layerindexlib/tests/layerindexobj.py index
> e2fbb95..0c5ec88 100644 ---
> a/bitbake/lib/layerindexlib/tests/layerindexobj.py +++
> b/bitbake/lib/layerindexlib/tests/layerindexobj.py @@ -1,17 +1,7 @@
>  # Copyright (C) 2017-2018 Wind River Systems, Inc.
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> -# See the GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> -# along with this program; if not, write to the Free Software
> -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
> 02111-1307 USA 
>  import unittest
>  import tempfile
> diff --git a/bitbake/lib/layerindexlib/tests/restapi.py
> b/bitbake/lib/layerindexlib/tests/restapi.py index 5876695..6d8dc00
> 100644 --- a/bitbake/lib/layerindexlib/tests/restapi.py
> +++ b/bitbake/lib/layerindexlib/tests/restapi.py
> @@ -1,17 +1,7 @@
>  # Copyright (C) 2017-2018 Wind River Systems, Inc.
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> -# See the GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> -# along with this program; if not, write to the Free Software
> -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
> 02111-1307 USA 
>  import unittest
>  import tempfile
> diff --git a/bitbake/lib/progressbar/__init__.py
> b/bitbake/lib/progressbar/__init__.py index fbab744..c545a62 100644
> --- a/bitbake/lib/progressbar/__init__.py
> +++ b/bitbake/lib/progressbar/__init__.py
> @@ -4,6 +4,8 @@
>  # progressbar  - Text progress bar library for Python.
>  # Copyright (c) 2005 Nilton Volpato
>  #
> +# SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause-Clear
> +#
>  # This library is free software; you can redistribute it and/or
>  # modify it under the terms of the GNU Lesser General Public
>  # License as published by the Free Software Foundation; either
> diff --git a/bitbake/lib/progressbar/compat.py
> b/bitbake/lib/progressbar/compat.py index a39f4a1..9804e0b 100644
> --- a/bitbake/lib/progressbar/compat.py
> +++ b/bitbake/lib/progressbar/compat.py
> @@ -3,6 +3,8 @@
>  # progressbar  - Text progress bar library for Python.
>  # Copyright (c) 2005 Nilton Volpato
>  #
> +# SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause-Clear
> +#
>  # This library is free software; you can redistribute it and/or
>  # modify it under the terms of the GNU Lesser General Public
>  # License as published by the Free Software Foundation; either
> diff --git a/bitbake/lib/progressbar/progressbar.py
> b/bitbake/lib/progressbar/progressbar.py index 2873ad6..e2b6ba1 100644
> --- a/bitbake/lib/progressbar/progressbar.py
> +++ b/bitbake/lib/progressbar/progressbar.py
> @@ -5,6 +5,8 @@
>  #
>  # (With some small changes after importing into BitBake)
>  #
> +# SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause-Clear
> +#
>  # This library is free software; you can redistribute it and/or
>  # modify it under the terms of the GNU Lesser General Public
>  # License as published by the Free Software Foundation; either
> diff --git a/bitbake/lib/progressbar/widgets.py
> b/bitbake/lib/progressbar/widgets.py index 77285ca..0772aa5 100644
> --- a/bitbake/lib/progressbar/widgets.py
> +++ b/bitbake/lib/progressbar/widgets.py
> @@ -3,6 +3,8 @@
>  # progressbar  - Text progress bar library for Python.
>  # Copyright (c) 2005 Nilton Volpato
>  #
> +# SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause-Clear
> +#
>  # This library is free software; you can redistribute it and/or
>  # modify it under the terms of the GNU Lesser General Public
>  # License as published by the Free Software Foundation; either
> diff --git a/bitbake/lib/prserv/__init__.py
> b/bitbake/lib/prserv/__init__.py index c3cb73a..9961040 100644
> --- a/bitbake/lib/prserv/__init__.py
> +++ b/bitbake/lib/prserv/__init__.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  __version__ = "1.0.0"
>  
>  import os, time
> diff --git a/bitbake/lib/prserv/db.py b/bitbake/lib/prserv/db.py
> index 495d09f..117d8c0 100644
> --- a/bitbake/lib/prserv/db.py
> +++ b/bitbake/lib/prserv/db.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  import logging
>  import os.path
>  import errno
> @@ -253,7 +257,7 @@ class PRData(object):
>          self.connection=sqlite3.connect(self.filename,
> isolation_level="EXCLUSIVE", check_same_thread = False)
> self.connection.row_factory=sqlite3.Row
> self.connection.execute("pragma synchronous = off;")
> -        self.connection.execute("PRAGMA journal_mode = WAL;")
> +        self.connection.execute("PRAGMA journal_mode = MEMORY;")
>          self._tables={}
>  
>      def disconnect(self):
> diff --git a/bitbake/lib/prserv/serv.py b/bitbake/lib/prserv/serv.py
> index 6a99728..be3acec 100644
> --- a/bitbake/lib/prserv/serv.py
> +++ b/bitbake/lib/prserv/serv.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  import os,sys,logging
>  import signal, time
>  from xmlrpc.server import SimpleXMLRPCServer,
> SimpleXMLRPCRequestHandler diff --git a/bitbake/lib/pyinotify.py
> b/bitbake/lib/pyinotify.py index 4eb03b0..1528a22 100644
> --- a/bitbake/lib/pyinotify.py
> +++ b/bitbake/lib/pyinotify.py
> @@ -1,25 +1,9 @@
> -#!/usr/bin/env python
> -
> +#
>  # pyinotify.py - python interface to inotify
>  # Copyright (c) 2005-2015 Sebastien Martini <seb@dbzteam.org>
>  #
> -# Permission is hereby granted, free of charge, to any person
> obtaining a copy -# of this software and associated documentation
> files (the "Software"), to deal -# in the Software without
> restriction, including without limitation the rights -# to use, copy,
> modify, merge, publish, distribute, sublicense, and/or sell -# copies
> of the Software, and to permit persons to whom the Software is -#
> furnished to do so, subject to the following conditions: -#
> -# The above copyright notice and this permission notice shall be
> included in -# all copies or substantial portions of the Software.
> +# SPDX-License-Identifier: MIT
>  #
> -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND
> NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT
> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY,
> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
> DEALINGS IN -# THE SOFTWARE. """
>  pyinotify
>  
> diff --git a/bitbake/lib/toaster/bldcollector/admin.py
> b/bitbake/lib/toaster/bldcollector/admin.py index 1f2e07f..feaa888
> 100644 --- a/bitbake/lib/toaster/bldcollector/admin.py
> +++ b/bitbake/lib/toaster/bldcollector/admin.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  from django.contrib import admin
>  from orm.models import BitbakeVersion, Release, ToasterSetting,
> Layer_Version from django import forms
> diff --git a/bitbake/lib/toaster/bldcollector/urls.py
> b/bitbake/lib/toaster/bldcollector/urls.py index 888175d..8eb1e34
> 100644 --- a/bitbake/lib/toaster/bldcollector/urls.py
> +++ b/bitbake/lib/toaster/bldcollector/urls.py
> @@ -3,19 +3,8 @@
>  #
>  # Copyright (C) 2014-2017   Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -
>  
>  from django.conf.urls import include, url
>  
> diff --git a/bitbake/lib/toaster/bldcollector/views.py
> b/bitbake/lib/toaster/bldcollector/views.py index f32fa4d..c708b41
> 100644 --- a/bitbake/lib/toaster/bldcollector/views.py
> +++ b/bitbake/lib/toaster/bldcollector/views.py
> @@ -3,18 +3,8 @@
>  #
>  # Copyright (C) 2014        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.views.decorators.cache import cache_control
>  from django.core.urlresolvers import reverse
> diff --git a/bitbake/lib/toaster/bldcontrol/admin.py
> b/bitbake/lib/toaster/bldcontrol/admin.py index fcbe5f5..e85c30e
> 100644 --- a/bitbake/lib/toaster/bldcontrol/admin.py
> +++ b/bitbake/lib/toaster/bldcontrol/admin.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  from django.contrib import admin
>  from django.contrib.admin.filters import RelatedFieldListFilter
>  from .models import BuildEnvironment
> diff --git a/bitbake/lib/toaster/bldcontrol/bbcontroller.py
> b/bitbake/lib/toaster/bldcontrol/bbcontroller.py index
> 5195600..301df18 100644 ---
> a/bitbake/lib/toaster/bldcontrol/bbcontroller.py +++
> b/bitbake/lib/toaster/bldcontrol/bbcontroller.py @@ -1,24 +1,10 @@
>  #
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2014        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -
>  
>  import os
>  import sys
> diff --git a/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py
> b/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py index
> 9490635..39ea736 100644 ---
> a/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py +++
> b/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py @@ -1,24
> +1,10 @@ #
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2014        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -
>  
>  import os
>  import sys
> diff --git
> a/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py
> b/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py
> index 14298d9..fe2c4dc 100644 ---
> a/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py
> +++
> b/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py
> @@ -1,3 +1,7 @@ +# +# SPDX-License-Identifier: GPL-2.0-only +#
> +
>  from django.core.management.base import BaseCommand, CommandError
>  from django.db import transaction
>  
> diff --git
> a/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py
> b/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py
> index 6a55dd4..50ec409 100644 ---
> a/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py +++
> b/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py @@
> -1,3 +1,7 @@ +# +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  from django.core.management.base import BaseCommand
>  from django.db import transaction
>  from django.db.models import Q
> diff --git a/bitbake/lib/toaster/bldcontrol/models.py
> b/bitbake/lib/toaster/bldcontrol/models.py index 409614b..bcffcf5
> 100644 --- a/bitbake/lib/toaster/bldcontrol/models.py
> +++ b/bitbake/lib/toaster/bldcontrol/models.py
> @@ -1,3 +1,7 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  from __future__ import unicode_literals
>  from django.db import models
>  from django.core.validators import MaxValueValidator,
> MinValueValidator diff --git
> a/bitbake/lib/toaster/bldcontrol/views.py
> b/bitbake/lib/toaster/bldcontrol/views.py index 60f00ef..286d88b
> 100644 --- a/bitbake/lib/toaster/bldcontrol/views.py +++
> b/bitbake/lib/toaster/bldcontrol/views.py @@ -1 +1,5 @@
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  # Create your views here.
> diff --git a/bitbake/lib/toaster/manage.py
> b/bitbake/lib/toaster/manage.py index 0c7ea50..ae32619 100755
> --- a/bitbake/lib/toaster/manage.py
> +++ b/bitbake/lib/toaster/manage.py
> @@ -1,4 +1,8 @@
>  #!/usr/bin/env python3
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  import os
>  import sys
>  
> diff --git a/bitbake/lib/toaster/orm/fixtures/oe-core.xml
> b/bitbake/lib/toaster/orm/fixtures/oe-core.xml index fec93ab..a723f5a
> 100644 --- a/bitbake/lib/toaster/orm/fixtures/oe-core.xml
> +++ b/bitbake/lib/toaster/orm/fixtures/oe-core.xml
> @@ -8,9 +8,9 @@
>  
>    <!-- Bitbake versions which correspond to the metadata release -->
>    <object model="orm.bitbakeversion" pk="1">
> -    <field type="CharField" name="name">sumo</field>
> +    <field type="CharField" name="name">warrior</field>
>      <field type="CharField"
> name="giturl">git://git.openembedded.org/bitbake</field>
> -    <field type="CharField" name="branch">1.38</field>
> +    <field type="CharField" name="branch">1.42</field>
>    </object>
>    <object model="orm.bitbakeversion" pk="2">
>      <field type="CharField" name="name">HEAD</field>
> @@ -23,18 +23,18 @@
>      <field type="CharField" name="branch">master</field>
>    </object>
>    <object model="orm.bitbakeversion" pk="4">
> -    <field type="CharField" name="name">thud</field>
> +    <field type="CharField" name="name">zeus</field>
>      <field type="CharField"
> name="giturl">git://git.openembedded.org/bitbake</field>
> -    <field type="CharField" name="branch">1.40</field>
> +    <field type="CharField" name="branch">1.44</field>
>    </object>
>  
>    <!-- Releases available -->
>    <object model="orm.release" pk="1">
> -    <field type="CharField" name="name">sumo</field>
> -    <field type="CharField" name="description">Openembedded
> Sumo</field>
> +    <field type="CharField" name="name">warrior</field>
> +    <field type="CharField" name="description">Openembedded
> Warrior</field> <field rel="ManyToOneRel" to="orm.bitbakeversion"
> name="bitbake_version">1</field>
> -    <field type="CharField" name="branch_name">sumo</field>
> -    <field type="TextField" name="helptext">Toaster will run your
> builds using the tip of the &lt;a
> href=\"http://cgit.openembedded.org/openembedded-core/log/?h=sumo\"&gt;OpenEmbedded
> Sumo&lt;/a&gt; branch.</field>
> +    <field type="CharField" name="branch_name">warrior</field>
> +    <field type="TextField" name="helptext">Toaster will run your
> builds using the tip of the &lt;a
> href=\"http://cgit.openembedded.org/openembedded-core/log/?h=warrior\"&gt;OpenEmbedded
> Warrior&lt;/a&gt; branch.</field> </object> <object
> model="orm.release" pk="2"> <field type="CharField"
> name="name">local</field> @@ -51,11 +51,11 @@ <field type="TextField"
> name="helptext">Toaster will run your builds using the tip of the
> &lt;a
> href=\"http://cgit.openembedded.org/openembedded-core/log/\"&gt;OpenEmbedded
> master&lt;/a&gt; branch.</field> </object> <object
> model="orm.release" pk="4">
> -    <field type="CharField" name="name">thud</field>
> -    <field type="CharField" name="description">Openembedded
> Rocko</field>
> -    <field rel="ManyToOneRel" to="orm.bitbakeversion"
> name="bitbake_version">1</field>
> -    <field type="CharField" name="branch_name">thud</field>
> -    <field type="TextField" name="helptext">Toaster will run your
> builds using the tip of the &lt;a
> href=\"http://cgit.openembedded.org/openembedded-core/log/?h=thud\"&gt;OpenEmbedded
> Thud&lt;/a&gt; branch.</field>
> +    <field type="CharField" name="name">zeus</field>
> +    <field type="CharField" name="description">Openembedded
> Zeus</field>
> +    <field rel="ManyToOneRel" to="orm.bitbakeversion"
> name="bitbake_version">4</field>
> +    <field type="CharField" name="branch_name">zeus</field>
> +    <field type="TextField" name="helptext">Toaster will run your
> builds using the tip of the &lt;a
> href=\"http://cgit.openembedded.org/openembedded-core/log/?h=zeus\"&gt;OpenEmbedded
> Zeus&lt;/a&gt; branch.</field> </object> <!-- Default layers for each
> release --> diff --git a/bitbake/lib/toaster/orm/fixtures/poky.xml
> b/bitbake/lib/toaster/orm/fixtures/poky.xml index fb9a771..7992383
> 100644 --- a/bitbake/lib/toaster/orm/fixtures/poky.xml
> +++ b/bitbake/lib/toaster/orm/fixtures/poky.xml
> @@ -8,9 +8,9 @@
>  
>    <!-- Bitbake versions which correspond to the metadata release -->
>    <object model="orm.bitbakeversion" pk="1">
> -    <field type="CharField" name="name">sumo</field>
> +    <field type="CharField" name="name">warrior</field>
>      <field type="CharField"
> name="giturl">git://git.yoctoproject.org/poky</field>
> -    <field type="CharField" name="branch">sumo</field>
> +    <field type="CharField" name="branch">warrior</field>
>      <field type="CharField" name="dirpath">bitbake</field>
>    </object>
>    <object model="orm.bitbakeversion" pk="2">
> @@ -26,20 +26,20 @@
>      <field type="CharField" name="dirpath">bitbake</field>
>    </object>
>    <object model="orm.bitbakeversion" pk="4">
> -    <field type="CharField" name="name">thud</field>
> +    <field type="CharField" name="name">zeus</field>
>      <field type="CharField"
> name="giturl">git://git.yoctoproject.org/poky</field>
> -    <field type="CharField" name="branch">thud</field>
> +    <field type="CharField" name="branch">zeus</field>
>      <field type="CharField" name="dirpath">bitbake</field>
>    </object>
>  
>  
>    <!-- Releases available -->
>    <object model="orm.release" pk="1">
> -    <field type="CharField" name="name">sumo</field>
> -    <field type="CharField" name="description">Yocto Project 2.5
> "Sumo"</field>
> +    <field type="CharField" name="name">warrior</field>
> +    <field type="CharField" name="description">Yocto Project 2.7
> "Warrior"</field> <field rel="ManyToOneRel" to="orm.bitbakeversion"
> name="bitbake_version">1</field>
> -    <field type="CharField" name="branch_name">sumo</field>
> -    <field type="TextField" name="helptext">Toaster will run your
> builds using the tip of the &lt;a
> href="http://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=sumo"&gt;Yocto
> Project Sumo branch&lt;/a&gt;.</field>
> +    <field type="CharField" name="branch_name">warrior</field>
> +    <field type="TextField" name="helptext">Toaster will run your
> builds using the tip of the &lt;a
> href="http://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=warrior"&gt;Yocto
> Project Warrior branch&lt;/a&gt;.</field> </object> <object
> model="orm.release" pk="2"> <field type="CharField"
> name="name">local</field> @@ -56,11 +56,11 @@ <field type="TextField"
> name="helptext">Toaster will run your builds using the tip of the
> &lt;a
> href="http://git.yoctoproject.org/cgit/cgit.cgi/poky/log/"&gt;Yocto
> Project Master branch&lt;/a&gt;.</field> </object> <object
> model="orm.release" pk="4">
> -    <field type="CharField" name="name">rocko</field>
> -    <field type="CharField" name="description">Yocto Project 2.6
> "Thud"</field>
> -    <field rel="ManyToOneRel" to="orm.bitbakeversion"
> name="bitbake_version">1</field>
> -    <field type="CharField" name="branch_name">thud</field>
> -    <field type="TextField" name="helptext">Toaster will run your
> builds using the tip of the &lt;a
> href="http://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=thud"&gt;Yocto
> Project Thud branch&lt;/a&gt;.</field>
> +    <field type="CharField" name="name">zeus</field>
> +    <field type="CharField" name="description">Yocto Project 3.0
> "Zeus"</field>
> +    <field rel="ManyToOneRel" to="orm.bitbakeversion"
> name="bitbake_version">4</field>
> +    <field type="CharField" name="branch_name">zeus</field>
> +    <field type="TextField" name="helptext">Toaster will run your
> builds using the tip of the &lt;a
> href="http://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=zeus"&gt;Yocto
> Project Zeus branch&lt;/a&gt;.</field> </object> <!-- Default project
> layers for each release --> @@ -130,7 +130,7 @@
>      <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field>
>      <field type="IntegerField" name="layer_source">0</field>
>      <field rel="ManyToOneRel" to="orm.release"
> name="release">1</field>
> -    <field type="CharField" name="branch">sumo</field>
> +    <field type="CharField" name="branch">warrior</field>
>      <field type="CharField" name="dirpath">meta</field>
>    </object>
>    <object model="orm.layer_version" pk="2">
> @@ -152,7 +152,7 @@
>      <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field>
>      <field type="IntegerField" name="layer_source">0</field>
>      <field rel="ManyToOneRel" to="orm.release"
> name="release">4</field>
> -    <field type="CharField" name="branch">rocko</field>
> +    <field type="CharField" name="branch">zeus</field>
>      <field type="CharField" name="dirpath">meta</field>
>    </object>
>  
> @@ -168,7 +168,7 @@
>      <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field>
>      <field type="IntegerField" name="layer_source">0</field>
>      <field rel="ManyToOneRel" to="orm.release"
> name="release">1</field>
> -    <field type="CharField" name="branch">sumo</field>
> +    <field type="CharField" name="branch">warrior</field>
>      <field type="CharField" name="dirpath">meta-poky</field>
>    </object>
>    <object model="orm.layer_version" pk="6">
> @@ -190,7 +190,7 @@
>      <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field>
>      <field type="IntegerField" name="layer_source">0</field>
>      <field rel="ManyToOneRel" to="orm.release"
> name="release">4</field>
> -    <field type="CharField" name="branch">rocko</field>
> +    <field type="CharField" name="branch">zeus</field>
>      <field type="CharField" name="dirpath">meta-poky</field>
>    </object>
>  
> @@ -206,7 +206,7 @@
>      <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field>
>      <field type="IntegerField" name="layer_source">0</field>
>      <field rel="ManyToOneRel" to="orm.release"
> name="release">1</field>
> -    <field type="CharField" name="branch">sumo</field>
> +    <field type="CharField" name="branch">warrior</field>
>      <field type="CharField" name="dirpath">meta-yocto-bsp</field>
>    </object>
>    <object model="orm.layer_version" pk="10">
> @@ -228,7 +228,7 @@
>      <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field>
>      <field type="IntegerField" name="layer_source">0</field>
>      <field rel="ManyToOneRel" to="orm.release"
> name="release">4</field>
> -    <field type="CharField" name="branch">rocko</field>
> +    <field type="CharField" name="branch">zeus</field>
>      <field type="CharField" name="dirpath">meta-yocto-bsp</field>
>    </object>
>  </django-objects>
> diff --git a/bitbake/lib/toaster/orm/management/commands/lsupdates.py
> b/bitbake/lib/toaster/orm/management/commands/lsupdates.py index
> 66114ff..5b5abbb 100644 ---
> a/bitbake/lib/toaster/orm/management/commands/lsupdates.py +++
> b/bitbake/lib/toaster/orm/management/commands/lsupdates.py @@ -1,23
> +1,10 @@ #
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2016-2017   Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.core.management.base import BaseCommand
>  
> diff --git a/bitbake/lib/toaster/orm/models.py
> b/bitbake/lib/toaster/orm/models.py index 7720290..bb6b5de 100644
> --- a/bitbake/lib/toaster/orm/models.py
> +++ b/bitbake/lib/toaster/orm/models.py
> @@ -1,23 +1,10 @@
>  #
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from __future__ import unicode_literals
>  
> @@ -978,12 +965,12 @@ class TargetSDKFile(models.Model):
>  class Target_Image_File(models.Model):
>      # valid suffixes for image files produced by a build
>      SUFFIXES = {
> -        'btrfs', 'cpio', 'cpio.gz', 'cpio.lz4', 'cpio.lzma',
> 'cpio.xz',
> -        'cramfs', 'elf', 'ext2', 'ext2.bz2', 'ext2.gz', 'ext2.lzma',
> 'ext4',
> -        'ext4.gz', 'ext3', 'ext3.gz', 'hdddirect', 'hddimg', 'iso',
> 'jffs2',
> -        'jffs2.sum', 'multiubi', 'qcow2', 'squashfs', 'squashfs-lzo',
> +        'btrfs', 'container', 'cpio', 'cpio.gz', 'cpio.lz4',
> 'cpio.lzma',
> +        'cpio.xz', 'cramfs', 'ext2', 'ext2.bz2', 'ext2.gz',
> 'ext2.lzma',
> +        'ext3', 'ext3.gz', 'ext4', 'ext4.gz', 'f2fs', 'hddimg',
> 'iso', 'jffs2',
> +        'jffs2.sum', 'multiubi', 'squashfs', 'squashfs-lz4',
> 'squashfs-lzo', 'squashfs-xz', 'tar', 'tar.bz2', 'tar.gz', 'tar.lz4',
> 'tar.xz', 'ubi',
> -        'ubifs', 'vdi', 'vmdk', 'wic', 'wic.bmap', 'wic.bz2',
> 'wic.gz', 'wic.lzma'
> +        'ubifs', 'wic', 'wic.bz2', 'wic.gz', 'wic.lzma'
>      }
>  
>      target = models.ForeignKey(Target)
> diff --git a/bitbake/lib/toaster/tests/browser/selenium_helpers.py
> b/bitbake/lib/toaster/tests/browser/selenium_helpers.py index
> 08711e4..02d4f4b 100644 ---
> a/bitbake/lib/toaster/tests/browser/selenium_helpers.py +++
> b/bitbake/lib/toaster/tests/browser/selenium_helpers.py @@ -1,23
> +1,10 @@ -#! /usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # The Wait class and some of SeleniumDriverHelper and
> SeleniumTestCase are # modified from Patchwork, released under the
> same licence terms as Toaster: diff --git
> a/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
> b/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py index
> 156d639..6c94684 100644 ---
> a/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py +++
> b/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py @@ -1,23
> +1,10 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only
>  #
>  # The Wait class and some of SeleniumDriverHelper and
> SeleniumTestCase are # modified from Patchwork, released under the
> same licence terms as Toaster: diff --git
> a/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
> b/bitbake/lib/toaster/tests/browser/test_all_builds_page.py index
> b86f29b..fba627b 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_all_builds_page.py +++
> b/bitbake/lib/toaster/tests/browser/test_all_builds_page.py @@ -1,23
> +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import re
>  
> diff --git
> a/bitbake/lib/toaster/tests/browser/test_all_projects_page.py
> b/bitbake/lib/toaster/tests/browser/test_all_projects_page.py index
> 44da640..afd2d35 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_all_projects_page.py +++
> b/bitbake/lib/toaster/tests/browser/test_all_projects_page.py @@
> -1,23 +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import re
>  
> diff --git
> a/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py
> b/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py index
> f8ccb54..d972aff 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py +++
> b/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py @@
> -1,23 +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.core.urlresolvers import reverse
>  from django.utils import timezone
> diff --git
> a/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py
> b/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py
> index 1c627ad..e2623e8 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py
> +++
> b/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py
> @@ -1,23 +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et -#
> -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3 #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.core.urlresolvers import reverse
>  from django.utils import timezone
> diff --git
> a/bitbake/lib/toaster/tests/browser/test_builddashboard_page_recipes.py
> b/bitbake/lib/toaster/tests/browser/test_builddashboard_page_recipes.py
> index ed18324..c542d45 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_builddashboard_page_recipes.py
> +++
> b/bitbake/lib/toaster/tests/browser/test_builddashboard_page_recipes.py
> @@ -1,23 +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et -#
> -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3 #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.core.urlresolvers import reverse
>  from django.utils import timezone
> diff --git
> a/bitbake/lib/toaster/tests/browser/test_builddashboard_page_tasks.py
> b/bitbake/lib/toaster/tests/browser/test_builddashboard_page_tasks.py
> index da50f16..22acb47 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_builddashboard_page_tasks.py
> +++
> b/bitbake/lib/toaster/tests/browser/test_builddashboard_page_tasks.py
> @@ -1,23 +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et -#
> -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3 #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.core.urlresolvers import reverse
>  from django.utils import timezone
> diff --git a/bitbake/lib/toaster/tests/browser/test_js_unit_tests.py
> b/bitbake/lib/toaster/tests/browser/test_js_unit_tests.py index
> 3c0b962..e8b4295 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_js_unit_tests.py +++
> b/bitbake/lib/toaster/tests/browser/test_js_unit_tests.py @@ -1,23
> +1,11 @@ -#! /usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  """
>  Run the js unit tests
> diff --git a/bitbake/lib/toaster/tests/browser/test_landing_page.py
> b/bitbake/lib/toaster/tests/browser/test_landing_page.py index
> 4d4cd66..0790198 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_landing_page.py +++
> b/bitbake/lib/toaster/tests/browser/test_landing_page.py @@ -1,23
> +1,11 @@ -#! /usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
> -# Copyright (C) 2013-2016 Intel Corporation
> -#
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# Copyright (C) 2013-2016 Intel Corporation
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.core.urlresolvers import reverse
>  from django.utils import timezone
> diff --git
> a/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
> b/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py index
> f24fb09..f81e696 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py +++
> b/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py @@
> -1,23 +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
> -# Copyright (C) 2013-2016 Intel Corporation
> -#
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# Copyright (C) 2013-2016 Intel Corporation
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.core.urlresolvers import reverse
>  from tests.browser.selenium_helpers import SeleniumTestCase
> diff --git
> a/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py
> b/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py
> index abc0b0b..15d25dc 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py
> +++
> b/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py
> @@ -1,23 +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et -#
> -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3 #
>  # BitBake Toaster Implementation
>  #
> -# Copyright (C) 2013-2016 Intel Corporation
> -#
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# Copyright (C) 2013-2016 Intel Corporation
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.core.urlresolvers import reverse
>  from django.utils import timezone
> diff --git
> a/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py
> b/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py
> index ab5a8e6..0aa3b7a 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py +++
> b/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py @@
> -1,23 +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.core.urlresolvers import reverse
>  from tests.browser.selenium_helpers import SeleniumTestCase
> diff --git
> a/bitbake/lib/toaster/tests/browser/test_new_project_page.py
> b/bitbake/lib/toaster/tests/browser/test_new_project_page.py index
> 77e5f15..8e56bb0 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_new_project_page.py +++
> b/bitbake/lib/toaster/tests/browser/test_new_project_page.py @@ -1,23
> +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.core.urlresolvers import reverse
>  from tests.browser.selenium_helpers import SeleniumTestCase
> diff --git
> a/bitbake/lib/toaster/tests/browser/test_project_builds_page.py
> b/bitbake/lib/toaster/tests/browser/test_project_builds_page.py index
> 9fe91ab..47fb10b 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_project_builds_page.py +++
> b/bitbake/lib/toaster/tests/browser/test_project_builds_page.py @@
> -1,23 +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import re
>  
> diff --git
> a/bitbake/lib/toaster/tests/browser/test_project_config_page.py
> b/bitbake/lib/toaster/tests/browser/test_project_config_page.py index
> 0710084..2816eb9 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_project_config_page.py +++
> b/bitbake/lib/toaster/tests/browser/test_project_config_page.py @@
> -1,23 +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import re
>  
> diff --git a/bitbake/lib/toaster/tests/browser/test_project_page.py
> b/bitbake/lib/toaster/tests/browser/test_project_page.py index
> 0186463..8b5e1b6 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_project_page.py +++
> b/bitbake/lib/toaster/tests/browser/test_project_page.py @@ -1,23
> +1,11 @@ -#! /usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.core.urlresolvers import reverse
>  from django.utils import timezone
> diff --git a/bitbake/lib/toaster/tests/browser/test_sample.py
> b/bitbake/lib/toaster/tests/browser/test_sample.py index
> 20ec53c..f4ad670 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_sample.py +++
> b/bitbake/lib/toaster/tests/browser/test_sample.py @@ -1,23 +1,11 @@
> -#! /usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  """
>  A small example test demonstrating the basics of writing a test with
> diff --git a/bitbake/lib/toaster/tests/browser/test_task_page.py
> b/bitbake/lib/toaster/tests/browser/test_task_page.py index
> 690d116..26f3dca 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_task_page.py +++
> b/bitbake/lib/toaster/tests/browser/test_task_page.py @@ -1,23 +1,11
> @@ -#! /usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.core.urlresolvers import reverse
>  from django.utils import timezone
> diff --git
> a/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py
> b/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py index
> 53ddf30..ef78cbb 100644 ---
> a/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py +++
> b/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py @@ -1,23
> +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from datetime import datetime
>  
> diff --git a/bitbake/lib/toaster/tests/builds/buildtest.py
> b/bitbake/lib/toaster/tests/builds/buildtest.py index
> 5a56a11..872bbd3 100644 ---
> a/bitbake/lib/toaster/tests/builds/buildtest.py +++
> b/bitbake/lib/toaster/tests/builds/buildtest.py @@ -1,23 +1,11 @@
> -#! /usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  import sys
> diff --git a/bitbake/lib/toaster/tests/builds/test_core_image_min.py
> b/bitbake/lib/toaster/tests/builds/test_core_image_min.py index
> 586f4a8..44b6cbe 100644 ---
> a/bitbake/lib/toaster/tests/builds/test_core_image_min.py +++
> b/bitbake/lib/toaster/tests/builds/test_core_image_min.py @@ -1,24
> +1,11 @@ -#! /usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -
>  # Tests were part of openembedded-core oe selftest Authored by:
> Lucian Musat # Ionut Chisanovici, Paul Eggleton and Cristian Iorga
>  
> diff --git a/bitbake/lib/toaster/tests/commands/test_loaddata.py
> b/bitbake/lib/toaster/tests/commands/test_loaddata.py index
> 951f6ff..9e8d555 100644 ---
> a/bitbake/lib/toaster/tests/commands/test_loaddata.py +++
> b/bitbake/lib/toaster/tests/commands/test_loaddata.py @@ -1,23 +1,11
> @@ -#! /usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.test import TestCase
>  from django.core import management
> diff --git a/bitbake/lib/toaster/tests/commands/test_lsupdates.py
> b/bitbake/lib/toaster/tests/commands/test_lsupdates.py index
> 49897a4..3c4fbe0 100644 ---
> a/bitbake/lib/toaster/tests/commands/test_lsupdates.py +++
> b/bitbake/lib/toaster/tests/commands/test_lsupdates.py @@ -1,23 +1,11
> @@ -#! /usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.test import TestCase
>  from django.core import management
> diff --git a/bitbake/lib/toaster/tests/commands/test_runbuilds.py
> b/bitbake/lib/toaster/tests/commands/test_runbuilds.py index
> 3e63483..e223b95 100644 ---
> a/bitbake/lib/toaster/tests/commands/test_runbuilds.py +++
> b/bitbake/lib/toaster/tests/commands/test_runbuilds.py @@ -1,23 +1,11
> @@ -#! /usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  
> diff --git a/bitbake/lib/toaster/tests/db/test_db.py
> b/bitbake/lib/toaster/tests/db/test_db.py index a0f5f6e..0410422
> 100644 --- a/bitbake/lib/toaster/tests/db/test_db.py
> +++ b/bitbake/lib/toaster/tests/db/test_db.py
> @@ -2,6 +2,8 @@
>  #
>  # Copyright (c) 2016 Damien Lespiau
>  #
> +# SPDX-License-Identifier: MIT
> +#
>  # Permission is hereby granted, free of charge, to any person
> obtaining a copy # of this software and associated documentation
> files (the "Software"), to deal # in the Software without
> restriction, including without limitation the rights diff --git
> a/bitbake/lib/toaster/tests/eventreplay/__init__.py
> b/bitbake/lib/toaster/tests/eventreplay/__init__.py index
> 6956619..8ed6792 100644 ---
> a/bitbake/lib/toaster/tests/eventreplay/__init__.py +++
> b/bitbake/lib/toaster/tests/eventreplay/__init__.py @@ -1,23 +1,11 @@
> -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  # Tests were part of openembedded-core oe selftest Authored by:
> Lucian Musat # Ionut Chisanovici, Paul Eggleton and Cristian Iorga
> diff --git
> a/bitbake/lib/toaster/tests/functional/functional_helpers.py
> b/bitbake/lib/toaster/tests/functional/functional_helpers.py index
> 486078a..455c408 100644 ---
> a/bitbake/lib/toaster/tests/functional/functional_helpers.py +++
> b/bitbake/lib/toaster/tests/functional/functional_helpers.py @@ -1,23
> +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster functional tests implementation
>  #
>  # Copyright (C) 2017 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import os
>  import logging
> diff --git
> a/bitbake/lib/toaster/tests/functional/test_functional_basic.py
> b/bitbake/lib/toaster/tests/functional/test_functional_basic.py index
> cfa2b0f..56c84fb 100644 ---
> a/bitbake/lib/toaster/tests/functional/test_functional_basic.py +++
> b/bitbake/lib/toaster/tests/functional/test_functional_basic.py @@
> -1,23 +1,11 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster functional tests implementation
>  #
>  # Copyright (C) 2017 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import time
>  import re
> diff --git a/bitbake/lib/toaster/tests/views/test_views.py
> b/bitbake/lib/toaster/tests/views/test_views.py index
> 1463077..68d9e9d 100644 ---
> a/bitbake/lib/toaster/tests/views/test_views.py +++
> b/bitbake/lib/toaster/tests/views/test_views.py @@ -1,23 +1,11 @@
> -#! /usr/bin/env python
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#! /usr/bin/env python3
>  #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013-2015 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  """Test cases for Toaster GUI and ReST."""
>  
> diff --git a/bitbake/lib/toaster/toastergui/api.py
> b/bitbake/lib/toaster/toastergui/api.py index 564d595..8b49b3e 100644
> --- a/bitbake/lib/toaster/toastergui/api.py
> +++ b/bitbake/lib/toaster/toastergui/api.py
> @@ -3,19 +3,8 @@
>  #
>  # Copyright (C) 2016        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -
>  # Please run flake8 on this file before sending patches
>  
>  import os
> diff --git a/bitbake/lib/toaster/toastergui/buildtables.py
> b/bitbake/lib/toaster/toastergui/buildtables.py index
> 755a7c2..327059d 100644 ---
> a/bitbake/lib/toaster/toastergui/buildtables.py +++
> b/bitbake/lib/toaster/toastergui/buildtables.py @@ -1,23 +1,10 @@
>  #
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2016 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from orm.models import Build, Task, Target, Package
>  from django.db.models import Q, Sum
> diff --git a/bitbake/lib/toaster/toastergui/static/js/importlayer.js
> b/bitbake/lib/toaster/toastergui/static/js/importlayer.js index
> 2964839..8e2032d 100644 ---
> a/bitbake/lib/toaster/toastergui/static/js/importlayer.js +++
> b/bitbake/lib/toaster/toastergui/static/js/importlayer.js @@ -17,11
> +17,15 @@ function importLayerPageInit (ctx) { var
> currentLayerDepSelection; var validLayerName = /^(\w|-)+$/;
>  
> +  /* Catch 'disable' race condition between type-ahead started and
> "input change" */
> +  var typeAheadStarted = 0;
> +
>    libtoaster.makeTypeahead(layerDepInput,
>                             libtoaster.ctx.layersTypeAheadUrl,
>                             { include_added: "true" }, function(item){
>      currentLayerDepSelection = item;
>      layerDepBtn.removeAttr("disabled");
> +    typeAheadStarted = 1;
>    });
>  
>    layerDepInput.on("typeahead:select", function(event, data){
> @@ -34,7 +38,10 @@ function importLayerPageInit (ctx) {
>    // disable the "Add layer" button when the layer input typeahead
> is empty // or not in the typeahead choices
>    layerDepInput.on("input change", function(){
> -    layerDepBtn.attr("disabled","disabled");
> +    if (0 == typeAheadStarted) {
> +      layerDepBtn.attr("disabled","disabled");
> +    }
> +    typeAheadStarted = 0;
>    });
>  
>    /* We automatically add "openembedded-core" layer for convenience
> as a @@ -50,6 +57,7 @@ function importLayerPageInit (ctx) {
>    });
>  
>    layerDepBtn.click(function(){
> +    typeAheadStarted = 0;
>      if (currentLayerDepSelection == undefined)
>        return;
>  
> @@ -77,7 +85,7 @@ function importLayerPageInit (ctx) {
>  
>      $("#layer-deps-list").append(newLayerDep);
>  
> -
> libtoaster.getLayerDepsForProject(currentLayerDepSelection.layerdetailurl,
> +
> libtoaster.getLayerDepsForProject(currentLayerDepSelection.xhrLayerUrl,
> function (data){ /* These are the dependencies of the layer added as
> a dependency */ if (data.list.length > 0) {
> diff --git a/bitbake/lib/toaster/toastergui/tablefilter.py
> b/bitbake/lib/toaster/toastergui/tablefilter.py index
> 65454e1..ffef795 100644 ---
> a/bitbake/lib/toaster/toastergui/tablefilter.py +++
> b/bitbake/lib/toaster/toastergui/tablefilter.py @@ -1,23 +1,10 @@
>  #
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2015        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.db.models import Q, Max, Min
>  from django.utils import dateparse, timezone
> diff --git a/bitbake/lib/toaster/toastergui/tables.py
> b/bitbake/lib/toaster/toastergui/tables.py index 9ff756b..b3ea222
> 100644 --- a/bitbake/lib/toaster/toastergui/tables.py
> +++ b/bitbake/lib/toaster/toastergui/tables.py
> @@ -1,23 +1,10 @@
>  #
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2015        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from toastergui.widgets import ToasterTable
>  from orm.models import Recipe, ProjectLayer, Layer_Version, Machine,
> Project diff --git
> a/bitbake/lib/toaster/toastergui/templatetags/field_values_filter.py
> b/bitbake/lib/toaster/toastergui/templatetags/field_values_filter.py
> index 5a73af7..eb48339 100644 ---
> a/bitbake/lib/toaster/toastergui/templatetags/field_values_filter.py
> +++
> b/bitbake/lib/toaster/toastergui/templatetags/field_values_filter.py
> @@ -1,3 +1,7 @@ +# +# SPDX-License-Identifier: GPL-2.0-only +#
> +
>  from django import template
>  
>  register = template.Library()
> diff --git
> a/bitbake/lib/toaster/toastergui/templatetags/objects_to_dictionaries_filter.py
> b/bitbake/lib/toaster/toastergui/templatetags/objects_to_dictionaries_filter.py
> index 0dcc7d2..048d533 100644 ---
> a/bitbake/lib/toaster/toastergui/templatetags/objects_to_dictionaries_filter.py
> +++
> b/bitbake/lib/toaster/toastergui/templatetags/objects_to_dictionaries_filter.py
> @@ -1,3 +1,7 @@ +# +# SPDX-License-Identifier: GPL-2.0-only +#
> +
>  from django import template
>  import json
>  
> diff --git
> a/bitbake/lib/toaster/toastergui/templatetags/project_url_tag.py
> b/bitbake/lib/toaster/toastergui/templatetags/project_url_tag.py
> index 04770ac..71e0925 100644 ---
> a/bitbake/lib/toaster/toastergui/templatetags/project_url_tag.py +++
> b/bitbake/lib/toaster/toastergui/templatetags/project_url_tag.py @@
> -1,3 +1,7 @@ +# +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  from django import template
>  from django.core.urlresolvers import reverse
>  
> diff --git
> a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py
> b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py index
> b170a16..1dbab3b 100644 ---
> a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py +++
> b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py @@ -1,23
> +1,10 @@ # -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from datetime import datetime, timedelta
>  from os.path import relpath
> diff --git a/bitbake/lib/toaster/toastergui/typeaheads.py
> b/bitbake/lib/toaster/toastergui/typeaheads.py index 5aa0f8d..fd750ff
> 100644 --- a/bitbake/lib/toaster/toastergui/typeaheads.py
> +++ b/bitbake/lib/toaster/toastergui/typeaheads.py
> @@ -3,18 +3,8 @@
>  #
>  # Copyright (C) 2015        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  import subprocess
>  
> diff --git a/bitbake/lib/toaster/toastergui/urls.py
> b/bitbake/lib/toaster/toastergui/urls.py index dc03e30..673d9ae 100644
> --- a/bitbake/lib/toaster/toastergui/urls.py
> +++ b/bitbake/lib/toaster/toastergui/urls.py
> @@ -3,18 +3,8 @@
>  #
>  # Copyright (C) 2013-2017    Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.conf.urls import include, url
>  from django.views.generic import RedirectView, TemplateView
> diff --git a/bitbake/lib/toaster/toastergui/views.py
> b/bitbake/lib/toaster/toastergui/views.py index c712b06..d7acaff
> 100644 --- a/bitbake/lib/toaster/toastergui/views.py
> +++ b/bitbake/lib/toaster/toastergui/views.py
> @@ -1,24 +1,10 @@
>  #
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. -
>  
>  import re
>  
> diff --git a/bitbake/lib/toaster/toastergui/widgets.py
> b/bitbake/lib/toaster/toastergui/widgets.py index db5c3aa..645f458
> 100644 --- a/bitbake/lib/toaster/toastergui/widgets.py
> +++ b/bitbake/lib/toaster/toastergui/widgets.py
> @@ -1,23 +1,10 @@
>  #
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2015        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.views.generic import View, TemplateView
>  from django.views.decorators.cache import cache_control
> diff --git
> a/bitbake/lib/toaster/toastermain/management/commands/builddelete.py
> b/bitbake/lib/toaster/toastermain/management/commands/builddelete.py
> index bf69a8f..c2d773a 100644 ---
> a/bitbake/lib/toaster/toastermain/management/commands/builddelete.py
> +++
> b/bitbake/lib/toaster/toastermain/management/commands/builddelete.py
> @@ -1,3 +1,7 @@ +# +# SPDX-License-Identifier: GPL-2.0-only +#
> +
>  from django.core.management.base import BaseCommand, CommandError
>  from django.core.exceptions import ObjectDoesNotExist
>  from orm.models import Build
> diff --git
> a/bitbake/lib/toaster/toastermain/management/commands/buildimport.py
> b/bitbake/lib/toaster/toastermain/management/commands/buildimport.py
> index 2d57ab5..408ad44 100644 ---
> a/bitbake/lib/toaster/toastermain/management/commands/buildimport.py
> +++
> b/bitbake/lib/toaster/toastermain/management/commands/buildimport.py
> @@ -1,23 +1,10 @@ # -# ex:ts=4:sw=4:sts=4:et -# -*- tab-width: 4;
> c-basic-offset: 4; indent-tabs-mode: nil -*- -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2018        Wind River Systems
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  # buildimport: import a project for project specific configuration
>  #
> diff --git
> a/bitbake/lib/toaster/toastermain/management/commands/buildslist.py
> b/bitbake/lib/toaster/toastermain/management/commands/buildslist.py
> index 70b5812..1ed2022 100644 ---
> a/bitbake/lib/toaster/toastermain/management/commands/buildslist.py
> +++
> b/bitbake/lib/toaster/toastermain/management/commands/buildslist.py
> @@ -1,3 +1,7 @@ +# +# SPDX-License-Identifier: GPL-2.0-only +#
> +
>  from django.core.management.base import BaseCommand, CommandError
>  from orm.models import Build
>  import os
> diff --git
> a/bitbake/lib/toaster/toastermain/management/commands/checksocket.py
> b/bitbake/lib/toaster/toastermain/management/commands/checksocket.py
> index 0399b86..811fd5d 100644 ---
> a/bitbake/lib/toaster/toastermain/management/commands/checksocket.py
> +++
> b/bitbake/lib/toaster/toastermain/management/commands/checksocket.py
> @@ -1,23 +1,11 @@ -#!/usr/bin/env python -# ex:ts=4:sw=4:sts=4:et -#
> -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> +#!/usr/bin/env python3 #
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2015 Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  """Custom management command checksocket."""
>  
> diff --git
> a/bitbake/lib/toaster/toastermain/management/commands/perf.py
> b/bitbake/lib/toaster/toastermain/management/commands/perf.py index
> 6b450bb..7d629fb 100644 ---
> a/bitbake/lib/toaster/toastermain/management/commands/perf.py +++
> b/bitbake/lib/toaster/toastermain/management/commands/perf.py @@ -1,3
> +1,7 @@ +# +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
>  from django.core.management.base import BaseCommand
>  from django.test.client import Client
>  import os, sys, re
> diff --git a/bitbake/lib/toaster/toastermain/settings.py
> b/bitbake/lib/toaster/toastermain/settings.py index 13541d3..74501fa
> 100644 --- a/bitbake/lib/toaster/toastermain/settings.py
> +++ b/bitbake/lib/toaster/toastermain/settings.py
> @@ -1,23 +1,10 @@
>  #
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  # Django settings for Toaster project.
>  
> diff --git
> a/bitbake/lib/toaster/toastermain/settings_production_example.py
> b/bitbake/lib/toaster/toastermain/settings_production_example.py
> index 61a2888..6cd0f52 100644 ---
> a/bitbake/lib/toaster/toastermain/settings_production_example.py +++
> b/bitbake/lib/toaster/toastermain/settings_production_example.py @@
> -1,23 +1,10 @@ # -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2016        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  # See Django documentation for more information about deployment
>  # https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
> diff --git a/bitbake/lib/toaster/toastermain/settings_test.py
> b/bitbake/lib/toaster/toastermain/settings_test.py index
> a322711..6538d9e 100644 ---
> a/bitbake/lib/toaster/toastermain/settings_test.py +++
> b/bitbake/lib/toaster/toastermain/settings_test.py @@ -1,23 +1,10 @@
>  #
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2016        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  # Django settings for Toaster project.
>  
> diff --git a/bitbake/lib/toaster/toastermain/urls.py
> b/bitbake/lib/toaster/toastermain/urls.py index e2fb0ae..ac77bc3
> 100644 --- a/bitbake/lib/toaster/toastermain/urls.py
> +++ b/bitbake/lib/toaster/toastermain/urls.py
> @@ -1,23 +1,10 @@
>  #
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
> -#
>  # BitBake Toaster Implementation
>  #
>  # Copyright (C) 2013        Intel Corporation
>  #
> -# This program is free software; you can redistribute it and/or
> modify -# it under the terms of the GNU General Public License
> version 2 as -# published by the Free Software Foundation.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> +# SPDX-License-Identifier: GPL-2.0-only
>  #
> -# You should have received a copy of the GNU General Public License
> along -# with this program; if not, write to the Free Software
> Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA. 
>  from django.conf.urls import include, url
>  from django.views.generic import RedirectView, TemplateView
> diff --git a/bitbake/lib/toaster/toastermain/wsgi.py
> b/bitbake/lib/toaster/toastermain/wsgi.py index 031b314..4c31283
> 100644 --- a/bitbake/lib/toaster/toastermain/wsgi.py
> +++ b/bitbake/lib/toaster/toastermain/wsgi.py
> @@ -1,7 +1,8 @@
> -"""
> -# ex:ts=4:sw=4:sts=4:et
> -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
>  #
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
> +"""
>  WSGI config for Toaster project.
>  
>  This module contains the WSGI application used by Django's
> development server diff --git a/doc/user_manual.md
> b/doc/user_manual.md index c2657da..96aa2e3 100644
> --- a/doc/user_manual.md
> +++ b/doc/user_manual.md
> @@ -135,8 +135,8 @@ DISTRO_ARCH ??= "armhf"
>  Then, call `bitbake` with image names, e.g.:
>  
>  ```
> -bitbake multiconfig:qemuarm-buster:isar-image-base \
> -        multiconfig:qemuarm-buster:isar-image-debug
> +bitbake mc:qemuarm-buster:isar-image-base \
> +        mc:qemuarm-buster:isar-image-debug
>  ```
>  
>  The following images are created:
> @@ -169,14 +169,14 @@ The following command will produce
> `isar-image-base` images for all targets: 
>  ```
>  $ bitbake \
> -    multiconfig:qemuarm-stretch:isar-image-base \
> -    multiconfig:qemuarm-buster:isar-image-base \
> -    multiconfig:qemuarm64-stretch:isar-image-base \
> -    multiconfig:qemui386-stretch:isar-image-base \
> -    multiconfig:qemui386-buster:isar-image-base \
> -    multiconfig:qemuamd64-stretch:isar-image-base \
> -    multiconfig:qemuamd64-buster:isar-image-base \
> -    multiconfig:rpi-stretch:isar-image-base
> +    mc:qemuarm-stretch:isar-image-base \
> +    mc:qemuarm-buster:isar-image-base \
> +    mc:qemuarm64-stretch:isar-image-base \
> +    mc:qemui386-stretch:isar-image-base \
> +    mc:qemui386-buster:isar-image-base \
> +    mc:qemuamd64-stretch:isar-image-base \
> +    mc:qemuamd64-buster:isar-image-base \
> +    mc:rpi-stretch:isar-image-base
>  ```
>  
>  Created images are:
> @@ -197,9 +197,9 @@ tmp/deploy/images/rpi/isar-image-base.rpi-sdimg
>  A bootable disk image is generated if you set IMAGE_TYPE to
> 'wic-img'. Behind the scenes a tool called `wic` is used to assemble
> the images. It is controlled by a `.wks` file which you can choose
> with changing WKS_FILE. Some examples in the tree use that feature
> already. ``` # Generate an image for the `i386` target architecture
> - $ bitbake multiconfig:qemui386-buster:isar-image-base
> + $ bitbake mc:qemui386-buster:isar-image-base
>   # Similarly, for the `amd64` target architecture, in this case EFI
> - $ bitbake multiconfig:qemuamd64-buster:isar-image-base
> + $ bitbake mc:qemuamd64-buster:isar-image-base
>  ```
>  
>  Variables may be used in `.wks.in` files; Isar will expand them and
> generate a regular `.wks` file before generating the disk image using
> `wic`. @@ -303,7 +303,7 @@ following variables define the default
> configuration to build for:
>   - `DISTRO_ARCH` - The Debian architecture to build for (e.g.,
> `armhf`). 
>  If BitBake is called with multiconfig targets (e.g.,
> -`multiconfig:qemuarm-buster:isar-image-base`), the following
> variable defines +`mc:qemuarm-buster:isar-image-base`), the following
> variable defines all supported configurations:
>  
>   - `BBMULTICONFIG` - The list of the complete configuration
> definition files. @@ -687,7 +687,7 @@ Debian cross-compilation works
> out of the box starting from Debian stretch distr Just like
> OpenEmbedded, Isar supports a devshell target for all dpkg package
> recipes. This target opens a terminal inside the buildchroot that
> runs the package build. To invoke it, just call -`bitbake
> multiconfig:${MACHINE}-${DISTRO}:<package_name> -c devshell`.
> +`bitbake mc:${MACHINE}-${DISTRO}:<package_name> -c devshell`. 
>  
>  ## Create an ISAR SDK root filesystem
> @@ -705,7 +705,7 @@ target binary artifacts. Developer chroots to sdk
> rootfs and develops applicatio ### Solution
>  
>  User manually triggers creation of SDK root filesystem for his
> target platform by launching the task `do_populate_sdk` for target
> image, f.e. -`bitbake -c do_populate_sdk
> multiconfig:${MACHINE}-${DISTRO}:isar-image-base`. +`bitbake -c
> do_populate_sdk mc:${MACHINE}-${DISTRO}:isar-image-base`. The
> resulting SDK rootfs is archived into
> `tmp/deploy/images/${MACHINE}/sdk-${DISTRO}-${DISTRO_ARCH}.tar.xz`.
> It is additionally available for direct use under
> `tmp/deploy/images/${MACHINE}/sdk-${DISTRO}-${DISTRO_ARCH}/`. @@
> -717,7 +717,7 @@ One may chroot into the SDK and install required
> target packages with the help o
>   - Trigger creation of SDK root filesystem
>  
>  ```
> -bitbake -c do_populate_sdk multiconfig:qemuarm-buster:isar-image-base
> +bitbake -c do_populate_sdk mc:qemuarm-buster:isar-image-base
>  ```
>  
>   - Mount the following directories in chroot by passing resulting
> rootfs as an argument to the script `mount_chroot.sh`: @@ -801,7
> +801,7 @@ BASE_REPO_KEY =
> "file://<absolute_path_to_your_pub_key_file>"'
>   - Trigger creation of local apt caching Debian packages during
> image generation. 
>  ```
> -bitbake -c cache_base_repo multiconfig:qemuarm-buster:isar-image-base
> +bitbake -c cache_base_repo mc:qemuarm-buster:isar-image-base
>  ```
>  
>   - Set `ISAR_USE_CACHED_BASE_REPO` in `conf/local.conf`:
> @@ -820,7 +820,7 @@ sudo rm -rf tmp
>   - Trigger again generation of image (now using local caching repo):
>  
>  ```
> -bitbake multiconfig:qemuarm-buster:isar-image-base
> +bitbake mc:qemuarm-buster:isar-image-base
>  ```
>  
>  ### Limitation
> @@ -860,5 +860,5 @@ DISTRO_APT_SOURCES_append = "
> conf/distro/docker-buster.list" And build the corresponding image
> target: 
>  ```
> -bitbake multiconfig:qemuarm64-buster:isar-image-base
> +bitbake mc:qemuarm64-buster:isar-image-base
>  ```
> diff --git a/meta-isar/conf/conf-notes.txt
> b/meta-isar/conf/conf-notes.txt index 74df97c..c557cd3 100644
> --- a/meta-isar/conf/conf-notes.txt
> +++ b/meta-isar/conf/conf-notes.txt
> @@ -1,4 +1,4 @@
>  Common targets are:
> -    multiconfig:qemuarm-buster:isar-image-base
> -    multiconfig:qemuamd64-buster:isar-image-base
> -    multiconfig:rpi-stretch:isar-image-base
> +    mc:qemuarm-buster:isar-image-base
> +    mc:qemuamd64-buster:isar-image-base
> +    mc:rpi-stretch:isar-image-base
> diff --git a/scripts/ci_build.sh b/scripts/ci_build.sh
> index 713d1c2..a8556a6 100755
> --- a/scripts/ci_build.sh
> +++ b/scripts/ci_build.sh
> @@ -21,40 +21,40 @@ BUILD_DIR=./build
>  BB_ARGS="-v"
>  
>  TARGETS_SET="\
> -            multiconfig:qemuarm-stretch:isar-image-base \
> -            multiconfig:qemuarm-buster:isar-image-base \
> -            multiconfig:qemuarm-bullseye:isar-image-base \
> -            multiconfig:qemuarm64-stretch:isar-image-base \
> -            multiconfig:qemui386-stretch:isar-image-base \
> -            multiconfig:qemui386-buster:isar-image-base \
> -            multiconfig:qemui386-bullseye:isar-image-base \
> -            multiconfig:qemuamd64-stretch:isar-image-base \
> -            multiconfig:qemuamd64-buster:isar-image-base \
> -            multiconfig:qemuamd64-buster-tgz:isar-image-base \
> -            multiconfig:qemuamd64-bullseye:isar-image-base \
> -            multiconfig:qemumipsel-stretch:isar-image-base \
> -            multiconfig:qemumipsel-buster:isar-image-base \
> -            multiconfig:qemumipsel-bullseye:isar-image-base \
> -            multiconfig:nand-ubi-demo-buster:isar-image-ubi \
> -            multiconfig:rpi-stretch:isar-image-base"
> +            mc:qemuarm-stretch:isar-image-base \
> +            mc:qemuarm-buster:isar-image-base \
> +            mc:qemuarm-bullseye:isar-image-base \
> +            mc:qemuarm64-stretch:isar-image-base \
> +            mc:qemui386-stretch:isar-image-base \
> +            mc:qemui386-buster:isar-image-base \
> +            mc:qemui386-bullseye:isar-image-base \
> +            mc:qemuamd64-stretch:isar-image-base \
> +            mc:qemuamd64-buster:isar-image-base \
> +            mc:qemuamd64-buster-tgz:isar-image-base \
> +            mc:qemuamd64-bullseye:isar-image-base \
> +            mc:qemumipsel-stretch:isar-image-base \
> +            mc:qemumipsel-buster:isar-image-base \
> +            mc:qemumipsel-bullseye:isar-image-base \
> +            mc:nand-ubi-demo-buster:isar-image-ubi \
> +            mc:rpi-stretch:isar-image-base"
>            # qemu-user-static of <= buster too old to build that
> -          # multiconfig:qemuarm64-buster:isar-image-base
> -          # multiconfig:qemuarm64-bullseye:isar-image-base
> +          # mc:qemuarm64-buster:isar-image-base
> +          # mc:qemuarm64-bullseye:isar-image-base
>  
>  CROSS_TARGETS_SET="\
> -                  multiconfig:qemuarm-stretch:isar-image-base \
> -                  multiconfig:qemuarm-buster:isar-image-base \
> -                  multiconfig:qemuarm-bullseye:isar-image-base \
> -                  multiconfig:qemuarm64-stretch:isar-image-base \
> -                  multiconfig:qemuamd64-stretch:isar-image-base \
> -                  multiconfig:de0-nano-soc-stretch:isar-image-base \
> -                  multiconfig:rpi-stretch:isar-image-base"
> +                  mc:qemuarm-stretch:isar-image-base \
> +                  mc:qemuarm-buster:isar-image-base \
> +                  mc:qemuarm-bullseye:isar-image-base \
> +                  mc:qemuarm64-stretch:isar-image-base \
> +                  mc:qemuamd64-stretch:isar-image-base \
> +                  mc:de0-nano-soc-stretch:isar-image-base \
> +                  mc:rpi-stretch:isar-image-base"
>  
>  REPRO_TARGETS_SET="\
> -            multiconfig:qemuarm-stretch:isar-image-base \
> -            multiconfig:qemuarm64-stretch:isar-image-base \
> -            multiconfig:qemuamd64-stretch:isar-image-base \
> -            multiconfig:qemuarm-buster:isar-image-base"
> +            mc:qemuarm-stretch:isar-image-base \
> +            mc:qemuarm64-stretch:isar-image-base \
> +            mc:qemuamd64-stretch:isar-image-base \
> +            mc:qemuarm-buster:isar-image-base"
>  
>  
>  show_help() {
> @@ -162,7 +162,7 @@ sed -i -e 's/ISAR_CROSS_COMPILE ?=
> "0"/ISAR_CROSS_COMPILE ?= "1"/g' conf/local.c bitbake $BB_ARGS
> $CROSS_TARGETS_SET while [ -e bitbake.sock ]; do sleep 1; done
>  # In addition test SDK creation
> -bitbake $BB_ARGS -c do_populate_sdk
> multiconfig:qemuarm-stretch:isar-image-base +bitbake $BB_ARGS -c
> do_populate_sdk mc:qemuarm-stretch:isar-image-base while [ -e
> bitbake.sock ]; do sleep 1; done 
>  if [ -z "$FAST_BUILD" ]; then
> @@ -175,6 +175,6 @@ fi
>  cp -a "${ISARROOT}/meta/classes/dpkg-base.bbclass"
> "${ISARROOT}/meta/classes/dpkg-base.bbclass.ci-backup" echo -e
> "do_fetch_append() {\n\n}" >>
> "${ISARROOT}/meta/classes/dpkg-base.bbclass" -bitbake $BB_ARGS
> multiconfig:qemuamd64-stretch:isar-image-base +bitbake $BB_ARGS
> mc:qemuamd64-stretch:isar-image-base 
>  mv "${ISARROOT}/meta/classes/dpkg-base.bbclass.ci-backup"
> "${ISARROOT}/meta/classes/dpkg-base.bbclass" diff --git
> a/scripts/start_vm b/scripts/start_vm index 9f07a03..71b55f2 100755
> --- a/scripts/start_vm
> +++ b/scripts/start_vm
> @@ -101,7 +101,7 @@ do
>      shift
>  done
>  
> -eval $(bitbake -e multiconfig:qemu$ARCH-$DISTRO:isar-image-base |
> grep "^DEPLOY_DIR_IMAGE=") +eval $(bitbake -e
> mc:qemu$ARCH-$DISTRO:isar-image-base | grep "^DEPLOY_DIR_IMAGE=")
> readonly IMAGE_DIR=$DEPLOY_DIR_IMAGE 
>  readonly ISARROOT="$(dirname "$0")"/..
> @@ -112,13 +112,13 @@ eval "$(egrep 'MACHINE_SERIAL' $MACHINE_CONF
> |bb2sh)" readonly
> CONFIG_CONF=$ISARROOT/meta-isar/conf/multiconfig/qemu$ARCH-$DISTRO.conf
> eval "$(egrep 'QEMU_' $CONFIG_CONF |bb2sh)" 
> -eval $(bitbake -e multiconfig:qemu$ARCH-$DISTRO:isar-image-base |
> grep "^IMAGE_TYPE=") +eval $(bitbake -e
> mc:qemu$ARCH-$DISTRO:isar-image-base | grep "^IMAGE_TYPE=") case
> "$IMAGE_TYPE" in ext4-img)
>      readonly
> ROOTFS_IMAGE=isar-image-base-debian-$DISTRO-qemu$ARCH.ext4.img 
> -    eval $(bitbake -e multiconfig:qemu$ARCH-$DISTRO:isar-image-base
> | grep "^KERNEL_IMAGE=")
> -    eval $(bitbake -e multiconfig:qemu$ARCH-$DISTRO:isar-image-base
> | grep "^INITRD_IMAGE=")
> +    eval $(bitbake -e mc:qemu$ARCH-$DISTRO:isar-image-base | grep
> "^KERNEL_IMAGE=")
> +    eval $(bitbake -e mc:qemu$ARCH-$DISTRO:isar-image-base | grep
> "^INITRD_IMAGE=") QKERNEL=$IMAGE_DIR/${KERNEL_IMAGE}
>      QINITRD=/dev/null
>      [ -n "$INITRD_IMAGE" ] && QINITRD=$IMAGE_DIR/${INITRD_IMAGE}
> diff --git a/testsuite/build_test/build_test.py
> b/testsuite/build_test/build_test.py index 4220d3a..7a55c2f 100644
> --- a/testsuite/build_test/build_test.py
> +++ b/testsuite/build_test/build_test.py
> @@ -22,7 +22,7 @@ class BuildTest(Test):
>  
>          #isar_root = dirname(__file__) + '/..'
>          os.chdir(build_dir)
> -        cmdline = ['bitbake', 'multiconfig:qemu' + arch + '-' +
> distro + ':isar-image-base']
> +        cmdline = ['bitbake', 'mc:qemu' + arch + '-' + distro +
> ':isar-image-base'] p1 = subprocess32.run(cmdline)
>  
>          if p1.returncode:
> diff --git a/testsuite/start_vm.py b/testsuite/start_vm.py
> index 02a4b51..a3e32ac 100755
> --- a/testsuite/start_vm.py
> +++ b/testsuite/start_vm.py
> @@ -10,7 +10,7 @@ import sys
>  import time
>  
>  def get_bitbake_env(arch, distro):
> -    multiconfig = 'multiconfig:qemu' + arch + '-' + distro +
> ':isar-image-base'
> +    multiconfig = 'mc:qemu' + arch + '-' + distro +
> ':isar-image-base' output = subprocess.check_output(['bitbake', '-e',
> str(multiconfig)]) return output
>  


  reply	other threads:[~2019-10-22  7:36 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-10-21 14:19 [PATCH 0/1] update bitbake " Cedric Hombourger
2019-10-21 14:19 ` [PATCH 1/1] bitbake: update " Cedric Hombourger
2019-10-22  7:36   ` Henning Schild [this message]
2019-10-22  7:41     ` Cedric Hombourger
2019-10-22 15:56       ` Baurzhan Ismagulov
2019-10-24  6:58         ` chombourger
2019-10-24 19:44           ` Baurzhan Ismagulov
2019-11-05 12:36             ` chombourger
2019-11-06  6:01               ` Baurzhan Ismagulov
2019-11-06  6:02                 ` Cedric Hombourger
2019-11-08  8:05 ` [PATCH 0/1] update bitbake " Jan Kiszka

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20191022093609.5ed343a5@md1za8fc.ad001.siemens.net \
    --to=henning.schild@siemens.com \
    --cc=Cedric_Hombourger@mentor.com \
    --cc=isar-users@googlegroups.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox