mirror of
https://git.yoctoproject.org/poky
synced 2026-09-12 15:49:36 +02:00
Compare commits
276 Commits
scarthgap-
...
yocto-3.2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5d6286a66 | ||
|
|
3bd4bf96cc | ||
|
|
41244407a0 | ||
|
|
f2139c483a | ||
|
|
6673eba881 | ||
|
|
c2feb63de8 | ||
|
|
a7ce6533ed | ||
|
|
95a02ba29c | ||
|
|
13ff17c145 | ||
|
|
1511e2f714 | ||
|
|
97a5bd47f9 | ||
|
|
20f18e4ff0 | ||
|
|
10470e6cc9 | ||
|
|
86c5922ebc | ||
|
|
a367523b55 | ||
|
|
6093edcc61 | ||
|
|
5d7728629a | ||
|
|
ac41e4a597 | ||
|
|
9df355c5f1 | ||
|
|
35be325c3b | ||
|
|
5842b2920c | ||
|
|
0003ed2b1c | ||
|
|
1d3cd3c2a5 | ||
|
|
49164d9cc1 | ||
|
|
2933296278 | ||
|
|
ecb66ea62e | ||
|
|
226c23ea54 | ||
|
|
f5287edae4 | ||
|
|
0e51506da3 | ||
|
|
110b6cb2ef | ||
|
|
09f368042f | ||
|
|
e9217245ee | ||
|
|
5b7bb2d5e0 | ||
|
|
67eb23b907 | ||
|
|
6a35ab437f | ||
|
|
68443cef53 | ||
|
|
2cf4a6a630 | ||
|
|
708f3ca9ac | ||
|
|
a72b80cf49 | ||
|
|
f1e1f86a8e | ||
|
|
6d64517635 | ||
|
|
e4de6d1752 | ||
|
|
70143264ec | ||
|
|
aaf7efefba | ||
|
|
76e69a2391 | ||
|
|
74ac483b01 | ||
|
|
7945041ca8 | ||
|
|
6c2eb8edc9 | ||
|
|
3436674697 | ||
|
|
8488b2d64a | ||
|
|
dccc532bfb | ||
|
|
0504472517 | ||
|
|
75c020c4b1 | ||
|
|
77911740ef | ||
|
|
8e23c6ec37 | ||
|
|
558a9149e0 | ||
|
|
c35cb3135e | ||
|
|
18ca369a5d | ||
|
|
677f227c3c | ||
|
|
cb502e4d64 | ||
|
|
482b1fc4d9 | ||
|
|
3a2dbf05a1 | ||
|
|
95057b9cde | ||
|
|
9de9e2e319 | ||
|
|
b97e7a717c | ||
|
|
1a0b7ca08e | ||
|
|
d61b32f627 | ||
|
|
58e578f7b6 | ||
|
|
d2c89a6f15 | ||
|
|
7e9fc45c23 | ||
|
|
a708630b4f | ||
|
|
d8b7d59e66 | ||
|
|
c9009b4554 | ||
|
|
b3bb553816 | ||
|
|
cbb1e7b388 | ||
|
|
a54010ae2e | ||
|
|
12d5688120 | ||
|
|
13166073da | ||
|
|
5251cbb92a | ||
|
|
38d5c5e7fd | ||
|
|
349d741c39 | ||
|
|
a4d614791b | ||
|
|
f643bfd10e | ||
|
|
60ca60be6f | ||
|
|
43e9b30386 | ||
|
|
671fe65b31 | ||
|
|
0f87a1f6bb | ||
|
|
2632e00c4d | ||
|
|
c6381a506f | ||
|
|
c804eb79c1 | ||
|
|
9b5e60d77a | ||
|
|
c966c76b04 | ||
|
|
c461987cf6 | ||
|
|
6aaea5fdb2 | ||
|
|
f602d3cffc | ||
|
|
4b94d45be2 | ||
|
|
2dee41a4fc | ||
|
|
dbde090045 | ||
|
|
d8a902440c | ||
|
|
a1296d40a1 | ||
|
|
44bc055c8c | ||
|
|
6df2d6ed74 | ||
|
|
4f7fddd848 | ||
|
|
3c451a8437 | ||
|
|
b2990440f2 | ||
|
|
b21e49829f | ||
|
|
6d900d2a59 | ||
|
|
c274a8cadb | ||
|
|
aa17223a61 | ||
|
|
b691810dde | ||
|
|
3ebe50c1ca | ||
|
|
28a1c56fb4 | ||
|
|
b247aba61a | ||
|
|
4c765197cf | ||
|
|
55e3ed56ec | ||
|
|
f39148a492 | ||
|
|
4c4d4a7718 | ||
|
|
c18d4712dc | ||
|
|
84a841b5b9 | ||
|
|
67925a6731 | ||
|
|
85167b3594 | ||
|
|
c78be2d8f8 | ||
|
|
2df017eb77 | ||
|
|
ada7232e51 | ||
|
|
ad798f5ecf | ||
|
|
40f269cf29 | ||
|
|
ce150eaae2 | ||
|
|
fe8298ec3b | ||
|
|
c67209dad5 | ||
|
|
a7e3826362 | ||
|
|
b7dc6f3bcf | ||
|
|
49b9491dc3 | ||
|
|
6e07fe76f0 | ||
|
|
cd52edb8e1 | ||
|
|
bd166d6318 | ||
|
|
6de89a57c4 | ||
|
|
a72a790aa2 | ||
|
|
5a3039c721 | ||
|
|
38b27effa5 | ||
|
|
38e98fdbc8 | ||
|
|
d10f2e57d3 | ||
|
|
dfc8a15c8c | ||
|
|
dce87b88fe | ||
|
|
ce6e7b9f3f | ||
|
|
ad77210892 | ||
|
|
089ea89f89 | ||
|
|
f7e9bff5fc | ||
|
|
5829cb869b | ||
|
|
8c0bbcc2a5 | ||
|
|
e7e96971dc | ||
|
|
e876c23cd8 | ||
|
|
b471e29b8d | ||
|
|
5a775fb147 | ||
|
|
4aa58e1fb5 | ||
|
|
28dfda38e1 | ||
|
|
b274b3434f | ||
|
|
b7f1e76954 | ||
|
|
be91887ecb | ||
|
|
bbf022e7cc | ||
|
|
41a242460d | ||
|
|
910cd78a08 | ||
|
|
1d4c135f22 | ||
|
|
12bf442946 | ||
|
|
999af8ca43 | ||
|
|
36f99e5a39 | ||
|
|
5f107f8cc4 | ||
|
|
70133b22d0 | ||
|
|
72e5b4225f | ||
|
|
00f21a210a | ||
|
|
7fa83081fc | ||
|
|
28530c28f9 | ||
|
|
e7c956337a | ||
|
|
9a5ad5f02e | ||
|
|
1153cc5919 | ||
|
|
c34acd279e | ||
|
|
6006b3514f | ||
|
|
e6cd9ba1a8 | ||
|
|
9e3e30f9a2 | ||
|
|
4687a1722c | ||
|
|
87af2a9001 | ||
|
|
94594f1cbf | ||
|
|
af2b905514 | ||
|
|
943ef2fad8 | ||
|
|
76dac9d657 | ||
|
|
333f24caec | ||
|
|
e5bd9b93b4 | ||
|
|
a4ff9dd2dc | ||
|
|
2d3224bf20 | ||
|
|
e6f6420d98 | ||
|
|
f0b8b3a960 | ||
|
|
fef73fcd3a | ||
|
|
d12e2d67c9 | ||
|
|
eeb98ec6ae | ||
|
|
3f2bc0a2e1 | ||
|
|
cbd023e0db | ||
|
|
307146220b | ||
|
|
d754cd3a49 | ||
|
|
3d5309b736 | ||
|
|
369b6e0192 | ||
|
|
e03e489758 | ||
|
|
321e17803e | ||
|
|
086ed4af2a | ||
|
|
67ff1d9ffb | ||
|
|
8de9b33e14 | ||
|
|
afe59c8e1d | ||
|
|
f6434fde67 | ||
|
|
e46465c718 | ||
|
|
e4156f232b | ||
|
|
bfa254bd1a | ||
|
|
4315a12330 | ||
|
|
9b58e1d1a8 | ||
|
|
f4ff33fd11 | ||
|
|
f9f50c5638 | ||
|
|
23eef02eff | ||
|
|
bef1f4761e | ||
|
|
8b9bdf1d1e | ||
|
|
1a4b81a392 | ||
|
|
c111b692cc | ||
|
|
701e43727a | ||
|
|
dedca9ecb7 | ||
|
|
d890775c90 | ||
|
|
fd3e68b355 | ||
|
|
678eafa74d | ||
|
|
c2014927f2 | ||
|
|
c5b7872dab | ||
|
|
2691a54e91 | ||
|
|
e2de476001 | ||
|
|
45c8a7e583 | ||
|
|
4d2fd8ddd3 | ||
|
|
ea0af53e2a | ||
|
|
2d342da2a3 | ||
|
|
f1b304df93 | ||
|
|
b569f2a414 | ||
|
|
411f541288 | ||
|
|
83477f0280 | ||
|
|
7e7893983f | ||
|
|
e3a67d60cc | ||
|
|
23a0428069 | ||
|
|
b74901b816 | ||
|
|
010625f35a | ||
|
|
0647439a0a | ||
|
|
87a05c7316 | ||
|
|
5c33ee311c | ||
|
|
3ad92d4d09 | ||
|
|
5e5a7fd73d | ||
|
|
3269613984 | ||
|
|
b955cbdcfb | ||
|
|
58e47e1b70 | ||
|
|
bb0524e189 | ||
|
|
7d58c8bed6 | ||
|
|
5232b03e22 | ||
|
|
e2312cd887 | ||
|
|
f552970178 | ||
|
|
d59e28ea73 | ||
|
|
61642ef429 | ||
|
|
7f6f1519b9 | ||
|
|
528de6bc4f | ||
|
|
0ccf16fab3 | ||
|
|
4e513e2b86 | ||
|
|
1272d1b8fc | ||
|
|
686396e3dc | ||
|
|
2fa7fde32f | ||
|
|
72050b72e2 | ||
|
|
2fa97151cd | ||
|
|
e67a7af07c | ||
|
|
2306702899 | ||
|
|
f652c4d1b8 | ||
|
|
ca1ed50ab3 | ||
|
|
46db037b1f | ||
|
|
70761072f5 | ||
|
|
efa68c6490 | ||
|
|
3daa976efb | ||
|
|
4d35e4b168 | ||
|
|
dff89518bd | ||
|
|
cdae385f7d | ||
|
|
b7a7dde44a |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -31,9 +31,3 @@ pull-*/
|
|||||||
bitbake/lib/toaster/contrib/tts/backlog.txt
|
bitbake/lib/toaster/contrib/tts/backlog.txt
|
||||||
bitbake/lib/toaster/contrib/tts/log/*
|
bitbake/lib/toaster/contrib/tts/log/*
|
||||||
bitbake/lib/toaster/contrib/tts/.cache/*
|
bitbake/lib/toaster/contrib/tts/.cache/*
|
||||||
bitbake/lib/bb/tests/runqueue-tests/bitbake-cookerdaemon.log
|
|
||||||
_toaster_clones/
|
|
||||||
downloads/
|
|
||||||
sstate-cache/
|
|
||||||
toaster.sqlite
|
|
||||||
.vscode/
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
# Template settings
|
# Template settings
|
||||||
TEMPLATECONF=${TEMPLATECONF:-meta-poky/conf/templates/default}
|
TEMPLATECONF=${TEMPLATECONF:-meta-poky/conf}
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
OpenEmbedded-Core and Yocto Project Maintainer Information
|
|
||||||
==========================================================
|
|
||||||
|
|
||||||
OpenEmbedded and Yocto Project work jointly together to maintain the metadata,
|
|
||||||
layers, tools and sub-projects that make up their ecosystems.
|
|
||||||
|
|
||||||
The projects operate through collaborative development. This currently takes
|
|
||||||
place on mailing lists for many components as the "pull request on github"
|
|
||||||
workflow works well for single or small numbers of maintainers but we have
|
|
||||||
a large number, all with different specialisms and benefit from the mailing
|
|
||||||
list review process. Changes therefore undergo peer review through mailing
|
|
||||||
lists in many cases.
|
|
||||||
|
|
||||||
This file aims to acknowledge people with specific skills/knowledge/interest
|
|
||||||
both to recognise their contributions but also empower them to help lead and
|
|
||||||
curate those components. Where we have people with specialist knowledge in
|
|
||||||
particular areas, during review patches/feedback from these people in these
|
|
||||||
areas would generally carry weight.
|
|
||||||
|
|
||||||
This file is maintained in OE-Core but may refer to components that are separate
|
|
||||||
to it if that makes sense in the context of maintainership. The README of specific
|
|
||||||
layers and components should ultimately be definitive about the patch process and
|
|
||||||
maintainership for the component.
|
|
||||||
|
|
||||||
Recipe Maintainers
|
|
||||||
------------------
|
|
||||||
|
|
||||||
See meta/conf/distro/include/maintainers.inc
|
|
||||||
|
|
||||||
Component/Subsystem Maintainers
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
* Kernel (inc. linux-yocto, perf): Bruce Ashfield
|
|
||||||
* Reproducible Builds: Joshua Watt
|
|
||||||
* Toaster: David Reyna
|
|
||||||
* Hash-Equivalence: Joshua Watt
|
|
||||||
* Recipe upgrade infrastructure: Alex Kanavin
|
|
||||||
* Toolchain: Khem Raj
|
|
||||||
* ptest-runner: Aníbal Limón
|
|
||||||
* opkg: Alex Stewart
|
|
||||||
* devtool: Saul Wold
|
|
||||||
* eSDK: Saul Wold
|
|
||||||
* overlayfs: Vyacheslav Yurkov
|
|
||||||
|
|
||||||
Maintainers needed
|
|
||||||
------------------
|
|
||||||
|
|
||||||
* Pseudo
|
|
||||||
* Layer Index
|
|
||||||
* recipetool
|
|
||||||
* QA framework/automated testing
|
|
||||||
* error reporting system/web UI
|
|
||||||
* wic
|
|
||||||
* Patchwork
|
|
||||||
* Patchtest
|
|
||||||
* Matchbox
|
|
||||||
* Sato
|
|
||||||
* Autobuilder
|
|
||||||
|
|
||||||
Layer Maintainers needed
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
* meta-gplv2 (ideally new strategy but active maintainer welcome)
|
|
||||||
|
|
||||||
Shadow maintainers/development needed
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
* toaster
|
|
||||||
* bitbake
|
|
||||||
|
|
||||||
|
|
||||||
35
Makefile
Normal file
35
Makefile
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Minimal makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line, and also
|
||||||
|
# from the environment for the first two.
|
||||||
|
SPHINXOPTS ?=
|
||||||
|
SPHINXBUILD ?= sphinx-build
|
||||||
|
SOURCEDIR = .
|
||||||
|
BUILDDIR = _build
|
||||||
|
DESTDIR = final
|
||||||
|
|
||||||
|
ifeq ($(shell if which $(SPHINXBUILD) >/dev/null 2>&1; then echo 1; else echo 0; fi),0)
|
||||||
|
$(error "The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed")
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Put it first so that "make" without argument is like "make help".
|
||||||
|
help:
|
||||||
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
.PHONY: help Makefile.sphinx clean publish
|
||||||
|
|
||||||
|
publish: Makefile.sphinx html singlehtml
|
||||||
|
rm -rf $(BUILDDIR)/$(DESTDIR)/
|
||||||
|
mkdir -p $(BUILDDIR)/$(DESTDIR)/
|
||||||
|
cp -r $(BUILDDIR)/html/* $(BUILDDIR)/$(DESTDIR)/
|
||||||
|
cp $(BUILDDIR)/singlehtml/index.html $(BUILDDIR)/$(DESTDIR)/singleindex.html
|
||||||
|
sed -i -e 's@index.html#@singleindex.html#@g' $(BUILDDIR)/$(DESTDIR)/singleindex.html
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@rm -rf $(BUILDDIR)
|
||||||
|
|
||||||
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
|
%: Makefile.sphinx
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
29
README.OE-Core
Normal file
29
README.OE-Core
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
OpenEmbedded-Core
|
||||||
|
=================
|
||||||
|
|
||||||
|
OpenEmbedded-Core is a layer containing the core metadata for current versions
|
||||||
|
of OpenEmbedded. It is distro-less (can build a functional image with
|
||||||
|
DISTRO = "nodistro") and contains only emulated machine support.
|
||||||
|
|
||||||
|
For information about OpenEmbedded, see the OpenEmbedded website:
|
||||||
|
http://www.openembedded.org/
|
||||||
|
|
||||||
|
The Yocto Project has extensive documentation about OE including a reference manual
|
||||||
|
which can be found at:
|
||||||
|
http://yoctoproject.org/documentation
|
||||||
|
|
||||||
|
|
||||||
|
Contributing
|
||||||
|
------------
|
||||||
|
|
||||||
|
Please refer to
|
||||||
|
http://www.openembedded.org/wiki/How_to_submit_a_patch_to_OpenEmbedded
|
||||||
|
for guidelines on how to submit patches.
|
||||||
|
|
||||||
|
Mailing list:
|
||||||
|
|
||||||
|
http://lists.openembedded.org/mailman/listinfo/openembedded-core
|
||||||
|
|
||||||
|
Source code:
|
||||||
|
|
||||||
|
http://git.openembedded.org/openembedded-core/
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
OpenEmbedded-Core
|
|
||||||
=================
|
|
||||||
|
|
||||||
OpenEmbedded-Core is a layer containing the core metadata for current versions
|
|
||||||
of OpenEmbedded. It is distro-less (can build a functional image with
|
|
||||||
DISTRO = "nodistro") and contains only emulated machine support.
|
|
||||||
|
|
||||||
For information about OpenEmbedded, see the OpenEmbedded website:
|
|
||||||
https://www.openembedded.org/
|
|
||||||
|
|
||||||
The Yocto Project has extensive documentation about OE including a reference manual
|
|
||||||
which can be found at:
|
|
||||||
https://docs.yoctoproject.org/
|
|
||||||
|
|
||||||
|
|
||||||
Contributing
|
|
||||||
------------
|
|
||||||
|
|
||||||
Please refer to our contributor guide here: https://docs.yoctoproject.org/dev/contributor-guide/
|
|
||||||
for full details on how to submit changes.
|
|
||||||
|
|
||||||
As a quick guide, patches should be sent to openembedded-core@lists.openembedded.org
|
|
||||||
The git command to do that would be:
|
|
||||||
|
|
||||||
git send-email -M -1 --to openembedded-core@lists.openembedded.org
|
|
||||||
|
|
||||||
Mailing list:
|
|
||||||
|
|
||||||
https://lists.openembedded.org/g/openembedded-core
|
|
||||||
|
|
||||||
Source code:
|
|
||||||
|
|
||||||
https://git.openembedded.org/openembedded-core/
|
|
||||||
1
README.hardware
Symbolic link
1
README.hardware
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
meta-yocto-bsp/README.hardware
|
||||||
@@ -1 +0,0 @@
|
|||||||
meta-yocto-bsp/README.hardware.md
|
|
||||||
1
README.poky
Symbolic link
1
README.poky
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
meta-poky/README.poky
|
||||||
@@ -1 +0,0 @@
|
|||||||
meta-poky/README.poky.md
|
|
||||||
22
SECURITY.md
22
SECURITY.md
@@ -1,22 +0,0 @@
|
|||||||
How to Report a Potential Vulnerability?
|
|
||||||
========================================
|
|
||||||
|
|
||||||
If you would like to report a public issue (for example, one with a released
|
|
||||||
CVE number), please report it using the
|
|
||||||
[https://bugzilla.yoctoproject.org/enter_bug.cgi?product=Security Security Bugzilla]
|
|
||||||
|
|
||||||
If you are dealing with a not-yet released or urgent issue, please send a
|
|
||||||
message to security AT yoctoproject DOT org, including as many details as
|
|
||||||
possible: the layer or software module affected, the recipe and its version,
|
|
||||||
and any example code, if available.
|
|
||||||
|
|
||||||
Branches maintained with security fixes
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
See [https://wiki.yoctoproject.org/wiki/Stable_Release_and_LTS Stable release and LTS]
|
|
||||||
for detailed info regarding the policies and maintenance of Stable branches.
|
|
||||||
|
|
||||||
The [https://wiki.yoctoproject.org/wiki/Releases Release page] contains a list of all
|
|
||||||
releases of the Yocto Project. Versions in grey are no longer actively maintained with
|
|
||||||
security patches, but well-tested patches may still be accepted for them for
|
|
||||||
significant issues.
|
|
||||||
@@ -7,57 +7,29 @@ One of BitBake's main users, OpenEmbedded, takes this core and builds embedded L
|
|||||||
stacks using a task-oriented approach.
|
stacks using a task-oriented approach.
|
||||||
|
|
||||||
For information about Bitbake, see the OpenEmbedded website:
|
For information about Bitbake, see the OpenEmbedded website:
|
||||||
https://www.openembedded.org/
|
http://www.openembedded.org/
|
||||||
|
|
||||||
Bitbake plain documentation can be found under the doc directory or its integrated
|
Bitbake plain documentation can be found under the doc directory or its integrated
|
||||||
html version at the Yocto Project website:
|
html version at the Yocto Project website:
|
||||||
https://docs.yoctoproject.org
|
http://yoctoproject.org/documentation
|
||||||
|
|
||||||
Bitbake requires Python version 3.8 or newer.
|
|
||||||
|
|
||||||
Contributing
|
Contributing
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Please refer to our contributor guide here: https://docs.yoctoproject.org/contributor-guide/
|
Please refer to
|
||||||
for full details on how to submit changes.
|
http://www.openembedded.org/wiki/How_to_submit_a_patch_to_OpenEmbedded
|
||||||
|
for guidelines on how to submit patches, just note that the latter documentation is intended
|
||||||
As a quick guide, patches should be sent to bitbake-devel@lists.openembedded.org
|
for OpenEmbedded (and its core) not bitbake patches (bitbake-devel@lists.openembedded.org)
|
||||||
The git command to do that would be:
|
but in general main guidelines apply. Once the commit(s) have been created, the way to send
|
||||||
|
the patch is through git-send-email. For example, to send the last commit (HEAD) on current
|
||||||
|
branch, type:
|
||||||
|
|
||||||
git send-email -M -1 --to bitbake-devel@lists.openembedded.org
|
git send-email -M -1 --to bitbake-devel@lists.openembedded.org
|
||||||
|
|
||||||
If you're sending a patch related to the BitBake manual, make sure you copy
|
|
||||||
the Yocto Project documentation mailing list:
|
|
||||||
|
|
||||||
git send-email -M -1 --to bitbake-devel@lists.openembedded.org --cc docs@lists.yoctoproject.org
|
|
||||||
|
|
||||||
Mailing list:
|
Mailing list:
|
||||||
|
|
||||||
https://lists.openembedded.org/g/bitbake-devel
|
http://lists.openembedded.org/mailman/listinfo/bitbake-devel
|
||||||
|
|
||||||
Source code:
|
Source code:
|
||||||
|
|
||||||
https://git.openembedded.org/bitbake/
|
http://git.openembedded.org/bitbake/
|
||||||
|
|
||||||
Testing
|
|
||||||
-------
|
|
||||||
|
|
||||||
Bitbake has a testsuite located in lib/bb/tests/ whichs aim to try and prevent regressions.
|
|
||||||
You can run this with "bitbake-selftest". In particular the fetcher is well covered since
|
|
||||||
it has so many corner cases. The datastore has many tests too. Testing with the testsuite is
|
|
||||||
recommended before submitting patches, particularly to the fetcher and datastore. We also
|
|
||||||
appreciate new test cases and may require them for more obscure issues.
|
|
||||||
|
|
||||||
To run the tests "zstd" and "git" must be installed.
|
|
||||||
|
|
||||||
The assumption is made that this testsuite is run from an initialized OpenEmbedded build
|
|
||||||
environment (i.e. `source oe-init-build-env` is used). If this is not the case, run the
|
|
||||||
testsuite as follows:
|
|
||||||
|
|
||||||
export PATH=$(pwd)/bin:$PATH
|
|
||||||
bin/bitbake-selftest
|
|
||||||
|
|
||||||
The testsuite can alternatively be executed using pytest, e.g. obtained from PyPI (in this
|
|
||||||
case, the PATH is configured automatically):
|
|
||||||
|
|
||||||
pytest
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
How to Report a Potential Vulnerability?
|
|
||||||
========================================
|
|
||||||
|
|
||||||
If you would like to report a public issue (for example, one with a released
|
|
||||||
CVE number), please report it using the
|
|
||||||
[https://bugzilla.yoctoproject.org/enter_bug.cgi?product=Security Security Bugzilla].
|
|
||||||
If you have a patch ready, submit it following the same procedure as any other
|
|
||||||
patch as described in README.md.
|
|
||||||
|
|
||||||
If you are dealing with a not-yet released or urgent issue, please send a
|
|
||||||
message to security AT yoctoproject DOT org, including as many details as
|
|
||||||
possible: the layer or software module affected, the recipe and its version,
|
|
||||||
and any example code, if available.
|
|
||||||
|
|
||||||
Branches maintained with security fixes
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
See [https://wiki.yoctoproject.org/wiki/Stable_Release_and_LTS Stable release and LTS]
|
|
||||||
for detailed info regarding the policies and maintenance of Stable branches.
|
|
||||||
|
|
||||||
The [https://wiki.yoctoproject.org/wiki/Releases Release page] contains a list of all
|
|
||||||
releases of the Yocto Project. Versions in grey are no longer actively maintained with
|
|
||||||
security patches, but well-tested patches may still be accepted for them for
|
|
||||||
significant issues.
|
|
||||||
@@ -12,8 +12,6 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
|
||||||
warnings.simplefilter("default")
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
||||||
'lib'))
|
'lib'))
|
||||||
@@ -25,9 +23,10 @@ except RuntimeError as exc:
|
|||||||
from bb import cookerdata
|
from bb import cookerdata
|
||||||
from bb.main import bitbake_main, BitBakeConfigParameters, BBMainException
|
from bb.main import bitbake_main, BitBakeConfigParameters, BBMainException
|
||||||
|
|
||||||
bb.utils.check_system_locale()
|
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__ = "2.8.1"
|
__version__ = "1.48.0"
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if __version__ != bb.__version__:
|
if __version__ != bb.__version__:
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
warnings.simplefilter("default")
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import pickle
|
import pickle
|
||||||
@@ -28,7 +26,6 @@ logger = bb.msg.logger_create(myname)
|
|||||||
|
|
||||||
is_dump = myname == 'bitbake-dumpsig'
|
is_dump = myname == 'bitbake-dumpsig'
|
||||||
|
|
||||||
|
|
||||||
def find_siginfo(tinfoil, pn, taskname, sigs=None):
|
def find_siginfo(tinfoil, pn, taskname, sigs=None):
|
||||||
result = None
|
result = None
|
||||||
tinfoil.set_event_mask(['bb.event.FindSigInfoResult',
|
tinfoil.set_event_mask(['bb.event.FindSigInfoResult',
|
||||||
@@ -54,7 +51,6 @@ def find_siginfo(tinfoil, pn, taskname, sigs=None):
|
|||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def find_siginfo_task(bbhandler, pn, taskname, sig1=None, sig2=None):
|
def find_siginfo_task(bbhandler, pn, taskname, sig1=None, sig2=None):
|
||||||
""" Find the most recent signature files for the specified PN/task """
|
""" Find the most recent signature files for the specified PN/task """
|
||||||
|
|
||||||
@@ -63,26 +59,22 @@ def find_siginfo_task(bbhandler, pn, taskname, sig1=None, sig2=None):
|
|||||||
|
|
||||||
if sig1 and sig2:
|
if sig1 and sig2:
|
||||||
sigfiles = find_siginfo(bbhandler, pn, taskname, [sig1, sig2])
|
sigfiles = find_siginfo(bbhandler, pn, taskname, [sig1, sig2])
|
||||||
if not sigfiles:
|
if len(sigfiles) == 0:
|
||||||
logger.error('No sigdata files found matching %s %s matching either %s or %s' % (pn, taskname, sig1, sig2))
|
logger.error('No sigdata files found matching %s %s matching either %s or %s' % (pn, taskname, sig1, sig2))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif sig1 not in sigfiles:
|
elif not sig1 in sigfiles:
|
||||||
logger.error('No sigdata files found matching %s %s with signature %s' % (pn, taskname, sig1))
|
logger.error('No sigdata files found matching %s %s with signature %s' % (pn, taskname, sig1))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif sig2 not in sigfiles:
|
elif not sig2 in sigfiles:
|
||||||
logger.error('No sigdata files found matching %s %s with signature %s' % (pn, taskname, sig2))
|
logger.error('No sigdata files found matching %s %s with signature %s' % (pn, taskname, sig2))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
latestfiles = [sigfiles[sig1], sigfiles[sig2]]
|
||||||
latestfiles = [sigfiles[sig1]['path'], sigfiles[sig2]['path']]
|
|
||||||
else:
|
else:
|
||||||
sigfiles = find_siginfo(bbhandler, pn, taskname)
|
filedates = find_siginfo(bbhandler, pn, taskname)
|
||||||
latestsigs = sorted(sigfiles.keys(), key=lambda h: sigfiles[h]['time'])[-2:]
|
latestfiles = sorted(filedates.keys(), key=lambda f: filedates[f])[-2:]
|
||||||
if not latestsigs:
|
if not latestfiles:
|
||||||
logger.error('No sigdata files found matching %s %s' % (pn, taskname))
|
logger.error('No sigdata files found matching %s %s' % (pn, taskname))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
latestfiles = [sigfiles[latestsigs[0]]['path']]
|
|
||||||
if len(latestsigs) > 1:
|
|
||||||
latestfiles.append(sigfiles[latestsigs[1]]['path'])
|
|
||||||
|
|
||||||
return latestfiles
|
return latestfiles
|
||||||
|
|
||||||
@@ -93,14 +85,14 @@ def recursecb(key, hash1, hash2):
|
|||||||
hashfiles = find_siginfo(tinfoil, key, None, hashes)
|
hashfiles = find_siginfo(tinfoil, key, None, hashes)
|
||||||
|
|
||||||
recout = []
|
recout = []
|
||||||
if not hashfiles:
|
if len(hashfiles) == 0:
|
||||||
recout.append("Unable to find matching sigdata for %s with hashes %s or %s" % (key, hash1, hash2))
|
recout.append("Unable to find matching sigdata for %s with hashes %s or %s" % (key, hash1, hash2))
|
||||||
elif hash1 not in hashfiles:
|
elif not hash1 in hashfiles:
|
||||||
recout.append("Unable to find matching sigdata for %s with hash %s" % (key, hash1))
|
recout.append("Unable to find matching sigdata for %s with hash %s" % (key, hash1))
|
||||||
elif hash2 not in hashfiles:
|
elif not hash2 in hashfiles:
|
||||||
recout.append("Unable to find matching sigdata for %s with hash %s" % (key, hash2))
|
recout.append("Unable to find matching sigdata for %s with hash %s" % (key, hash2))
|
||||||
else:
|
else:
|
||||||
out2 = bb.siggen.compare_sigfiles(hashfiles[hash1]['path'], hashfiles[hash2]['path'], recursecb, color=color)
|
out2 = bb.siggen.compare_sigfiles(hashfiles[hash1], hashfiles[hash2], recursecb, color=color)
|
||||||
for change in out2:
|
for change in out2:
|
||||||
for line in change.splitlines():
|
for line in change.splitlines():
|
||||||
recout.append(' ' + line)
|
recout.append(' ' + line)
|
||||||
@@ -117,36 +109,36 @@ parser.add_argument('-D', '--debug',
|
|||||||
|
|
||||||
if is_dump:
|
if is_dump:
|
||||||
parser.add_argument("-t", "--task",
|
parser.add_argument("-t", "--task",
|
||||||
help="find the signature data file for the last run of the specified task",
|
help="find the signature data file for the last run of the specified task",
|
||||||
action="store", dest="taskargs", nargs=2, metavar=('recipename', 'taskname'))
|
action="store", dest="taskargs", nargs=2, metavar=('recipename', 'taskname'))
|
||||||
|
|
||||||
parser.add_argument("sigdatafile1",
|
parser.add_argument("sigdatafile1",
|
||||||
help="Signature file to dump. Not used when using -t/--task.",
|
help="Signature file to dump. Not used when using -t/--task.",
|
||||||
action="store", nargs='?', metavar="sigdatafile")
|
action="store", nargs='?', metavar="sigdatafile")
|
||||||
else:
|
else:
|
||||||
parser.add_argument('-c', '--color',
|
parser.add_argument('-c', '--color',
|
||||||
help='Colorize the output (where %(metavar)s is %(choices)s)',
|
help='Colorize the output (where %(metavar)s is %(choices)s)',
|
||||||
choices=['auto', 'always', 'never'], default='auto', metavar='color')
|
choices=['auto', 'always', 'never'], default='auto', metavar='color')
|
||||||
|
|
||||||
parser.add_argument('-d', '--dump',
|
parser.add_argument('-d', '--dump',
|
||||||
help='Dump the last signature data instead of comparing (equivalent to using bitbake-dumpsig)',
|
help='Dump the last signature data instead of comparing (equivalent to using bitbake-dumpsig)',
|
||||||
action='store_true')
|
action='store_true')
|
||||||
|
|
||||||
parser.add_argument("-t", "--task",
|
parser.add_argument("-t", "--task",
|
||||||
help="find the signature data files for the last two runs of the specified task and compare them",
|
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'))
|
action="store", dest="taskargs", nargs=2, metavar=('recipename', 'taskname'))
|
||||||
|
|
||||||
parser.add_argument("-s", "--signature",
|
parser.add_argument("-s", "--signature",
|
||||||
help="With -t/--task, specify the signatures to look for instead of taking the last two",
|
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'))
|
action="store", dest="sigargs", nargs=2, metavar=('fromsig', 'tosig'))
|
||||||
|
|
||||||
parser.add_argument("sigdatafile1",
|
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.",
|
help="First signature file to compare (or signature file to dump, if second not specified). Not used when using -t/--task.",
|
||||||
action="store", nargs='?')
|
action="store", nargs='?')
|
||||||
|
|
||||||
parser.add_argument("sigdatafile2",
|
parser.add_argument("sigdatafile2",
|
||||||
help="Second signature file to compare",
|
help="Second signature file to compare",
|
||||||
action="store", nargs='?')
|
action="store", nargs='?')
|
||||||
|
|
||||||
options = parser.parse_args()
|
options = parser.parse_args()
|
||||||
if is_dump:
|
if is_dump:
|
||||||
@@ -164,8 +156,7 @@ if options.taskargs:
|
|||||||
with bb.tinfoil.Tinfoil() as tinfoil:
|
with bb.tinfoil.Tinfoil() as tinfoil:
|
||||||
tinfoil.prepare(config_only=True)
|
tinfoil.prepare(config_only=True)
|
||||||
if not options.dump and options.sigargs:
|
if not options.dump and options.sigargs:
|
||||||
files = find_siginfo_task(tinfoil, options.taskargs[0], options.taskargs[1], options.sigargs[0],
|
files = find_siginfo_task(tinfoil, options.taskargs[0], options.taskargs[1], options.sigargs[0], options.sigargs[1])
|
||||||
options.sigargs[1])
|
|
||||||
else:
|
else:
|
||||||
files = find_siginfo_task(tinfoil, options.taskargs[0], options.taskargs[1])
|
files = find_siginfo_task(tinfoil, options.taskargs[0], options.taskargs[1])
|
||||||
|
|
||||||
@@ -174,8 +165,7 @@ if options.taskargs:
|
|||||||
output = bb.siggen.dump_sigfile(files[-1])
|
output = bb.siggen.dump_sigfile(files[-1])
|
||||||
else:
|
else:
|
||||||
if len(files) < 2:
|
if len(files) < 2:
|
||||||
logger.error('Only one matching sigdata file found for the specified task (%s %s)' % (
|
logger.error('Only one matching sigdata file found for the specified task (%s %s)' % (options.taskargs[0], options.taskargs[1]))
|
||||||
options.taskargs[0], options.taskargs[1]))
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Recurse into signature comparison
|
# Recurse into signature comparison
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
#! /usr/bin/env python3
|
|
||||||
#
|
|
||||||
# Copyright (C) 2021 Richard Purdie
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import io
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import warnings
|
|
||||||
warnings.simplefilter("default")
|
|
||||||
|
|
||||||
bindir = os.path.dirname(__file__)
|
|
||||||
topdir = os.path.dirname(bindir)
|
|
||||||
sys.path[0:0] = [os.path.join(topdir, 'lib')]
|
|
||||||
|
|
||||||
import bb.tinfoil
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
parser = argparse.ArgumentParser(description="Bitbake Query Variable")
|
|
||||||
parser.add_argument("variable", help="variable name to query")
|
|
||||||
parser.add_argument("-r", "--recipe", help="Recipe name to query", default=None, required=False)
|
|
||||||
parser.add_argument('-u', '--unexpand', help='Do not expand the value (with --value)', action="store_true")
|
|
||||||
parser.add_argument('-f', '--flag', help='Specify a variable flag to query (with --value)', default=None)
|
|
||||||
parser.add_argument('--value', help='Only report the value, no history and no variable name', action="store_true")
|
|
||||||
parser.add_argument('-q', '--quiet', help='Silence bitbake server logging', action="store_true")
|
|
||||||
parser.add_argument('--ignore-undefined', help='Suppress any errors related to undefined variables', action="store_true")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if not args.value:
|
|
||||||
if args.unexpand:
|
|
||||||
sys.exit("--unexpand only makes sense with --value")
|
|
||||||
|
|
||||||
if args.flag:
|
|
||||||
sys.exit("--flag only makes sense with --value")
|
|
||||||
|
|
||||||
quiet = args.quiet or args.value
|
|
||||||
with bb.tinfoil.Tinfoil(tracking=True, setup_logging=not quiet) as tinfoil:
|
|
||||||
if args.recipe:
|
|
||||||
tinfoil.prepare(quiet=3 if quiet else 2)
|
|
||||||
d = tinfoil.parse_recipe(args.recipe)
|
|
||||||
else:
|
|
||||||
tinfoil.prepare(quiet=2, config_only=True)
|
|
||||||
d = tinfoil.config_data
|
|
||||||
|
|
||||||
value = None
|
|
||||||
if args.flag:
|
|
||||||
value = d.getVarFlag(args.variable, args.flag, expand=not args.unexpand)
|
|
||||||
if value is None and not args.ignore_undefined:
|
|
||||||
sys.exit(f"The flag '{args.flag}' is not defined for variable '{args.variable}'")
|
|
||||||
else:
|
|
||||||
value = d.getVar(args.variable, expand=not args.unexpand)
|
|
||||||
if value is None and not args.ignore_undefined:
|
|
||||||
sys.exit(f"The variable '{args.variable}' is not defined")
|
|
||||||
if args.value:
|
|
||||||
print(str(value if value is not None else ""))
|
|
||||||
else:
|
|
||||||
bb.data.emit_var(args.variable, d=d, all=True)
|
|
||||||
@@ -13,10 +13,6 @@ import pprint
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import warnings
|
|
||||||
import netrc
|
|
||||||
import json
|
|
||||||
warnings.simplefilter("default")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import tqdm
|
import tqdm
|
||||||
@@ -38,42 +34,18 @@ except ImportError:
|
|||||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib'))
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib'))
|
||||||
|
|
||||||
import hashserv
|
import hashserv
|
||||||
import bb.asyncrpc
|
|
||||||
|
|
||||||
DEFAULT_ADDRESS = 'unix://./hashserve.sock'
|
DEFAULT_ADDRESS = 'unix://./hashserve.sock'
|
||||||
METHOD = 'stress.test.method'
|
METHOD = 'stress.test.method'
|
||||||
|
|
||||||
def print_user(u):
|
|
||||||
print(f"Username: {u['username']}")
|
|
||||||
if "permissions" in u:
|
|
||||||
print("Permissions: " + " ".join(u["permissions"]))
|
|
||||||
if "token" in u:
|
|
||||||
print(f"Token: {u['token']}")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
def handle_get(args, client):
|
|
||||||
result = client.get_taskhash(args.method, args.taskhash, all_properties=True)
|
|
||||||
if not result:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
print(json.dumps(result, sort_keys=True, indent=4))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def handle_get_outhash(args, client):
|
|
||||||
result = client.get_outhash(args.method, args.outhash, args.taskhash)
|
|
||||||
if not result:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
print(json.dumps(result, sort_keys=True, indent=4))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def handle_stats(args, client):
|
def handle_stats(args, client):
|
||||||
if args.reset:
|
if args.reset:
|
||||||
s = client.reset_stats()
|
s = client.reset_stats()
|
||||||
else:
|
else:
|
||||||
s = client.get_stats()
|
s = client.get_stats()
|
||||||
print(json.dumps(s, sort_keys=True, indent=4))
|
pprint.pprint(s)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def handle_stress(args, client):
|
def handle_stress(args, client):
|
||||||
@@ -82,24 +54,25 @@ def main():
|
|||||||
nonlocal missed_hashes
|
nonlocal missed_hashes
|
||||||
nonlocal max_time
|
nonlocal max_time
|
||||||
|
|
||||||
with hashserv.create_client(args.address) as client:
|
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()
|
for i in range(args.requests):
|
||||||
l = client.get_unihash(METHOD, taskhash.hexdigest())
|
taskhash = hashlib.sha256()
|
||||||
elapsed = time.perf_counter() - start_time
|
taskhash.update(args.taskhash_seed.encode('utf-8'))
|
||||||
|
taskhash.update(str(i).encode('utf-8'))
|
||||||
|
|
||||||
with lock:
|
start_time = time.perf_counter()
|
||||||
if l:
|
l = client.get_unihash(METHOD, taskhash.hexdigest())
|
||||||
found_hashes += 1
|
elapsed = time.perf_counter() - start_time
|
||||||
else:
|
|
||||||
missed_hashes += 1
|
|
||||||
|
|
||||||
max_time = max(elapsed, max_time)
|
with lock:
|
||||||
pbar.update()
|
if l:
|
||||||
|
found_hashes += 1
|
||||||
|
else:
|
||||||
|
missed_hashes += 1
|
||||||
|
|
||||||
|
max_time = max(elapsed, max_time)
|
||||||
|
pbar.update()
|
||||||
|
|
||||||
max_time = 0
|
max_time = 0
|
||||||
found_hashes = 0
|
found_hashes = 0
|
||||||
@@ -138,114 +111,12 @@ def main():
|
|||||||
with lock:
|
with lock:
|
||||||
pbar.update()
|
pbar.update()
|
||||||
|
|
||||||
def handle_remove(args, client):
|
|
||||||
where = {k: v for k, v in args.where}
|
|
||||||
if where:
|
|
||||||
result = client.remove(where)
|
|
||||||
print("Removed %d row(s)" % (result["count"]))
|
|
||||||
else:
|
|
||||||
print("No query specified")
|
|
||||||
|
|
||||||
def handle_clean_unused(args, client):
|
|
||||||
result = client.clean_unused(args.max_age)
|
|
||||||
print("Removed %d rows" % (result["count"]))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def handle_refresh_token(args, client):
|
|
||||||
r = client.refresh_token(args.username)
|
|
||||||
print_user(r)
|
|
||||||
|
|
||||||
def handle_set_user_permissions(args, client):
|
|
||||||
r = client.set_user_perms(args.username, args.permissions)
|
|
||||||
print_user(r)
|
|
||||||
|
|
||||||
def handle_get_user(args, client):
|
|
||||||
r = client.get_user(args.username)
|
|
||||||
print_user(r)
|
|
||||||
|
|
||||||
def handle_get_all_users(args, client):
|
|
||||||
users = client.get_all_users()
|
|
||||||
print("{username:20}| {permissions}".format(username="Username", permissions="Permissions"))
|
|
||||||
print(("-" * 20) + "+" + ("-" * 20))
|
|
||||||
for u in users:
|
|
||||||
print("{username:20}| {permissions}".format(username=u["username"], permissions=" ".join(u["permissions"])))
|
|
||||||
|
|
||||||
def handle_new_user(args, client):
|
|
||||||
r = client.new_user(args.username, args.permissions)
|
|
||||||
print_user(r)
|
|
||||||
|
|
||||||
def handle_delete_user(args, client):
|
|
||||||
r = client.delete_user(args.username)
|
|
||||||
print_user(r)
|
|
||||||
|
|
||||||
def handle_get_db_usage(args, client):
|
|
||||||
usage = client.get_db_usage()
|
|
||||||
print(usage)
|
|
||||||
tables = sorted(usage.keys())
|
|
||||||
print("{name:20}| {rows:20}".format(name="Table name", rows="Rows"))
|
|
||||||
print(("-" * 20) + "+" + ("-" * 20))
|
|
||||||
for t in tables:
|
|
||||||
print("{name:20}| {rows:<20}".format(name=t, rows=usage[t]["rows"]))
|
|
||||||
print()
|
|
||||||
|
|
||||||
total_rows = sum(t["rows"] for t in usage.values())
|
|
||||||
print(f"Total rows: {total_rows}")
|
|
||||||
|
|
||||||
def handle_get_db_query_columns(args, client):
|
|
||||||
columns = client.get_db_query_columns()
|
|
||||||
print("\n".join(sorted(columns)))
|
|
||||||
|
|
||||||
def handle_gc_status(args, client):
|
|
||||||
result = client.gc_status()
|
|
||||||
if not result["mark"]:
|
|
||||||
print("No Garbage collection in progress")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
print("Current Mark: %s" % result["mark"])
|
|
||||||
print("Total hashes to keep: %d" % result["keep"])
|
|
||||||
print("Total hashes to remove: %s" % result["remove"])
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def handle_gc_mark(args, client):
|
|
||||||
where = {k: v for k, v in args.where}
|
|
||||||
result = client.gc_mark(args.mark, where)
|
|
||||||
print("New hashes marked: %d" % result["count"])
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def handle_gc_sweep(args, client):
|
|
||||||
result = client.gc_sweep(args.mark)
|
|
||||||
print("Removed %d rows" % result["count"])
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def handle_unihash_exists(args, client):
|
|
||||||
result = client.unihash_exists(args.unihash)
|
|
||||||
if args.quiet:
|
|
||||||
return 0 if result else 1
|
|
||||||
|
|
||||||
print("true" if result else "false")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Hash Equivalence Client')
|
parser = argparse.ArgumentParser(description='Hash Equivalence Client')
|
||||||
parser.add_argument('--address', default=DEFAULT_ADDRESS, help='Server address (default "%(default)s")')
|
parser.add_argument('--address', default=DEFAULT_ADDRESS, help='Server address (default "%(default)s")')
|
||||||
parser.add_argument('--log', default='WARNING', help='Set logging level')
|
parser.add_argument('--log', default='WARNING', help='Set logging level')
|
||||||
parser.add_argument('--login', '-l', metavar="USERNAME", help="Authenticate as USERNAME")
|
|
||||||
parser.add_argument('--password', '-p', metavar="TOKEN", help="Authenticate using token TOKEN")
|
|
||||||
parser.add_argument('--become', '-b', metavar="USERNAME", help="Impersonate user USERNAME (if allowed) when performing actions")
|
|
||||||
parser.add_argument('--no-netrc', '-n', action="store_false", dest="netrc", help="Do not use .netrc")
|
|
||||||
|
|
||||||
subparsers = parser.add_subparsers()
|
subparsers = parser.add_subparsers()
|
||||||
|
|
||||||
get_parser = subparsers.add_parser('get', help="Get the unihash for a taskhash")
|
|
||||||
get_parser.add_argument("method", help="Method to query")
|
|
||||||
get_parser.add_argument("taskhash", help="Task hash to query")
|
|
||||||
get_parser.set_defaults(func=handle_get)
|
|
||||||
|
|
||||||
get_outhash_parser = subparsers.add_parser('get-outhash', help="Get output hash information")
|
|
||||||
get_outhash_parser.add_argument("method", help="Method to query")
|
|
||||||
get_outhash_parser.add_argument("outhash", help="Output hash to query")
|
|
||||||
get_outhash_parser.add_argument("taskhash", help="Task hash to query")
|
|
||||||
get_outhash_parser.set_defaults(func=handle_get_outhash)
|
|
||||||
|
|
||||||
stats_parser = subparsers.add_parser('stats', help='Show server stats')
|
stats_parser = subparsers.add_parser('stats', help='Show server stats')
|
||||||
stats_parser.add_argument('--reset', action='store_true',
|
stats_parser.add_argument('--reset', action='store_true',
|
||||||
help='Reset server stats')
|
help='Reset server stats')
|
||||||
@@ -264,64 +135,6 @@ def main():
|
|||||||
help='Include string in outhash')
|
help='Include string in outhash')
|
||||||
stress_parser.set_defaults(func=handle_stress)
|
stress_parser.set_defaults(func=handle_stress)
|
||||||
|
|
||||||
remove_parser = subparsers.add_parser('remove', help="Remove hash entries")
|
|
||||||
remove_parser.add_argument("--where", "-w", metavar="KEY VALUE", nargs=2, action="append", default=[],
|
|
||||||
help="Remove entries from table where KEY == VALUE")
|
|
||||||
remove_parser.set_defaults(func=handle_remove)
|
|
||||||
|
|
||||||
clean_unused_parser = subparsers.add_parser('clean-unused', help="Remove unused database entries")
|
|
||||||
clean_unused_parser.add_argument("max_age", metavar="SECONDS", type=int, help="Remove unused entries older than SECONDS old")
|
|
||||||
clean_unused_parser.set_defaults(func=handle_clean_unused)
|
|
||||||
|
|
||||||
refresh_token_parser = subparsers.add_parser('refresh-token', help="Refresh auth token")
|
|
||||||
refresh_token_parser.add_argument("--username", "-u", help="Refresh the token for another user (if authorized)")
|
|
||||||
refresh_token_parser.set_defaults(func=handle_refresh_token)
|
|
||||||
|
|
||||||
set_user_perms_parser = subparsers.add_parser('set-user-perms', help="Set new permissions for user")
|
|
||||||
set_user_perms_parser.add_argument("--username", "-u", help="Username", required=True)
|
|
||||||
set_user_perms_parser.add_argument("permissions", metavar="PERM", nargs="*", default=[], help="New permissions")
|
|
||||||
set_user_perms_parser.set_defaults(func=handle_set_user_permissions)
|
|
||||||
|
|
||||||
get_user_parser = subparsers.add_parser('get-user', help="Get user")
|
|
||||||
get_user_parser.add_argument("--username", "-u", help="Username")
|
|
||||||
get_user_parser.set_defaults(func=handle_get_user)
|
|
||||||
|
|
||||||
get_all_users_parser = subparsers.add_parser('get-all-users', help="List all users")
|
|
||||||
get_all_users_parser.set_defaults(func=handle_get_all_users)
|
|
||||||
|
|
||||||
new_user_parser = subparsers.add_parser('new-user', help="Create new user")
|
|
||||||
new_user_parser.add_argument("--username", "-u", help="Username", required=True)
|
|
||||||
new_user_parser.add_argument("permissions", metavar="PERM", nargs="*", default=[], help="New permissions")
|
|
||||||
new_user_parser.set_defaults(func=handle_new_user)
|
|
||||||
|
|
||||||
delete_user_parser = subparsers.add_parser('delete-user', help="Delete user")
|
|
||||||
delete_user_parser.add_argument("--username", "-u", help="Username", required=True)
|
|
||||||
delete_user_parser.set_defaults(func=handle_delete_user)
|
|
||||||
|
|
||||||
db_usage_parser = subparsers.add_parser('get-db-usage', help="Database Usage")
|
|
||||||
db_usage_parser.set_defaults(func=handle_get_db_usage)
|
|
||||||
|
|
||||||
db_query_columns_parser = subparsers.add_parser('get-db-query-columns', help="Show columns that can be used in database queries")
|
|
||||||
db_query_columns_parser.set_defaults(func=handle_get_db_query_columns)
|
|
||||||
|
|
||||||
gc_status_parser = subparsers.add_parser("gc-status", help="Show garbage collection status")
|
|
||||||
gc_status_parser.set_defaults(func=handle_gc_status)
|
|
||||||
|
|
||||||
gc_mark_parser = subparsers.add_parser('gc-mark', help="Mark hashes to be kept for garbage collection")
|
|
||||||
gc_mark_parser.add_argument("mark", help="Mark for this garbage collection operation")
|
|
||||||
gc_mark_parser.add_argument("--where", "-w", metavar="KEY VALUE", nargs=2, action="append", default=[],
|
|
||||||
help="Keep entries in table where KEY == VALUE")
|
|
||||||
gc_mark_parser.set_defaults(func=handle_gc_mark)
|
|
||||||
|
|
||||||
gc_sweep_parser = subparsers.add_parser('gc-sweep', help="Perform garbage collection and delete any entries that are not marked")
|
|
||||||
gc_sweep_parser.add_argument("mark", help="Mark for this garbage collection operation")
|
|
||||||
gc_sweep_parser.set_defaults(func=handle_gc_sweep)
|
|
||||||
|
|
||||||
unihash_exists_parser = subparsers.add_parser('unihash-exists', help="Check if a unihash is known to the server")
|
|
||||||
unihash_exists_parser.add_argument("--quiet", action="store_true", help="Don't print status. Instead, exit with 0 if unihash exists and 1 if it does not")
|
|
||||||
unihash_exists_parser.add_argument("unihash", help="Unihash to check")
|
|
||||||
unihash_exists_parser.set_defaults(func=handle_unihash_exists)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
logger = logging.getLogger('hashserv')
|
logger = logging.getLogger('hashserv')
|
||||||
@@ -335,30 +148,14 @@ def main():
|
|||||||
console.setLevel(level)
|
console.setLevel(level)
|
||||||
logger.addHandler(console)
|
logger.addHandler(console)
|
||||||
|
|
||||||
login = args.login
|
|
||||||
password = args.password
|
|
||||||
|
|
||||||
if login is None and args.netrc:
|
|
||||||
try:
|
|
||||||
n = netrc.netrc()
|
|
||||||
auth = n.authenticators(args.address)
|
|
||||||
if auth is not None:
|
|
||||||
login, _, password = auth
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
except netrc.NetrcParseError as e:
|
|
||||||
sys.stderr.write(f"Error parsing {e.filename}:{e.lineno}: {e.msg}\n")
|
|
||||||
|
|
||||||
func = getattr(args, 'func', None)
|
func = getattr(args, 'func', None)
|
||||||
if func:
|
if func:
|
||||||
try:
|
client = hashserv.create_client(args.address)
|
||||||
with hashserv.create_client(args.address, login, password) as client:
|
# Try to establish a connection to the server now to detect failures
|
||||||
if args.become:
|
# early
|
||||||
client.become_user(args.become)
|
client.connect()
|
||||||
return func(args, client)
|
|
||||||
except bb.asyncrpc.InvokeError as e:
|
return func(args, client)
|
||||||
print(f"ERROR: {e}")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -10,162 +10,53 @@ import sys
|
|||||||
import logging
|
import logging
|
||||||
import argparse
|
import argparse
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.simplefilter("default")
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib'))
|
||||||
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib"))
|
|
||||||
|
|
||||||
import hashserv
|
import hashserv
|
||||||
from hashserv.server import DEFAULT_ANON_PERMS
|
|
||||||
|
|
||||||
VERSION = "1.0.0"
|
VERSION = "1.0.0"
|
||||||
|
|
||||||
DEFAULT_BIND = "unix://./hashserve.sock"
|
DEFAULT_BIND = 'unix://./hashserve.sock'
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(description='Hash Equivalence Reference Server. Version=%s' % VERSION,
|
||||||
description="Hash Equivalence Reference Server. Version=%s" % VERSION,
|
epilog='''The bind address is the path to a unix domain socket if it is
|
||||||
formatter_class=argparse.RawTextHelpFormatter,
|
prefixed with "unix://". Otherwise, it is an IP address
|
||||||
epilog="""
|
and port in form ADDRESS:PORT. To bind to all addresses, leave
|
||||||
The bind address may take one of the following formats:
|
the ADDRESS empty, e.g. "--bind :8686". To bind to a specific
|
||||||
unix://PATH - Bind to unix domain socket at PATH
|
IPv6 address, enclose the address in "[]", e.g.
|
||||||
ws://ADDRESS:PORT - Bind to websocket on ADDRESS:PORT
|
"--bind [::1]:8686"'''
|
||||||
ADDRESS:PORT - Bind to raw TCP socket on ADDRESS:PORT
|
)
|
||||||
|
|
||||||
To bind to all addresses, leave the ADDRESS empty, e.g. "--bind :8686" or
|
parser.add_argument('--bind', default=DEFAULT_BIND, help='Bind address (default "%(default)s")')
|
||||||
"--bind ws://:8686". To bind to a specific IPv6 address, enclose the address in
|
parser.add_argument('--database', default='./hashserv.db', help='Database file (default "%(default)s")')
|
||||||
"[]", e.g. "--bind [::1]:8686" or "--bind ws://[::1]:8686"
|
parser.add_argument('--log', default='WARNING', help='Set logging level')
|
||||||
|
|
||||||
Note that the default Anonymous permissions are designed to not break existing
|
|
||||||
server instances when upgrading, but are not particularly secure defaults. If
|
|
||||||
you want to use authentication, it is recommended that you use "--anon-perms
|
|
||||||
@read" to only give anonymous users read access, or "--anon-perms @none" to
|
|
||||||
give un-authenticated users no access at all.
|
|
||||||
|
|
||||||
Setting "--anon-perms @all" or "--anon-perms @user-admin" is not allowed, since
|
|
||||||
this would allow anonymous users to manage all users accounts, which is a bad
|
|
||||||
idea.
|
|
||||||
|
|
||||||
If you are using user authentication, you should run your server in websockets
|
|
||||||
mode with an SSL terminating load balancer in front of it (as this server does
|
|
||||||
not implement SSL). Otherwise all usernames and passwords will be transmitted
|
|
||||||
in the clear. When configured this way, clients can connect using a secure
|
|
||||||
websocket, as in "wss://SERVER:PORT"
|
|
||||||
|
|
||||||
The following permissions are supported by the server:
|
|
||||||
|
|
||||||
@none - No permissions
|
|
||||||
@read - The ability to read equivalent hashes from the server
|
|
||||||
@report - The ability to report equivalent hashes to the server
|
|
||||||
@db-admin - Manage the hash database(s). This includes cleaning the
|
|
||||||
database, removing hashes, etc.
|
|
||||||
@user-admin - The ability to manage user accounts. This includes, creating
|
|
||||||
users, deleting users, resetting login tokens, and assigning
|
|
||||||
permissions.
|
|
||||||
@all - All possible permissions, including any that may be added
|
|
||||||
in the future
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"-b",
|
|
||||||
"--bind",
|
|
||||||
default=os.environ.get("HASHSERVER_BIND", DEFAULT_BIND),
|
|
||||||
help='Bind address (default $HASHSERVER_BIND, "%(default)s")',
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-d",
|
|
||||||
"--database",
|
|
||||||
default=os.environ.get("HASHSERVER_DB", "./hashserv.db"),
|
|
||||||
help='Database file (default $HASHSERVER_DB, "%(default)s")',
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-l",
|
|
||||||
"--log",
|
|
||||||
default=os.environ.get("HASHSERVER_LOG_LEVEL", "WARNING"),
|
|
||||||
help='Set logging level (default $HASHSERVER_LOG_LEVEL, "%(default)s")',
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-u",
|
|
||||||
"--upstream",
|
|
||||||
default=os.environ.get("HASHSERVER_UPSTREAM", None),
|
|
||||||
help="Upstream hashserv to pull hashes from ($HASHSERVER_UPSTREAM)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-r",
|
|
||||||
"--read-only",
|
|
||||||
action="store_true",
|
|
||||||
help="Disallow write operations from clients ($HASHSERVER_READ_ONLY)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--db-username",
|
|
||||||
default=os.environ.get("HASHSERVER_DB_USERNAME", None),
|
|
||||||
help="Database username ($HASHSERVER_DB_USERNAME)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--db-password",
|
|
||||||
default=os.environ.get("HASHSERVER_DB_PASSWORD", None),
|
|
||||||
help="Database password ($HASHSERVER_DB_PASSWORD)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--anon-perms",
|
|
||||||
metavar="PERM[,PERM[,...]]",
|
|
||||||
default=os.environ.get("HASHSERVER_ANON_PERMS", ",".join(DEFAULT_ANON_PERMS)),
|
|
||||||
help='Permissions to give anonymous users (default $HASHSERVER_ANON_PERMS, "%(default)s")',
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--admin-user",
|
|
||||||
default=os.environ.get("HASHSERVER_ADMIN_USER", None),
|
|
||||||
help="Create default admin user with name ADMIN_USER ($HASHSERVER_ADMIN_USER)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--admin-password",
|
|
||||||
default=os.environ.get("HASHSERVER_ADMIN_PASSWORD", None),
|
|
||||||
help="Create default admin user with password ADMIN_PASSWORD ($HASHSERVER_ADMIN_PASSWORD)",
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
logger = logging.getLogger("hashserv")
|
logger = logging.getLogger('hashserv')
|
||||||
|
|
||||||
level = getattr(logging, args.log.upper(), None)
|
level = getattr(logging, args.log.upper(), None)
|
||||||
if not isinstance(level, int):
|
if not isinstance(level, int):
|
||||||
raise ValueError("Invalid log level: %s (Try ERROR/WARNING/INFO/DEBUG)" % args.log)
|
raise ValueError('Invalid log level: %s' % args.log)
|
||||||
|
|
||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
console = logging.StreamHandler()
|
console = logging.StreamHandler()
|
||||||
console.setLevel(level)
|
console.setLevel(level)
|
||||||
logger.addHandler(console)
|
logger.addHandler(console)
|
||||||
|
|
||||||
read_only = (os.environ.get("HASHSERVER_READ_ONLY", "0") == "1") or args.read_only
|
server = hashserv.create_server(args.bind, args.database)
|
||||||
if "," in args.anon_perms:
|
|
||||||
anon_perms = args.anon_perms.split(",")
|
|
||||||
else:
|
|
||||||
anon_perms = args.anon_perms.split()
|
|
||||||
|
|
||||||
server = hashserv.create_server(
|
|
||||||
args.bind,
|
|
||||||
args.database,
|
|
||||||
upstream=args.upstream,
|
|
||||||
read_only=read_only,
|
|
||||||
db_username=args.db_username,
|
|
||||||
db_password=args.db_password,
|
|
||||||
anon_perms=anon_perms,
|
|
||||||
admin_username=args.admin_user,
|
|
||||||
admin_password=args.admin_password,
|
|
||||||
)
|
|
||||||
server.serve_forever()
|
server.serve_forever()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
try:
|
try:
|
||||||
ret = main()
|
ret = main()
|
||||||
except Exception:
|
except Exception:
|
||||||
ret = 1
|
ret = 1
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit(ret)
|
sys.exit(ret)
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import warnings
|
|
||||||
warnings.simplefilter("default")
|
|
||||||
|
|
||||||
bindir = os.path.dirname(__file__)
|
bindir = os.path.dirname(__file__)
|
||||||
topdir = os.path.dirname(bindir)
|
topdir = os.path.dirname(bindir)
|
||||||
@@ -68,11 +66,11 @@ def main():
|
|||||||
|
|
||||||
registered = False
|
registered = False
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
if hasattr(plugin, 'tinfoil_init'):
|
|
||||||
plugin.tinfoil_init(tinfoil)
|
|
||||||
if hasattr(plugin, 'register_commands'):
|
if hasattr(plugin, 'register_commands'):
|
||||||
registered = True
|
registered = True
|
||||||
plugin.register_commands(subparsers)
|
plugin.register_commands(subparsers)
|
||||||
|
if hasattr(plugin, 'tinfoil_init'):
|
||||||
|
plugin.tinfoil_init(tinfoil)
|
||||||
|
|
||||||
if not registered:
|
if not registered:
|
||||||
logger.error("No commands registered - missing plugins?")
|
logger.error("No commands registered - missing plugins?")
|
||||||
|
|||||||
@@ -1,83 +1,49 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys,logging
|
import sys,logging
|
||||||
import argparse
|
import optparse
|
||||||
import warnings
|
|
||||||
warnings.simplefilter("default")
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib"))
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)),'lib'))
|
||||||
|
|
||||||
import prserv
|
import prserv
|
||||||
import prserv.serv
|
import prserv.serv
|
||||||
|
|
||||||
VERSION = "1.1.0"
|
__version__="1.0.0"
|
||||||
|
|
||||||
PRHOST_DEFAULT="0.0.0.0"
|
PRHOST_DEFAULT='0.0.0.0'
|
||||||
PRPORT_DEFAULT=8585
|
PRPORT_DEFAULT=8585
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
parser = optparse.OptionParser(
|
||||||
description="BitBake PR Server. Version=%s" % VERSION,
|
version="Bitbake PR Service Core version %s, %%prog version %s" % (prserv.__version__, __version__),
|
||||||
formatter_class=argparse.RawTextHelpFormatter)
|
usage = "%prog < --start | --stop > [options]")
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_option("-f", "--file", help="database filename(default: prserv.sqlite3)", action="store",
|
||||||
"-f",
|
dest="dbfile", type="string", default="prserv.sqlite3")
|
||||||
"--file",
|
parser.add_option("-l", "--log", help="log filename(default: prserv.log)", action="store",
|
||||||
default="prserv.sqlite3",
|
dest="logfile", type="string", default="prserv.log")
|
||||||
help="database filename (default: prserv.sqlite3)",
|
parser.add_option("--loglevel", help="logging level, i.e. CRITICAL, ERROR, WARNING, INFO, DEBUG",
|
||||||
)
|
action = "store", type="string", dest="loglevel", default = "INFO")
|
||||||
parser.add_argument(
|
parser.add_option("--start", help="start daemon",
|
||||||
"-l",
|
action="store_true", dest="start")
|
||||||
"--log",
|
parser.add_option("--stop", help="stop daemon",
|
||||||
default="prserv.log",
|
action="store_true", dest="stop")
|
||||||
help="log filename(default: prserv.log)",
|
parser.add_option("--host", help="ip address to bind", action="store",
|
||||||
)
|
dest="host", type="string", default=PRHOST_DEFAULT)
|
||||||
parser.add_argument(
|
parser.add_option("--port", help="port number(default: 8585)", action="store",
|
||||||
"--loglevel",
|
dest="port", type="int", default=PRPORT_DEFAULT)
|
||||||
default="INFO",
|
|
||||||
help="logging level, i.e. CRITICAL, ERROR, WARNING, INFO, DEBUG",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--start",
|
|
||||||
action="store_true",
|
|
||||||
help="start daemon",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--stop",
|
|
||||||
action="store_true",
|
|
||||||
help="stop daemon",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--host",
|
|
||||||
help="ip address to bind",
|
|
||||||
default=PRHOST_DEFAULT,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--port",
|
|
||||||
type=int,
|
|
||||||
default=PRPORT_DEFAULT,
|
|
||||||
help="port number (default: 8585)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-r",
|
|
||||||
"--read-only",
|
|
||||||
action="store_true",
|
|
||||||
help="open database in read-only mode",
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
options, args = parser.parse_args(sys.argv)
|
||||||
prserv.init_logger(os.path.abspath(args.log), args.loglevel)
|
prserv.init_logger(os.path.abspath(options.logfile),options.loglevel)
|
||||||
|
|
||||||
if args.start:
|
if options.start:
|
||||||
ret=prserv.serv.start_daemon(args.file, args.host, args.port, os.path.abspath(args.log), args.read_only)
|
ret=prserv.serv.start_daemon(options.dbfile, options.host, options.port,os.path.abspath(options.logfile))
|
||||||
elif args.stop:
|
elif options.stop:
|
||||||
ret=prserv.serv.stop_daemon(args.host, args.port)
|
ret=prserv.serv.stop_daemon(options.host, options.port)
|
||||||
else:
|
else:
|
||||||
ret=parser.print_help()
|
ret=parser.print_help()
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@@ -7,8 +7,6 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys, logging
|
import sys, logging
|
||||||
import warnings
|
|
||||||
warnings.simplefilter("default")
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib'))
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib'))
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
@@ -31,7 +29,6 @@ tests = ["bb.tests.codeparser",
|
|||||||
"bb.tests.runqueue",
|
"bb.tests.runqueue",
|
||||||
"bb.tests.siggen",
|
"bb.tests.siggen",
|
||||||
"bb.tests.utils",
|
"bb.tests.utils",
|
||||||
"bb.tests.compression",
|
|
||||||
"hashserv.tests",
|
"hashserv.tests",
|
||||||
"layerindexlib.tests.layerindexobj",
|
"layerindexlib.tests.layerindexobj",
|
||||||
"layerindexlib.tests.restapi",
|
"layerindexlib.tests.restapi",
|
||||||
|
|||||||
@@ -8,16 +8,14 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
warnings.simplefilter("default")
|
|
||||||
import logging
|
import logging
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib'))
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib'))
|
||||||
|
|
||||||
import bb
|
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.")
|
||||||
bb.utils.check_system_locale()
|
|
||||||
|
|
||||||
# Users shouldn't be running this code directly
|
# Users shouldn't be running this code directly
|
||||||
if len(sys.argv) != 11 or not sys.argv[1].startswith("decafbad"):
|
if len(sys.argv) != 10 or not sys.argv[1].startswith("decafbad"):
|
||||||
print("bitbake-server is meant for internal execution by bitbake itself, please don't use it standalone.")
|
print("bitbake-server is meant for internal execution by bitbake itself, please don't use it standalone.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@@ -28,11 +26,12 @@ readypipeinfd = int(sys.argv[3])
|
|||||||
logfile = sys.argv[4]
|
logfile = sys.argv[4]
|
||||||
lockname = sys.argv[5]
|
lockname = sys.argv[5]
|
||||||
sockname = sys.argv[6]
|
sockname = sys.argv[6]
|
||||||
timeout = float(sys.argv[7])
|
timeout = sys.argv[7]
|
||||||
profile = bool(int(sys.argv[8]))
|
xmlrpcinterface = (sys.argv[8], int(sys.argv[9]))
|
||||||
xmlrpcinterface = (sys.argv[9], int(sys.argv[10]))
|
|
||||||
if xmlrpcinterface[0] == "None":
|
if xmlrpcinterface[0] == "None":
|
||||||
xmlrpcinterface = (None, xmlrpcinterface[1])
|
xmlrpcinterface = (None, xmlrpcinterface[1])
|
||||||
|
if timeout == "None":
|
||||||
|
timeout = None
|
||||||
|
|
||||||
# Replace standard fds with our own
|
# Replace standard fds with our own
|
||||||
with open('/dev/null', 'r') as si:
|
with open('/dev/null', 'r') as si:
|
||||||
@@ -51,5 +50,5 @@ logger = logging.getLogger("BitBake")
|
|||||||
handler = bb.event.LogHandler()
|
handler = bb.event.LogHandler()
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
|
|
||||||
bb.server.process.execServer(lockfd, readypipeinfd, lockname, sockname, timeout, xmlrpcinterface, profile)
|
bb.server.process.execServer(lockfd, readypipeinfd, lockname, sockname, timeout, xmlrpcinterface)
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
warnings.simplefilter("default")
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib'))
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib'))
|
||||||
from bb import fetch2
|
from bb import fetch2
|
||||||
import logging
|
import logging
|
||||||
@@ -19,12 +16,11 @@ import signal
|
|||||||
import pickle
|
import pickle
|
||||||
import traceback
|
import traceback
|
||||||
import queue
|
import queue
|
||||||
import shlex
|
|
||||||
import subprocess
|
|
||||||
from multiprocessing import Lock
|
from multiprocessing import Lock
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
bb.utils.check_system_locale()
|
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.")
|
||||||
|
|
||||||
# Users shouldn't be running this code directly
|
# Users shouldn't be running this code directly
|
||||||
if len(sys.argv) != 2 or not sys.argv[1].startswith("decafbad"):
|
if len(sys.argv) != 2 or not sys.argv[1].startswith("decafbad"):
|
||||||
@@ -91,19 +87,19 @@ def worker_fire_prepickled(event):
|
|||||||
worker_thread_exit = False
|
worker_thread_exit = False
|
||||||
|
|
||||||
def worker_flush(worker_queue):
|
def worker_flush(worker_queue):
|
||||||
worker_queue_int = bytearray()
|
worker_queue_int = b""
|
||||||
global worker_pipe, worker_thread_exit
|
global worker_pipe, worker_thread_exit
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
worker_queue_int.extend(worker_queue.get(True, 1))
|
worker_queue_int = worker_queue_int + worker_queue.get(True, 1)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
while (worker_queue_int or not worker_queue.empty()):
|
while (worker_queue_int or not worker_queue.empty()):
|
||||||
try:
|
try:
|
||||||
(_, ready, _) = select.select([], [worker_pipe], [], 1)
|
(_, ready, _) = select.select([], [worker_pipe], [], 1)
|
||||||
if not worker_queue.empty():
|
if not worker_queue.empty():
|
||||||
worker_queue_int.extend(worker_queue.get())
|
worker_queue_int = worker_queue_int + worker_queue.get()
|
||||||
written = os.write(worker_pipe, worker_queue_int)
|
written = os.write(worker_pipe, worker_queue_int)
|
||||||
worker_queue_int = worker_queue_int[written:]
|
worker_queue_int = worker_queue_int[written:]
|
||||||
except (IOError, OSError) as e:
|
except (IOError, OSError) as e:
|
||||||
@@ -121,10 +117,9 @@ def worker_child_fire(event, d):
|
|||||||
|
|
||||||
data = b"<event>" + pickle.dumps(event) + b"</event>"
|
data = b"<event>" + pickle.dumps(event) + b"</event>"
|
||||||
try:
|
try:
|
||||||
with bb.utils.lock_timeout(worker_pipe_lock):
|
worker_pipe_lock.acquire()
|
||||||
while(len(data)):
|
worker_pipe.write(data)
|
||||||
written = worker_pipe.write(data)
|
worker_pipe_lock.release()
|
||||||
data = data[written:]
|
|
||||||
except IOError:
|
except IOError:
|
||||||
sigterm_handler(None, None)
|
sigterm_handler(None, None)
|
||||||
raise
|
raise
|
||||||
@@ -143,59 +138,40 @@ def sigterm_handler(signum, frame):
|
|||||||
os.killpg(0, signal.SIGTERM)
|
os.killpg(0, signal.SIGTERM)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
def fork_off_task(cfg, data, databuilder, workerdata, extraconfigdata, runtask):
|
def fork_off_task(cfg, data, databuilder, workerdata, fn, task, taskname, taskhash, unihash, appends, taskdepdata, extraconfigdata, quieterrors=False, dry_run_exec=False):
|
||||||
|
|
||||||
fn = runtask['fn']
|
|
||||||
task = runtask['task']
|
|
||||||
taskname = runtask['taskname']
|
|
||||||
taskhash = runtask['taskhash']
|
|
||||||
unihash = runtask['unihash']
|
|
||||||
appends = runtask['appends']
|
|
||||||
layername = runtask['layername']
|
|
||||||
taskdepdata = runtask['taskdepdata']
|
|
||||||
quieterrors = runtask['quieterrors']
|
|
||||||
# We need to setup the environment BEFORE the fork, since
|
# We need to setup the environment BEFORE the fork, since
|
||||||
# a fork() or exec*() activates PSEUDO...
|
# a fork() or exec*() activates PSEUDO...
|
||||||
|
|
||||||
envbackup = {}
|
envbackup = {}
|
||||||
fakeroot = False
|
|
||||||
fakeenv = {}
|
fakeenv = {}
|
||||||
umask = None
|
umask = None
|
||||||
|
|
||||||
uid = os.getuid()
|
taskdep = workerdata["taskdeps"][fn]
|
||||||
gid = os.getgid()
|
|
||||||
|
|
||||||
taskdep = runtask['taskdep']
|
|
||||||
if 'umask' in taskdep and taskname in taskdep['umask']:
|
if 'umask' in taskdep and taskname in taskdep['umask']:
|
||||||
umask = taskdep['umask'][taskname]
|
|
||||||
elif workerdata["umask"]:
|
|
||||||
umask = workerdata["umask"]
|
|
||||||
if umask:
|
|
||||||
# umask might come in as a number or text string..
|
# umask might come in as a number or text string..
|
||||||
try:
|
try:
|
||||||
umask = int(umask, 8)
|
umask = int(taskdep['umask'][taskname],8)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
umask = taskdep['umask'][taskname]
|
||||||
|
|
||||||
dry_run = cfg.dry_run or runtask['dry_run']
|
dry_run = cfg.dry_run or dry_run_exec
|
||||||
|
|
||||||
# We can't use the fakeroot environment in a dry run as it possibly hasn't been built
|
# We can't use the fakeroot environment in a dry run as it possibly hasn't been built
|
||||||
if 'fakeroot' in taskdep and taskname in taskdep['fakeroot'] and not dry_run:
|
if 'fakeroot' in taskdep and taskname in taskdep['fakeroot'] and not dry_run:
|
||||||
fakeroot = True
|
envvars = (workerdata["fakerootenv"][fn] or "").split()
|
||||||
envvars = (runtask['fakerootenv'] or "").split()
|
for key, value in (var.split('=') for var in envvars):
|
||||||
for key, value in (var.split('=',1) for var in envvars):
|
|
||||||
envbackup[key] = os.environ.get(key)
|
envbackup[key] = os.environ.get(key)
|
||||||
os.environ[key] = value
|
os.environ[key] = value
|
||||||
fakeenv[key] = value
|
fakeenv[key] = value
|
||||||
|
|
||||||
fakedirs = (runtask['fakerootdirs'] or "").split()
|
fakedirs = (workerdata["fakerootdirs"][fn] or "").split()
|
||||||
for p in fakedirs:
|
for p in fakedirs:
|
||||||
bb.utils.mkdirhier(p)
|
bb.utils.mkdirhier(p)
|
||||||
logger.debug2('Running %s:%s under fakeroot, fakedirs: %s' %
|
logger.debug(2, 'Running %s:%s under fakeroot, fakedirs: %s' %
|
||||||
(fn, taskname, ', '.join(fakedirs)))
|
(fn, taskname, ', '.join(fakedirs)))
|
||||||
else:
|
else:
|
||||||
envvars = (runtask['fakerootnoenv'] or "").split()
|
envvars = (workerdata["fakerootnoenv"][fn] or "").split()
|
||||||
for key, value in (var.split('=',1) for var in envvars):
|
for key, value in (var.split('=') for var in envvars):
|
||||||
envbackup[key] = os.environ.get(key)
|
envbackup[key] = os.environ.get(key)
|
||||||
os.environ[key] = value
|
os.environ[key] = value
|
||||||
fakeenv[key] = value
|
fakeenv[key] = value
|
||||||
@@ -237,21 +213,19 @@ def fork_off_task(cfg, data, databuilder, workerdata, extraconfigdata, runtask):
|
|||||||
# Let SIGHUP exit as SIGTERM
|
# Let SIGHUP exit as SIGTERM
|
||||||
signal.signal(signal.SIGHUP, sigterm_handler)
|
signal.signal(signal.SIGHUP, sigterm_handler)
|
||||||
|
|
||||||
# No stdin & stdout
|
# No stdin
|
||||||
# stdout is used as a status report channel and must not be used by child processes.
|
newsi = os.open(os.devnull, os.O_RDWR)
|
||||||
dumbio = os.open(os.devnull, os.O_RDWR)
|
os.dup2(newsi, sys.stdin.fileno())
|
||||||
os.dup2(dumbio, sys.stdin.fileno())
|
|
||||||
os.dup2(dumbio, sys.stdout.fileno())
|
|
||||||
|
|
||||||
if umask is not None:
|
if umask:
|
||||||
os.umask(umask)
|
os.umask(umask)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
bb_cache = bb.cache.NoCache(databuilder)
|
||||||
(realfn, virtual, mc) = bb.cache.virtualfn2realfn(fn)
|
(realfn, virtual, mc) = bb.cache.virtualfn2realfn(fn)
|
||||||
the_data = databuilder.mcdata[mc]
|
the_data = databuilder.mcdata[mc]
|
||||||
the_data.setVar("BB_WORKERCONTEXT", "1")
|
the_data.setVar("BB_WORKERCONTEXT", "1")
|
||||||
the_data.setVar("BB_TASKDEPDATA", taskdepdata)
|
the_data.setVar("BB_TASKDEPDATA", taskdepdata)
|
||||||
the_data.setVar('BB_CURRENTTASK', taskname.replace("do_", ""))
|
|
||||||
if cfg.limited_deps:
|
if cfg.limited_deps:
|
||||||
the_data.setVar("BB_LIMITEDDEPS", "1")
|
the_data.setVar("BB_LIMITEDDEPS", "1")
|
||||||
the_data.setVar("BUILDNAME", workerdata["buildname"])
|
the_data.setVar("BUILDNAME", workerdata["buildname"])
|
||||||
@@ -265,20 +239,12 @@ def fork_off_task(cfg, data, databuilder, workerdata, extraconfigdata, runtask):
|
|||||||
bb.parse.siggen.set_taskhashes(workerdata["newhashes"])
|
bb.parse.siggen.set_taskhashes(workerdata["newhashes"])
|
||||||
ret = 0
|
ret = 0
|
||||||
|
|
||||||
the_data = databuilder.parseRecipe(fn, appends, layername)
|
the_data = bb_cache.loadDataFull(fn, appends)
|
||||||
the_data.setVar('BB_TASKHASH', taskhash)
|
the_data.setVar('BB_TASKHASH', taskhash)
|
||||||
the_data.setVar('BB_UNIHASH', unihash)
|
the_data.setVar('BB_UNIHASH', unihash)
|
||||||
bb.parse.siggen.setup_datacache_from_datastore(fn, the_data)
|
|
||||||
|
|
||||||
bb.utils.set_process_name("%s:%s" % (the_data.getVar("PN"), taskname.replace("do_", "")))
|
bb.utils.set_process_name("%s:%s" % (the_data.getVar("PN"), taskname.replace("do_", "")))
|
||||||
|
|
||||||
if not bb.utils.to_boolean(the_data.getVarFlag(taskname, 'network')):
|
|
||||||
if bb.utils.is_local_uid(uid):
|
|
||||||
logger.debug("Attempting to disable network for %s" % taskname)
|
|
||||||
bb.utils.disable_network(uid, gid)
|
|
||||||
else:
|
|
||||||
logger.debug("Skipping disable network for %s since %s is not a local uid." % (taskname, uid))
|
|
||||||
|
|
||||||
# exported_vars() returns a generator which *cannot* be passed to os.environ.update()
|
# exported_vars() returns a generator which *cannot* be passed to os.environ.update()
|
||||||
# successfully. We also need to unset anything from the environment which shouldn't be there
|
# successfully. We also need to unset anything from the environment which shouldn't be there
|
||||||
exports = bb.data.exported_vars(the_data)
|
exports = bb.data.exported_vars(the_data)
|
||||||
@@ -307,20 +273,10 @@ def fork_off_task(cfg, data, databuilder, workerdata, extraconfigdata, runtask):
|
|||||||
if not quieterrors:
|
if not quieterrors:
|
||||||
logger.critical(traceback.format_exc())
|
logger.critical(traceback.format_exc())
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
|
|
||||||
sys.stdout.flush()
|
|
||||||
sys.stderr.flush()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if dry_run:
|
if dry_run:
|
||||||
return 0
|
return 0
|
||||||
try:
|
return bb.build.exec_task(fn, taskname, the_data, cfg.profile)
|
||||||
ret = bb.build.exec_task(fn, taskname, the_data, cfg.profile)
|
|
||||||
finally:
|
|
||||||
if fakeroot:
|
|
||||||
fakerootcmd = shlex.split(the_data.getVar("FAKEROOTCMD"))
|
|
||||||
subprocess.run(fakerootcmd + ['-S'], check=True, stdout=subprocess.PIPE)
|
|
||||||
return ret
|
|
||||||
except:
|
except:
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
if not profiling:
|
if not profiling:
|
||||||
@@ -352,12 +308,12 @@ class runQueueWorkerPipe():
|
|||||||
if pipeout:
|
if pipeout:
|
||||||
pipeout.close()
|
pipeout.close()
|
||||||
bb.utils.nonblockingfd(self.input)
|
bb.utils.nonblockingfd(self.input)
|
||||||
self.queue = bytearray()
|
self.queue = b""
|
||||||
|
|
||||||
def read(self):
|
def read(self):
|
||||||
start = len(self.queue)
|
start = len(self.queue)
|
||||||
try:
|
try:
|
||||||
self.queue.extend(self.input.read(102400) or b"")
|
self.queue = self.queue + (self.input.read(102400) or b"")
|
||||||
except (OSError, IOError) as e:
|
except (OSError, IOError) as e:
|
||||||
if e.errno != errno.EAGAIN:
|
if e.errno != errno.EAGAIN:
|
||||||
raise
|
raise
|
||||||
@@ -365,9 +321,7 @@ class runQueueWorkerPipe():
|
|||||||
end = len(self.queue)
|
end = len(self.queue)
|
||||||
index = self.queue.find(b"</event>")
|
index = self.queue.find(b"</event>")
|
||||||
while index != -1:
|
while index != -1:
|
||||||
msg = self.queue[:index+8]
|
worker_fire_prepickled(self.queue[:index+8])
|
||||||
assert msg.startswith(b"<event>") and msg.count(b"<event>") == 1
|
|
||||||
worker_fire_prepickled(msg)
|
|
||||||
self.queue = self.queue[index+8:]
|
self.queue = self.queue[index+8:]
|
||||||
index = self.queue.find(b"</event>")
|
index = self.queue.find(b"</event>")
|
||||||
return (end > start)
|
return (end > start)
|
||||||
@@ -385,7 +339,7 @@ class BitbakeWorker(object):
|
|||||||
def __init__(self, din):
|
def __init__(self, din):
|
||||||
self.input = din
|
self.input = din
|
||||||
bb.utils.nonblockingfd(self.input)
|
bb.utils.nonblockingfd(self.input)
|
||||||
self.queue = bytearray()
|
self.queue = b""
|
||||||
self.cookercfg = None
|
self.cookercfg = None
|
||||||
self.databuilder = None
|
self.databuilder = None
|
||||||
self.data = None
|
self.data = None
|
||||||
@@ -419,7 +373,7 @@ class BitbakeWorker(object):
|
|||||||
if len(r) == 0:
|
if len(r) == 0:
|
||||||
# EOF on pipe, server must have terminated
|
# EOF on pipe, server must have terminated
|
||||||
self.sigterm_exception(signal.SIGTERM, None)
|
self.sigterm_exception(signal.SIGTERM, None)
|
||||||
self.queue.extend(r)
|
self.queue = self.queue + r
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
pass
|
pass
|
||||||
if len(self.queue):
|
if len(self.queue):
|
||||||
@@ -439,35 +393,19 @@ class BitbakeWorker(object):
|
|||||||
while self.process_waitpid():
|
while self.process_waitpid():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
def handle_item(self, item, func):
|
def handle_item(self, item, func):
|
||||||
opening_tag = b"<" + item + b">"
|
if self.queue.startswith(b"<" + item + b">"):
|
||||||
if not self.queue.startswith(opening_tag):
|
index = self.queue.find(b"</" + item + b">")
|
||||||
return
|
while index != -1:
|
||||||
|
func(self.queue[(len(item) + 2):index])
|
||||||
tag_len = len(opening_tag)
|
self.queue = self.queue[(index + len(item) + 3):]
|
||||||
if len(self.queue) < tag_len + 4:
|
index = self.queue.find(b"</" + item + b">")
|
||||||
# we need to receive more data
|
|
||||||
return
|
|
||||||
header = self.queue[tag_len:tag_len + 4]
|
|
||||||
payload_len = int.from_bytes(header, 'big')
|
|
||||||
# closing tag has length (tag_len + 1)
|
|
||||||
if len(self.queue) < tag_len * 2 + 1 + payload_len:
|
|
||||||
# we need to receive more data
|
|
||||||
return
|
|
||||||
|
|
||||||
index = self.queue.find(b"</" + item + b">")
|
|
||||||
if index != -1:
|
|
||||||
try:
|
|
||||||
func(self.queue[(tag_len + 4):index])
|
|
||||||
except pickle.UnpicklingError:
|
|
||||||
workerlog_write("Unable to unpickle data: %s\n" % ":".join("{:02x}".format(c) for c in self.queue))
|
|
||||||
raise
|
|
||||||
self.queue = self.queue[(index + len(b"</") + len(item) + len(b">")):]
|
|
||||||
|
|
||||||
def handle_cookercfg(self, data):
|
def handle_cookercfg(self, data):
|
||||||
self.cookercfg = pickle.loads(data)
|
self.cookercfg = pickle.loads(data)
|
||||||
self.databuilder = bb.cookerdata.CookerDataBuilder(self.cookercfg, worker=True)
|
self.databuilder = bb.cookerdata.CookerDataBuilder(self.cookercfg, worker=True)
|
||||||
self.databuilder.parseBaseConfiguration(worker=True)
|
self.databuilder.parseBaseConfiguration()
|
||||||
self.data = self.databuilder.data
|
self.data = self.databuilder.data
|
||||||
|
|
||||||
def handle_extraconfigdata(self, data):
|
def handle_extraconfigdata(self, data):
|
||||||
@@ -482,7 +420,6 @@ class BitbakeWorker(object):
|
|||||||
for mc in self.databuilder.mcdata:
|
for mc in self.databuilder.mcdata:
|
||||||
self.databuilder.mcdata[mc].setVar("PRSERV_HOST", self.workerdata["prhost"])
|
self.databuilder.mcdata[mc].setVar("PRSERV_HOST", self.workerdata["prhost"])
|
||||||
self.databuilder.mcdata[mc].setVar("BB_HASHSERVE", self.workerdata["hashservaddr"])
|
self.databuilder.mcdata[mc].setVar("BB_HASHSERVE", self.workerdata["hashservaddr"])
|
||||||
self.databuilder.mcdata[mc].setVar("__bbclasstype", "recipe")
|
|
||||||
|
|
||||||
def handle_newtaskhashes(self, data):
|
def handle_newtaskhashes(self, data):
|
||||||
self.workerdata["newhashes"] = pickle.loads(data)
|
self.workerdata["newhashes"] = pickle.loads(data)
|
||||||
@@ -500,15 +437,11 @@ class BitbakeWorker(object):
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def handle_runtask(self, data):
|
def handle_runtask(self, data):
|
||||||
runtask = pickle.loads(data)
|
fn, task, taskname, taskhash, unihash, quieterrors, appends, taskdepdata, dry_run_exec = pickle.loads(data)
|
||||||
|
|
||||||
fn = runtask['fn']
|
|
||||||
task = runtask['task']
|
|
||||||
taskname = runtask['taskname']
|
|
||||||
|
|
||||||
workerlog_write("Handling runtask %s %s %s\n" % (task, fn, taskname))
|
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, self.extraconfigdata, runtask)
|
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_pids[pid] = task
|
||||||
self.build_pipes[pid] = runQueueWorkerPipe(pipein, pipeout)
|
self.build_pipes[pid] = runQueueWorkerPipe(pipein, pipeout)
|
||||||
|
|
||||||
@@ -572,11 +505,9 @@ except BaseException as e:
|
|||||||
import traceback
|
import traceback
|
||||||
sys.stderr.write(traceback.format_exc())
|
sys.stderr.write(traceback.format_exc())
|
||||||
sys.stderr.write(str(e))
|
sys.stderr.write(str(e))
|
||||||
finally:
|
|
||||||
worker_thread_exit = True
|
|
||||||
worker_thread.join()
|
|
||||||
|
|
||||||
workerlog_write("exiting")
|
worker_thread_exit = True
|
||||||
if not normalexit:
|
worker_thread.join()
|
||||||
sys.exit(1)
|
|
||||||
|
workerlog_write("exitting")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
#
|
#
|
||||||
|
|
||||||
@@ -18,23 +16,19 @@ import itertools
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
|
||||||
warnings.simplefilter("default")
|
|
||||||
|
|
||||||
version = 1.0
|
version = 1.0
|
||||||
|
|
||||||
|
|
||||||
git_cmd = ['git', '-c', 'safe.bareRepository=all']
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if sys.version_info < (3, 4, 0):
|
if sys.version_info < (3, 4, 0):
|
||||||
sys.exit('Python 3.4 or greater is required')
|
sys.exit('Python 3.4 or greater is required')
|
||||||
|
|
||||||
git_dir = check_output(git_cmd + ['rev-parse', '--git-dir']).rstrip()
|
git_dir = check_output(['git', 'rev-parse', '--git-dir']).rstrip()
|
||||||
shallow_file = os.path.join(git_dir, 'shallow')
|
shallow_file = os.path.join(git_dir, 'shallow')
|
||||||
if os.path.exists(shallow_file):
|
if os.path.exists(shallow_file):
|
||||||
try:
|
try:
|
||||||
check_output(git_cmd + ['fetch', '--unshallow'])
|
check_output(['git', 'fetch', '--unshallow'])
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
try:
|
try:
|
||||||
os.unlink(shallow_file)
|
os.unlink(shallow_file)
|
||||||
@@ -43,21 +37,21 @@ def main():
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
args = process_args()
|
args = process_args()
|
||||||
revs = check_output(git_cmd + ['rev-list'] + args.revisions).splitlines()
|
revs = check_output(['git', 'rev-list'] + args.revisions).splitlines()
|
||||||
|
|
||||||
make_shallow(shallow_file, args.revisions, args.refs)
|
make_shallow(shallow_file, args.revisions, args.refs)
|
||||||
|
|
||||||
ref_revs = check_output(git_cmd + ['rev-list'] + args.refs).splitlines()
|
ref_revs = check_output(['git', 'rev-list'] + args.refs).splitlines()
|
||||||
remaining_history = set(revs) & set(ref_revs)
|
remaining_history = set(revs) & set(ref_revs)
|
||||||
for rev in remaining_history:
|
for rev in remaining_history:
|
||||||
if check_output(git_cmd + ['rev-parse', '{}^@'.format(rev)]):
|
if check_output(['git', 'rev-parse', '{}^@'.format(rev)]):
|
||||||
sys.exit('Error: %s was not made shallow' % rev)
|
sys.exit('Error: %s was not made shallow' % rev)
|
||||||
|
|
||||||
filter_refs(args.refs)
|
filter_refs(args.refs)
|
||||||
|
|
||||||
if args.shrink:
|
if args.shrink:
|
||||||
shrink_repo(git_dir)
|
shrink_repo(git_dir)
|
||||||
subprocess.check_call(git_cmd + ['fsck', '--unreachable'])
|
subprocess.check_call(['git', 'fsck', '--unreachable'])
|
||||||
|
|
||||||
|
|
||||||
def process_args():
|
def process_args():
|
||||||
@@ -74,12 +68,12 @@ def process_args():
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.refs:
|
if args.refs:
|
||||||
args.refs = check_output(git_cmd + ['rev-parse', '--symbolic-full-name'] + args.refs).splitlines()
|
args.refs = check_output(['git', 'rev-parse', '--symbolic-full-name'] + args.refs).splitlines()
|
||||||
else:
|
else:
|
||||||
args.refs = get_all_refs(lambda r, t, tt: t == 'commit' or tt == 'commit')
|
args.refs = get_all_refs(lambda r, t, tt: t == 'commit' or tt == 'commit')
|
||||||
|
|
||||||
args.refs = list(filter(lambda r: not r.endswith('/HEAD'), args.refs))
|
args.refs = list(filter(lambda r: not r.endswith('/HEAD'), args.refs))
|
||||||
args.revisions = check_output(git_cmd + ['rev-parse'] + ['%s^{}' % i for i in args.revisions]).splitlines()
|
args.revisions = check_output(['git', 'rev-parse'] + ['%s^{}' % i for i in args.revisions]).splitlines()
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
@@ -97,7 +91,7 @@ def make_shallow(shallow_file, revisions, refs):
|
|||||||
|
|
||||||
def get_all_refs(ref_filter=None):
|
def get_all_refs(ref_filter=None):
|
||||||
"""Return all the existing refs in this repository, optionally filtering the refs."""
|
"""Return all the existing refs in this repository, optionally filtering the refs."""
|
||||||
ref_output = check_output(git_cmd + ['for-each-ref', '--format=%(refname)\t%(objecttype)\t%(*objecttype)'])
|
ref_output = check_output(['git', 'for-each-ref', '--format=%(refname)\t%(objecttype)\t%(*objecttype)'])
|
||||||
ref_split = [tuple(iter_extend(l.rsplit('\t'), 3)) for l in ref_output.splitlines()]
|
ref_split = [tuple(iter_extend(l.rsplit('\t'), 3)) for l in ref_output.splitlines()]
|
||||||
if ref_filter:
|
if ref_filter:
|
||||||
ref_split = (e for e in ref_split if ref_filter(*e))
|
ref_split = (e for e in ref_split if ref_filter(*e))
|
||||||
@@ -115,7 +109,7 @@ def filter_refs(refs):
|
|||||||
all_refs = get_all_refs()
|
all_refs = get_all_refs()
|
||||||
to_remove = set(all_refs) - set(refs)
|
to_remove = set(all_refs) - set(refs)
|
||||||
if to_remove:
|
if to_remove:
|
||||||
check_output(['xargs', '-0', '-n', '1'] + git_cmd + ['update-ref', '-d', '--no-deref'],
|
check_output(['xargs', '-0', '-n', '1', 'git', 'update-ref', '-d', '--no-deref'],
|
||||||
input=''.join(l + '\0' for l in to_remove))
|
input=''.join(l + '\0' for l in to_remove))
|
||||||
|
|
||||||
|
|
||||||
@@ -128,7 +122,7 @@ def follow_history_intersections(revisions, refs):
|
|||||||
if rev in seen:
|
if rev in seen:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
parents = check_output(git_cmd + ['rev-parse', '%s^@' % rev]).splitlines()
|
parents = check_output(['git', 'rev-parse', '%s^@' % rev]).splitlines()
|
||||||
|
|
||||||
yield rev
|
yield rev
|
||||||
seen.add(rev)
|
seen.add(rev)
|
||||||
@@ -136,12 +130,12 @@ def follow_history_intersections(revisions, refs):
|
|||||||
if not parents:
|
if not parents:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
check_refs = check_output(git_cmd + ['merge-base', '--independent'] + sorted(refs)).splitlines()
|
check_refs = check_output(['git', 'merge-base', '--independent'] + sorted(refs)).splitlines()
|
||||||
for parent in parents:
|
for parent in parents:
|
||||||
for ref in check_refs:
|
for ref in check_refs:
|
||||||
print("Checking %s vs %s" % (parent, ref))
|
print("Checking %s vs %s" % (parent, ref))
|
||||||
try:
|
try:
|
||||||
merge_base = check_output(git_cmd + ['merge-base', parent, ref]).rstrip()
|
merge_base = check_output(['git', 'merge-base', parent, ref]).rstrip()
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@@ -161,14 +155,14 @@ def iter_except(func, exception, start=None):
|
|||||||
|
|
||||||
def shrink_repo(git_dir):
|
def shrink_repo(git_dir):
|
||||||
"""Shrink the newly shallow repository, removing the unreachable objects."""
|
"""Shrink the newly shallow repository, removing the unreachable objects."""
|
||||||
subprocess.check_call(git_cmd + ['reflog', 'expire', '--expire-unreachable=now', '--all'])
|
subprocess.check_call(['git', 'reflog', 'expire', '--expire-unreachable=now', '--all'])
|
||||||
subprocess.check_call(git_cmd + ['repack', '-ad'])
|
subprocess.check_call(['git', 'repack', '-ad'])
|
||||||
try:
|
try:
|
||||||
os.unlink(os.path.join(git_dir, 'objects', 'info', 'alternates'))
|
os.unlink(os.path.join(git_dir, 'objects', 'info', 'alternates'))
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
if exc.errno != errno.ENOENT:
|
if exc.errno != errno.ENOENT:
|
||||||
raise
|
raise
|
||||||
subprocess.check_call(git_cmd + ['prune', '--expire', 'now'])
|
subprocess.check_call(['git', 'prune', '--expire', 'now'])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ databaseCheck()
|
|||||||
$MANAGE migrate --noinput || retval=1
|
$MANAGE migrate --noinput || retval=1
|
||||||
|
|
||||||
if [ $retval -eq 1 ]; then
|
if [ $retval -eq 1 ]; then
|
||||||
echo "Failed migrations, halting system start" 1>&2
|
echo "Failed migrations, aborting system start" 1>&2
|
||||||
return $retval
|
return $retval
|
||||||
fi
|
fi
|
||||||
# Make sure that checksettings can pick up any value for TEMPLATECONF
|
# Make sure that checksettings can pick up any value for TEMPLATECONF
|
||||||
@@ -41,7 +41,7 @@ databaseCheck()
|
|||||||
$MANAGE checksettings --traceback || retval=1
|
$MANAGE checksettings --traceback || retval=1
|
||||||
|
|
||||||
if [ $retval -eq 1 ]; then
|
if [ $retval -eq 1 ]; then
|
||||||
printf "\nError while checking settings; exiting\n"
|
printf "\nError while checking settings; aborting\n"
|
||||||
return $retval
|
return $retval
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ webserverStartAll()
|
|||||||
echo "Starting webserver..."
|
echo "Starting webserver..."
|
||||||
|
|
||||||
$MANAGE runserver --noreload "$ADDR_PORT" \
|
$MANAGE runserver --noreload "$ADDR_PORT" \
|
||||||
</dev/null >>${TOASTER_LOGS_DIR}/web.log 2>&1 \
|
</dev/null >>${BUILDDIR}/toaster_web.log 2>&1 \
|
||||||
& echo $! >${BUILDDIR}/.toastermain.pid
|
& echo $! >${BUILDDIR}/.toastermain.pid
|
||||||
|
|
||||||
sleep 1
|
sleep 1
|
||||||
@@ -181,14 +181,6 @@ WEBSERVER=1
|
|||||||
export TOASTER_BUILDSERVER=1
|
export TOASTER_BUILDSERVER=1
|
||||||
ADDR_PORT="localhost:8000"
|
ADDR_PORT="localhost:8000"
|
||||||
TOASTERDIR=`dirname $BUILDDIR`
|
TOASTERDIR=`dirname $BUILDDIR`
|
||||||
# ${BUILDDIR}/toaster_logs/ became the default location for toaster logs
|
|
||||||
# This is needed for implemented django-log-viewer: https://pypi.org/project/django-log-viewer/
|
|
||||||
# If the directory does not exist, create it.
|
|
||||||
TOASTER_LOGS_DIR="${BUILDDIR}/toaster_logs/"
|
|
||||||
if [ ! -d $TOASTER_LOGS_DIR ]
|
|
||||||
then
|
|
||||||
mkdir $TOASTER_LOGS_DIR
|
|
||||||
fi
|
|
||||||
unset CMD
|
unset CMD
|
||||||
for param in $*; do
|
for param in $*; do
|
||||||
case $param in
|
case $param in
|
||||||
@@ -256,7 +248,7 @@ fi
|
|||||||
# 3) the sqlite db if that is being used.
|
# 3) the sqlite db if that is being used.
|
||||||
# 4) pid's we need to clean up on exit/shutdown
|
# 4) pid's we need to clean up on exit/shutdown
|
||||||
export TOASTER_DIR=$TOASTERDIR
|
export TOASTER_DIR=$TOASTERDIR
|
||||||
export BB_ENV_PASSTHROUGH_ADDITIONS="$BB_ENV_PASSTHROUGH_ADDITIONS TOASTER_DIR"
|
export BB_ENV_EXTRAWHITE="$BB_ENV_EXTRAWHITE TOASTER_DIR"
|
||||||
|
|
||||||
# Determine the action. If specified by arguments, fine, if not, toggle it
|
# Determine the action. If specified by arguments, fine, if not, toggle it
|
||||||
if [ "$CMD" = "start" ] ; then
|
if [ "$CMD" = "start" ] ; then
|
||||||
@@ -307,7 +299,7 @@ case $CMD in
|
|||||||
export BITBAKE_UI='toasterui'
|
export BITBAKE_UI='toasterui'
|
||||||
if [ $TOASTER_BUILDSERVER -eq 1 ] ; then
|
if [ $TOASTER_BUILDSERVER -eq 1 ] ; then
|
||||||
$MANAGE runbuilds \
|
$MANAGE runbuilds \
|
||||||
</dev/null >>${TOASTER_LOGS_DIR}/toaster_runbuilds.log 2>&1 \
|
</dev/null >>${BUILDDIR}/toaster_runbuilds.log 2>&1 \
|
||||||
& echo $! >${BUILDDIR}/.runbuilds.pid
|
& echo $! >${BUILDDIR}/.runbuilds.pid
|
||||||
else
|
else
|
||||||
echo "Toaster build server not started."
|
echo "Toaster build server not started."
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ import sys
|
|||||||
import json
|
import json
|
||||||
import pickle
|
import pickle
|
||||||
import codecs
|
import codecs
|
||||||
import warnings
|
|
||||||
warnings.simplefilter("default")
|
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
@@ -30,23 +28,79 @@ sys.path.insert(0, join(dirname(dirname(abspath(__file__))), 'lib'))
|
|||||||
|
|
||||||
import bb.cooker
|
import bb.cooker
|
||||||
from bb.ui import toasterui
|
from bb.ui import toasterui
|
||||||
from bb.ui import eventreplay
|
|
||||||
|
class EventPlayer:
|
||||||
|
"""Emulate a connection to a bitbake server."""
|
||||||
|
|
||||||
|
def __init__(self, eventfile, variables):
|
||||||
|
self.eventfile = eventfile
|
||||||
|
self.variables = variables
|
||||||
|
self.eventmask = []
|
||||||
|
|
||||||
|
def waitEvent(self, _timeout):
|
||||||
|
"""Read event from the file."""
|
||||||
|
line = self.eventfile.readline().strip()
|
||||||
|
if not line:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
event_str = json.loads(line)['vars'].encode('utf-8')
|
||||||
|
event = pickle.loads(codecs.decode(event_str, 'base64'))
|
||||||
|
event_name = "%s.%s" % (event.__module__, event.__class__.__name__)
|
||||||
|
if event_name not in self.eventmask:
|
||||||
|
return
|
||||||
|
return event
|
||||||
|
except ValueError as err:
|
||||||
|
print("Failed loading ", line)
|
||||||
|
raise err
|
||||||
|
|
||||||
|
def runCommand(self, command_line):
|
||||||
|
"""Emulate running a command on the server."""
|
||||||
|
name = command_line[0]
|
||||||
|
|
||||||
|
if name == "getVariable":
|
||||||
|
var_name = command_line[1]
|
||||||
|
variable = self.variables.get(var_name)
|
||||||
|
if variable:
|
||||||
|
return variable['v'], None
|
||||||
|
return None, "Missing variable %s" % var_name
|
||||||
|
|
||||||
|
elif name == "getAllKeysWithFlags":
|
||||||
|
dump = {}
|
||||||
|
flaglist = command_line[1]
|
||||||
|
for key, val in self.variables.items():
|
||||||
|
try:
|
||||||
|
if not key.startswith("__"):
|
||||||
|
dump[key] = {
|
||||||
|
'v': val['v'],
|
||||||
|
'history' : val['history'],
|
||||||
|
}
|
||||||
|
for flag in flaglist:
|
||||||
|
dump[key][flag] = val[flag]
|
||||||
|
except Exception as err:
|
||||||
|
print(err)
|
||||||
|
return (dump, None)
|
||||||
|
|
||||||
|
elif name == 'setEventMask':
|
||||||
|
self.eventmask = command_line[-1]
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception("Command %s not implemented" % command_line[0])
|
||||||
|
|
||||||
|
def getEventHandle(self):
|
||||||
|
"""
|
||||||
|
This method is called by toasterui.
|
||||||
|
The return value is passed to self.runCommand but not used there.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
with open(argv[-1]) as eventfile:
|
with open(argv[-1]) as eventfile:
|
||||||
# load variables from the first line
|
# load variables from the first line
|
||||||
variables = None
|
variables = json.loads(eventfile.readline().strip())['allvariables']
|
||||||
while line := eventfile.readline().strip():
|
|
||||||
try:
|
|
||||||
variables = json.loads(line)['allvariables']
|
|
||||||
break
|
|
||||||
except (KeyError, json.JSONDecodeError):
|
|
||||||
continue
|
|
||||||
if not variables:
|
|
||||||
sys.exit("Cannot find allvariables entry in event log file %s" % argv[-1])
|
|
||||||
eventfile.seek(0)
|
|
||||||
params = namedtuple('ConfigParams', ['observe_only'])(True)
|
params = namedtuple('ConfigParams', ['observe_only'])(True)
|
||||||
player = eventreplay.EventPlayer(eventfile, variables)
|
player = EventPlayer(eventfile, variables)
|
||||||
|
|
||||||
return toasterui.main(player, player, params)
|
return toasterui.main(player, player, params)
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
# SPDX-License-Identifier: MIT
|
|
||||||
#
|
|
||||||
# Copyright (c) 2021 Joshua Watt <JPEWhacker@gmail.com>
|
|
||||||
#
|
|
||||||
# Dockerfile to build a bitbake hash equivalence server container
|
|
||||||
#
|
|
||||||
# From the root of the bitbake repository, run:
|
|
||||||
#
|
|
||||||
# docker build -f contrib/hashserv/Dockerfile .
|
|
||||||
#
|
|
||||||
|
|
||||||
FROM alpine:3.13.1
|
|
||||||
|
|
||||||
RUN apk add --no-cache python3
|
|
||||||
|
|
||||||
COPY bin/bitbake-hashserv /opt/bbhashserv/bin/
|
|
||||||
COPY lib/hashserv /opt/bbhashserv/lib/hashserv/
|
|
||||||
COPY lib/bb /opt/bbhashserv/lib/bb/
|
|
||||||
COPY lib/codegen.py /opt/bbhashserv/lib/codegen.py
|
|
||||||
COPY lib/ply /opt/bbhashserv/lib/ply/
|
|
||||||
COPY lib/bs4 /opt/bbhashserv/lib/bs4/
|
|
||||||
|
|
||||||
ENTRYPOINT ["/opt/bbhashserv/bin/bitbake-hashserv"]
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# SPDX-License-Identifier: MIT
|
|
||||||
#
|
|
||||||
# Copyright (c) 2022 Daniel Gomez <daniel@qtec.com>
|
|
||||||
#
|
|
||||||
# Dockerfile to build a bitbake PR service container
|
|
||||||
#
|
|
||||||
# From the root of the bitbake repository, run:
|
|
||||||
#
|
|
||||||
# docker build -f contrib/prserv/Dockerfile . -t prserv
|
|
||||||
#
|
|
||||||
# Running examples:
|
|
||||||
#
|
|
||||||
# 1. PR Service in RW mode, port 18585:
|
|
||||||
#
|
|
||||||
# docker run --detach --tty \
|
|
||||||
# --env PORT=18585 \
|
|
||||||
# --publish 18585:18585 \
|
|
||||||
# --volume $PWD:/var/lib/bbprserv \
|
|
||||||
# prserv
|
|
||||||
#
|
|
||||||
# 2. PR Service in RO mode, default port (8585) and custom LOGFILE:
|
|
||||||
#
|
|
||||||
# docker run --detach --tty \
|
|
||||||
# --env DBMODE="--read-only" \
|
|
||||||
# --env LOGFILE=/var/lib/bbprserv/prservro.log \
|
|
||||||
# --publish 8585:8585 \
|
|
||||||
# --volume $PWD:/var/lib/bbprserv \
|
|
||||||
# prserv
|
|
||||||
#
|
|
||||||
|
|
||||||
FROM alpine:3.14.4
|
|
||||||
|
|
||||||
RUN apk add --no-cache python3
|
|
||||||
|
|
||||||
COPY bin/bitbake-prserv /opt/bbprserv/bin/
|
|
||||||
COPY lib/prserv /opt/bbprserv/lib/prserv/
|
|
||||||
COPY lib/bb /opt/bbprserv/lib/bb/
|
|
||||||
COPY lib/codegen.py /opt/bbprserv/lib/codegen.py
|
|
||||||
COPY lib/ply /opt/bbprserv/lib/ply/
|
|
||||||
COPY lib/bs4 /opt/bbprserv/lib/bs4/
|
|
||||||
|
|
||||||
ENV PATH=$PATH:/opt/bbprserv/bin
|
|
||||||
|
|
||||||
RUN mkdir -p /var/lib/bbprserv
|
|
||||||
|
|
||||||
ENV DBFILE=/var/lib/bbprserv/prserv.sqlite3 \
|
|
||||||
LOGFILE=/var/lib/bbprserv/prserv.log \
|
|
||||||
LOGLEVEL=debug \
|
|
||||||
HOST=0.0.0.0 \
|
|
||||||
PORT=8585 \
|
|
||||||
DBMODE=""
|
|
||||||
|
|
||||||
ENTRYPOINT [ "/bin/sh", "-c", \
|
|
||||||
"bitbake-prserv \
|
|
||||||
--file=$DBFILE \
|
|
||||||
--log=$LOGFILE \
|
|
||||||
--loglevel=$LOGLEVEL \
|
|
||||||
--start \
|
|
||||||
--host=$HOST \
|
|
||||||
--port=$PORT \
|
|
||||||
$DBMODE \
|
|
||||||
&& tail -f $LOGFILE"]
|
|
||||||
@@ -40,7 +40,7 @@ set cpo&vim
|
|||||||
|
|
||||||
let s:maxoff = 50 " maximum number of lines to look backwards for ()
|
let s:maxoff = 50 " maximum number of lines to look backwards for ()
|
||||||
|
|
||||||
function! GetBBPythonIndent(lnum)
|
function GetPythonIndent(lnum)
|
||||||
|
|
||||||
" If this line is explicitly joined: If the previous line was also joined,
|
" If this line is explicitly joined: If the previous line was also joined,
|
||||||
" line it up with that one, otherwise add two 'shiftwidth'
|
" line it up with that one, otherwise add two 'shiftwidth'
|
||||||
@@ -257,7 +257,7 @@ let b:did_indent = 1
|
|||||||
setlocal indentkeys+=0\"
|
setlocal indentkeys+=0\"
|
||||||
|
|
||||||
|
|
||||||
function! BitbakeIndent(lnum)
|
function BitbakeIndent(lnum)
|
||||||
if !has('syntax_items')
|
if !has('syntax_items')
|
||||||
return -1
|
return -1
|
||||||
endif
|
endif
|
||||||
@@ -315,7 +315,7 @@ function! BitbakeIndent(lnum)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
if index(["bbPyDefRegion", "bbPyFuncRegion"], name) != -1
|
if index(["bbPyDefRegion", "bbPyFuncRegion"], name) != -1
|
||||||
let ret = GetBBPythonIndent(a:lnum)
|
let ret = GetPythonIndent(a:lnum)
|
||||||
" Should normally always be indented by at least one shiftwidth; but allow
|
" Should normally always be indented by at least one shiftwidth; but allow
|
||||||
" return of -1 (defer to autoindent) or -2 (force indent to 0)
|
" return of -1 (defer to autoindent) or -2 (force indent to 0)
|
||||||
if ret == 0
|
if ret == 0
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ fun! NewBBAppendTemplate()
|
|||||||
set nopaste
|
set nopaste
|
||||||
|
|
||||||
" New bbappend template
|
" New bbappend template
|
||||||
0 put ='FILESEXTRAPATHS:prepend := \"${THISDIR}/${PN}:\"'
|
0 put ='FILESEXTRAPATHS_prepend := \"${THISDIR}/${PN}:\"'
|
||||||
2
|
2
|
||||||
|
|
||||||
if paste == 1
|
if paste == 1
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ syn region bbString matchgroup=bbQuote start=+'+ skip=+\\$+ end=+'+
|
|||||||
syn match bbExport "^export" nextgroup=bbIdentifier skipwhite
|
syn match bbExport "^export" nextgroup=bbIdentifier skipwhite
|
||||||
syn keyword bbExportFlag export contained nextgroup=bbIdentifier skipwhite
|
syn keyword bbExportFlag export contained nextgroup=bbIdentifier skipwhite
|
||||||
syn match bbIdentifier "[a-zA-Z0-9\-_\.\/\+]\+" display contained
|
syn match bbIdentifier "[a-zA-Z0-9\-_\.\/\+]\+" display contained
|
||||||
syn match bbVarDeref "${[a-zA-Z0-9\-_:\.\/\+]\+}" contained
|
syn match bbVarDeref "${[a-zA-Z0-9\-_\.\/\+]\+}" contained
|
||||||
syn match bbVarEq "\(:=\|+=\|=+\|\.=\|=\.\|?=\|??=\|=\)" contained nextgroup=bbVarValue
|
syn match bbVarEq "\(:=\|+=\|=+\|\.=\|=\.\|?=\|??=\|=\)" contained nextgroup=bbVarValue
|
||||||
syn match bbVarDef "^\(export\s*\)\?\([a-zA-Z0-9\-_\.\/\+][${}a-zA-Z0-9\-_:\.\/\+]*\)\s*\(:=\|+=\|=+\|\.=\|=\.\|?=\|??=\|=\)\@=" contains=bbExportFlag,bbIdentifier,bbOverrideOperator,bbVarDeref nextgroup=bbVarEq
|
syn match bbVarDef "^\(export\s*\)\?\([a-zA-Z0-9\-_\.\/\+]\+\(_[${}a-zA-Z0-9\-_\.\/\+]\+\)\?\)\s*\(:=\|+=\|=+\|\.=\|=\.\|?=\|??=\|=\)\@=" contains=bbExportFlag,bbIdentifier,bbVarDeref nextgroup=bbVarEq
|
||||||
syn match bbVarValue ".*$" contained contains=bbString,bbVarDeref,bbVarPyValue
|
syn match bbVarValue ".*$" contained contains=bbString,bbVarDeref,bbVarPyValue
|
||||||
syn region bbVarPyValue start=+${@+ skip=+\\$+ end=+}+ contained contains=@python
|
syn region bbVarPyValue start=+${@+ skip=+\\$+ end=+}+ contained contains=@python
|
||||||
|
|
||||||
@@ -63,14 +63,13 @@ syn region bbVarFlagFlag matchgroup=bbArrayBrackets start="\[" end="\]\s*
|
|||||||
|
|
||||||
" Includes and requires
|
" Includes and requires
|
||||||
syn keyword bbInclude inherit include require contained
|
syn keyword bbInclude inherit include require contained
|
||||||
syn match bbIncludeRest ".*$" contained contains=bbString,bbVarDeref,bbVarPyValue
|
syn match bbIncludeRest ".*$" contained contains=bbString,bbVarDeref
|
||||||
syn match bbIncludeLine "^\(inherit\|include\|require\)\s\+" contains=bbInclude nextgroup=bbIncludeRest
|
syn match bbIncludeLine "^\(inherit\|include\|require\)\s\+" contains=bbInclude nextgroup=bbIncludeRest
|
||||||
|
|
||||||
" Add taks and similar
|
" Add taks and similar
|
||||||
syn keyword bbStatement addtask deltask addhandler after before EXPORT_FUNCTIONS contained
|
syn keyword bbStatement addtask deltask addhandler after before EXPORT_FUNCTIONS contained
|
||||||
syn match bbStatementRest /[^\\]*$/ skipwhite contained contains=bbStatement,bbVarDeref,bbVarPyValue
|
syn match bbStatementRest ".*$" skipwhite contained contains=bbStatement
|
||||||
syn region bbStatementRestCont start=/.*\\$/ end=/^[^\\]*$/ contained contains=bbStatement,bbVarDeref,bbVarPyValue,bbContinue keepend
|
syn match bbStatementLine "^\(addtask\|deltask\|addhandler\|after\|before\|EXPORT_FUNCTIONS\)\s\+" contains=bbStatement nextgroup=bbStatementRest
|
||||||
syn match bbStatementLine "^\(addtask\|deltask\|addhandler\|after\|before\|EXPORT_FUNCTIONS\)\s\+" contains=bbStatement nextgroup=bbStatementRest,bbStatementRestCont
|
|
||||||
|
|
||||||
" OE Important Functions
|
" OE Important Functions
|
||||||
syn keyword bbOEFunctions do_fetch do_unpack do_patch do_configure do_compile do_stage do_install do_package contained
|
syn keyword bbOEFunctions do_fetch do_unpack do_patch do_configure do_compile do_stage do_install do_package contained
|
||||||
@@ -78,15 +77,13 @@ syn keyword bbOEFunctions do_fetch do_unpack do_patch do_configure do_comp
|
|||||||
" Generic Functions
|
" Generic Functions
|
||||||
syn match bbFunction "\h[0-9A-Za-z_\-\.]*" display contained contains=bbOEFunctions
|
syn match bbFunction "\h[0-9A-Za-z_\-\.]*" display contained contains=bbOEFunctions
|
||||||
|
|
||||||
syn keyword bbOverrideOperator append prepend remove contained
|
|
||||||
|
|
||||||
" BitBake shell metadata
|
" BitBake shell metadata
|
||||||
syn include @shell syntax/sh.vim
|
syn include @shell syntax/sh.vim
|
||||||
if exists("b:current_syntax")
|
if exists("b:current_syntax")
|
||||||
unlet b:current_syntax
|
unlet b:current_syntax
|
||||||
endif
|
endif
|
||||||
syn keyword bbShFakeRootFlag fakeroot contained
|
syn keyword bbShFakeRootFlag fakeroot contained
|
||||||
syn match bbShFuncDef "^\(fakeroot\s*\)\?\([\.0-9A-Za-z_:${}\-\.]\+\)\(python\)\@<!\(\s*()\s*\)\({\)\@=" contains=bbShFakeRootFlag,bbFunction,bbOverrideOperator,bbVarDeref,bbDelimiter nextgroup=bbShFuncRegion skipwhite
|
syn match bbShFuncDef "^\(fakeroot\s*\)\?\([\.0-9A-Za-z_${}\-\.]\+\)\(python\)\@<!\(\s*()\s*\)\({\)\@=" contains=bbShFakeRootFlag,bbFunction,bbVarDeref,bbDelimiter nextgroup=bbShFuncRegion skipwhite
|
||||||
syn region bbShFuncRegion matchgroup=bbDelimiter start="{\s*$" end="^}\s*$" contained contains=@shell
|
syn region bbShFuncRegion matchgroup=bbDelimiter start="{\s*$" end="^}\s*$" contained contains=@shell
|
||||||
|
|
||||||
" Python value inside shell functions
|
" Python value inside shell functions
|
||||||
@@ -94,7 +91,7 @@ syn region shDeref start=+${@+ skip=+\\$+ excludenl end=+}+ contained co
|
|||||||
|
|
||||||
" BitBake python metadata
|
" BitBake python metadata
|
||||||
syn keyword bbPyFlag python contained
|
syn keyword bbPyFlag python contained
|
||||||
syn match bbPyFuncDef "^\(fakeroot\s*\)\?\(python\)\(\s\+[0-9A-Za-z_:${}\-\.]\+\)\?\(\s*()\s*\)\({\)\@=" contains=bbShFakeRootFlag,bbPyFlag,bbFunction,bbOverrideOperator,bbVarDeref,bbDelimiter nextgroup=bbPyFuncRegion skipwhite
|
syn match bbPyFuncDef "^\(fakeroot\s*\)\?\(python\)\(\s\+[0-9A-Za-z_${}\-\.]\+\)\?\(\s*()\s*\)\({\)\@=" contains=bbShFakeRootFlag,bbPyFlag,bbFunction,bbVarDeref,bbDelimiter nextgroup=bbPyFuncRegion skipwhite
|
||||||
syn region bbPyFuncRegion matchgroup=bbDelimiter start="{\s*$" end="^}\s*$" contained contains=@python
|
syn region bbPyFuncRegion matchgroup=bbDelimiter start="{\s*$" end="^}\s*$" contained contains=@python
|
||||||
|
|
||||||
" BitBake 'def'd python functions
|
" BitBake 'def'd python functions
|
||||||
@@ -123,9 +120,7 @@ hi def link bbPyFlag Type
|
|||||||
hi def link bbPyDef Statement
|
hi def link bbPyDef Statement
|
||||||
hi def link bbStatement Statement
|
hi def link bbStatement Statement
|
||||||
hi def link bbStatementRest Identifier
|
hi def link bbStatementRest Identifier
|
||||||
hi def link bbStatementRestCont Identifier
|
|
||||||
hi def link bbOEFunctions Special
|
hi def link bbOEFunctions Special
|
||||||
hi def link bbVarPyValue PreProc
|
hi def link bbVarPyValue PreProc
|
||||||
hi def link bbOverrideOperator Operator
|
|
||||||
|
|
||||||
let b:current_syntax = "bb"
|
let b:current_syntax = "bb"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# You can set these variables from the command line, and also
|
# You can set these variables from the command line, and also
|
||||||
# from the environment for the first two.
|
# from the environment for the first two.
|
||||||
SPHINXOPTS ?= -W --keep-going -j auto
|
SPHINXOPTS ?=
|
||||||
SPHINXBUILD ?= sphinx-build
|
SPHINXBUILD ?= sphinx-build
|
||||||
SOURCEDIR = .
|
SOURCEDIR = .
|
||||||
BUILDDIR = _build
|
BUILDDIR = _build
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ Manual Organization
|
|||||||
|
|
||||||
Folders exist for individual manuals as follows:
|
Folders exist for individual manuals as follows:
|
||||||
|
|
||||||
* bitbake-user-manual --- The BitBake User Manual
|
* bitbake-user-manual - The BitBake User Manual
|
||||||
|
|
||||||
Each folder is self-contained regarding content and figures.
|
Each folder is self-contained regarding content and figures.
|
||||||
|
|
||||||
If you want to find HTML versions of the BitBake manuals on the web,
|
If you want to find HTML versions of the BitBake manuals on the web,
|
||||||
go to https://www.openembedded.org/wiki/Documentation.
|
go to http://www.openembedded.org/wiki/Documentation.
|
||||||
|
|
||||||
Sphinx
|
Sphinx
|
||||||
======
|
======
|
||||||
@@ -47,8 +47,8 @@ To install all required packages run:
|
|||||||
|
|
||||||
To build the documentation locally, run:
|
To build the documentation locally, run:
|
||||||
|
|
||||||
$ cd doc
|
$ cd documentation
|
||||||
$ make html
|
$ make -f Makefile.sphinx html
|
||||||
|
|
||||||
The resulting HTML index page will be _build/html/index.html, and you
|
The resulting HTML index page will be _build/html/index.html, and you
|
||||||
can browse your own copy of the locally generated documentation with
|
can browse your own copy of the locally generated documentation with
|
||||||
|
|||||||
9
bitbake/doc/_templates/footer.html
vendored
9
bitbake/doc/_templates/footer.html
vendored
@@ -1,9 +0,0 @@
|
|||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<div role="contentinfo">
|
|
||||||
<p>© Copyright {{ copyright }}
|
|
||||||
<br>Last updated on {{ last_updated }} from the <a href="https://git.openembedded.org/bitbake/">bitbake</a> git repository.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ data, or simply return information about the execution environment.
|
|||||||
|
|
||||||
This chapter describes BitBake's execution process from start to finish
|
This chapter describes BitBake's execution process from start to finish
|
||||||
when you use it to create an image. The execution process is launched
|
when you use it to create an image. The execution process is launched
|
||||||
using the following command form::
|
using the following command form: ::
|
||||||
|
|
||||||
$ bitbake target
|
$ bitbake target
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ the BitBake command and its options, see ":ref:`The BitBake Command
|
|||||||
your project's ``local.conf`` configuration file.
|
your project's ``local.conf`` configuration file.
|
||||||
|
|
||||||
A common method to determine this value for your build host is to run
|
A common method to determine this value for your build host is to run
|
||||||
the following::
|
the following: ::
|
||||||
|
|
||||||
$ grep processor /proc/cpuinfo
|
$ grep processor /proc/cpuinfo
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ the BitBake command and its options, see ":ref:`The BitBake Command
|
|||||||
the number of processors, which takes into account hyper-threading.
|
the number of processors, which takes into account hyper-threading.
|
||||||
Thus, a quad-core build host with hyper-threading most likely shows
|
Thus, a quad-core build host with hyper-threading most likely shows
|
||||||
eight processors, which is the value you would then assign to
|
eight processors, which is the value you would then assign to
|
||||||
:term:`BB_NUMBER_THREADS`.
|
``BB_NUMBER_THREADS``.
|
||||||
|
|
||||||
A possibly simpler solution is that some Linux distributions (e.g.
|
A possibly simpler solution is that some Linux distributions (e.g.
|
||||||
Debian and Ubuntu) provide the ``ncpus`` command.
|
Debian and Ubuntu) provide the ``ncpus`` command.
|
||||||
@@ -65,13 +65,13 @@ data itself is of various types:
|
|||||||
|
|
||||||
The ``layer.conf`` files are used to construct key variables such as
|
The ``layer.conf`` files are used to construct key variables such as
|
||||||
:term:`BBPATH` and :term:`BBFILES`.
|
:term:`BBPATH` and :term:`BBFILES`.
|
||||||
:term:`BBPATH` is used to search for configuration and class files under the
|
``BBPATH`` is used to search for configuration and class files under the
|
||||||
``conf`` and ``classes`` directories, respectively. :term:`BBFILES` is used
|
``conf`` and ``classes`` directories, respectively. ``BBFILES`` is used
|
||||||
to locate both recipe and recipe append files (``.bb`` and
|
to locate both recipe and recipe append files (``.bb`` and
|
||||||
``.bbappend``). If there is no ``bblayers.conf`` file, it is assumed the
|
``.bbappend``). If there is no ``bblayers.conf`` file, it is assumed the
|
||||||
user has set the :term:`BBPATH` and :term:`BBFILES` directly in the environment.
|
user has set the ``BBPATH`` and ``BBFILES`` directly in the environment.
|
||||||
|
|
||||||
Next, the ``bitbake.conf`` file is located using the :term:`BBPATH` variable
|
Next, the ``bitbake.conf`` file is located using the ``BBPATH`` variable
|
||||||
that was just constructed. The ``bitbake.conf`` file may also include
|
that was just constructed. The ``bitbake.conf`` file may also include
|
||||||
other configuration files using the ``include`` or ``require``
|
other configuration files using the ``include`` or ``require``
|
||||||
directives.
|
directives.
|
||||||
@@ -79,8 +79,8 @@ directives.
|
|||||||
Prior to parsing configuration files, BitBake looks at certain
|
Prior to parsing configuration files, BitBake looks at certain
|
||||||
variables, including:
|
variables, including:
|
||||||
|
|
||||||
- :term:`BB_ENV_PASSTHROUGH`
|
- :term:`BB_ENV_WHITELIST`
|
||||||
- :term:`BB_ENV_PASSTHROUGH_ADDITIONS`
|
- :term:`BB_ENV_EXTRAWHITE`
|
||||||
- :term:`BB_PRESERVE_ENV`
|
- :term:`BB_PRESERVE_ENV`
|
||||||
- :term:`BB_ORIGENV`
|
- :term:`BB_ORIGENV`
|
||||||
- :term:`BITBAKE_UI`
|
- :term:`BITBAKE_UI`
|
||||||
@@ -104,7 +104,7 @@ BitBake first searches the current working directory for an optional
|
|||||||
contain a :term:`BBLAYERS` variable that is a
|
contain a :term:`BBLAYERS` variable that is a
|
||||||
space-delimited list of 'layer' directories. Recall that if BitBake
|
space-delimited list of 'layer' directories. Recall that if BitBake
|
||||||
cannot find a ``bblayers.conf`` file, then it is assumed the user has
|
cannot find a ``bblayers.conf`` file, then it is assumed the user has
|
||||||
set the :term:`BBPATH` and :term:`BBFILES` variables directly in the
|
set the ``BBPATH`` and ``BBFILES`` variables directly in the
|
||||||
environment.
|
environment.
|
||||||
|
|
||||||
For each directory (layer) in this list, a ``conf/layer.conf`` file is
|
For each directory (layer) in this list, a ``conf/layer.conf`` file is
|
||||||
@@ -114,7 +114,7 @@ files automatically set up :term:`BBPATH` and other
|
|||||||
variables correctly for a given build directory.
|
variables correctly for a given build directory.
|
||||||
|
|
||||||
BitBake then expects to find the ``conf/bitbake.conf`` file somewhere in
|
BitBake then expects to find the ``conf/bitbake.conf`` file somewhere in
|
||||||
the user-specified :term:`BBPATH`. That configuration file generally has
|
the user-specified ``BBPATH``. That configuration file generally has
|
||||||
include directives to pull in any other metadata such as files specific
|
include directives to pull in any other metadata such as files specific
|
||||||
to the architecture, the machine, the local environment, and so forth.
|
to the architecture, the machine, the local environment, and so forth.
|
||||||
|
|
||||||
@@ -135,11 +135,11 @@ The ``base.bbclass`` file is always included. Other classes that are
|
|||||||
specified in the configuration using the
|
specified in the configuration using the
|
||||||
:term:`INHERIT` variable are also included. BitBake
|
:term:`INHERIT` variable are also included. BitBake
|
||||||
searches for class files in a ``classes`` subdirectory under the paths
|
searches for class files in a ``classes`` subdirectory under the paths
|
||||||
in :term:`BBPATH` in the same way as configuration files.
|
in ``BBPATH`` in the same way as configuration files.
|
||||||
|
|
||||||
A good way to get an idea of the configuration files and the class files
|
A good way to get an idea of the configuration files and the class files
|
||||||
used in your execution environment is to run the following BitBake
|
used in your execution environment is to run the following BitBake
|
||||||
command::
|
command: ::
|
||||||
|
|
||||||
$ bitbake -e > mybb.log
|
$ bitbake -e > mybb.log
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ execution environment.
|
|||||||
pair of curly braces in a shell function, the closing curly brace
|
pair of curly braces in a shell function, the closing curly brace
|
||||||
must not be located at the start of the line without leading spaces.
|
must not be located at the start of the line without leading spaces.
|
||||||
|
|
||||||
Here is an example that causes BitBake to produce a parsing error::
|
Here is an example that causes BitBake to produce a parsing error: ::
|
||||||
|
|
||||||
fakeroot create_shar() {
|
fakeroot create_shar() {
|
||||||
cat << "EOF" > ${SDK_DEPLOY}/${TOOLCHAIN_OUTPUTNAME}.sh
|
cat << "EOF" > ${SDK_DEPLOY}/${TOOLCHAIN_OUTPUTNAME}.sh
|
||||||
@@ -184,13 +184,13 @@ Locating and Parsing Recipes
|
|||||||
During the configuration phase, BitBake will have set
|
During the configuration phase, BitBake will have set
|
||||||
:term:`BBFILES`. BitBake now uses it to construct a
|
:term:`BBFILES`. BitBake now uses it to construct a
|
||||||
list of recipes to parse, along with any append files (``.bbappend``) to
|
list of recipes to parse, along with any append files (``.bbappend``) to
|
||||||
apply. :term:`BBFILES` is a space-separated list of available files and
|
apply. ``BBFILES`` is a space-separated list of available files and
|
||||||
supports wildcards. An example would be::
|
supports wildcards. An example would be: ::
|
||||||
|
|
||||||
BBFILES = "/path/to/bbfiles/*.bb /path/to/appends/*.bbappend"
|
BBFILES = "/path/to/bbfiles/*.bb /path/to/appends/*.bbappend"
|
||||||
|
|
||||||
BitBake parses each
|
BitBake parses each
|
||||||
recipe and append file located with :term:`BBFILES` and stores the values of
|
recipe and append file located with ``BBFILES`` and stores the values of
|
||||||
various variables into the datastore.
|
various variables into the datastore.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@@ -201,18 +201,18 @@ For each file, a fresh copy of the base configuration is made, then the
|
|||||||
recipe is parsed line by line. Any inherit statements cause BitBake to
|
recipe is parsed line by line. Any inherit statements cause BitBake to
|
||||||
find and then parse class files (``.bbclass``) using
|
find and then parse class files (``.bbclass``) using
|
||||||
:term:`BBPATH` as the search path. Finally, BitBake
|
:term:`BBPATH` as the search path. Finally, BitBake
|
||||||
parses in order any append files found in :term:`BBFILES`.
|
parses in order any append files found in ``BBFILES``.
|
||||||
|
|
||||||
One common convention is to use the recipe filename to define pieces of
|
One common convention is to use the recipe filename to define pieces of
|
||||||
metadata. For example, in ``bitbake.conf`` the recipe name and version
|
metadata. For example, in ``bitbake.conf`` the recipe name and version
|
||||||
are used to set the variables :term:`PN` and
|
are used to set the variables :term:`PN` and
|
||||||
:term:`PV`::
|
:term:`PV`: ::
|
||||||
|
|
||||||
PN = "${@bb.parse.vars_from_file(d.getVar('FILE', False),d)[0] or 'defaultpkgname'}"
|
PN = "${@bb.parse.BBHandler.vars_from_file(d.getVar('FILE', False),d)[0] or 'defaultpkgname'}"
|
||||||
PV = "${@bb.parse.vars_from_file(d.getVar('FILE', False),d)[1] or '1.0'}"
|
PV = "${@bb.parse.BBHandler.vars_from_file(d.getVar('FILE', False),d)[1] or '1.0'}"
|
||||||
|
|
||||||
In this example, a recipe called "something_1.2.3.bb" would set
|
In this example, a recipe called "something_1.2.3.bb" would set
|
||||||
:term:`PN` to "something" and :term:`PV` to "1.2.3".
|
``PN`` to "something" and ``PV`` to "1.2.3".
|
||||||
|
|
||||||
By the time parsing is complete for a recipe, BitBake has a list of
|
By the time parsing is complete for a recipe, BitBake has a list of
|
||||||
tasks that the recipe defines and a set of data consisting of keys and
|
tasks that the recipe defines and a set of data consisting of keys and
|
||||||
@@ -228,7 +228,7 @@ and then reload it.
|
|||||||
Where possible, subsequent BitBake commands reuse this cache of recipe
|
Where possible, subsequent BitBake commands reuse this cache of recipe
|
||||||
information. The validity of this cache is determined by first computing
|
information. The validity of this cache is determined by first computing
|
||||||
a checksum of the base configuration data (see
|
a checksum of the base configuration data (see
|
||||||
:term:`BB_HASHCONFIG_IGNORE_VARS`) and
|
:term:`BB_HASHCONFIG_WHITELIST`) and
|
||||||
then checking if the checksum matches. If that checksum matches what is
|
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
|
in the cache and the recipe and class files have not changed, BitBake is
|
||||||
able to use the cache. BitBake then reloads the cached information about
|
able to use the cache. BitBake then reloads the cached information about
|
||||||
@@ -238,14 +238,13 @@ Recipe file collections exist to allow the user to have multiple
|
|||||||
repositories of ``.bb`` files that contain the same exact package. For
|
repositories of ``.bb`` files that contain the same exact package. For
|
||||||
example, one could easily use them to make one's own local copy of an
|
example, one could easily use them to make one's own local copy of an
|
||||||
upstream repository, but with custom modifications that one does not
|
upstream repository, but with custom modifications that one does not
|
||||||
want upstream. Here is an example::
|
want upstream. Here is an example: ::
|
||||||
|
|
||||||
BBFILES = "/stuff/openembedded/*/*.bb /stuff/openembedded.modified/*/*.bb"
|
BBFILES = "/stuff/openembedded/*/*.bb /stuff/openembedded.modified/*/*.bb"
|
||||||
BBFILE_COLLECTIONS = "upstream local"
|
BBFILE_COLLECTIONS = "upstream local"
|
||||||
BBFILE_PATTERN_upstream = "^/stuff/openembedded/"
|
BBFILE_PATTERN_upstream = "^/stuff/openembedded/"
|
||||||
BBFILE_PATTERN_local = "^/stuff/openembedded.modified/"
|
BBFILE_PATTERN_local = "^/stuff/openembedded.modified/"
|
||||||
BBFILE_PRIORITY_upstream = "5"
|
BBFILE_PRIORITY_upstream = "5" BBFILE_PRIORITY_local = "10"
|
||||||
BBFILE_PRIORITY_local = "10"
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@@ -260,21 +259,21 @@ Providers
|
|||||||
|
|
||||||
Assuming BitBake has been instructed to execute a target and that all
|
Assuming BitBake has been instructed to execute a target and that all
|
||||||
the recipe files have been parsed, BitBake starts to figure out how to
|
the recipe files have been parsed, BitBake starts to figure out how to
|
||||||
build the target. BitBake looks through the :term:`PROVIDES` list for each
|
build the target. BitBake looks through the ``PROVIDES`` list for each
|
||||||
of the recipes. A :term:`PROVIDES` list is the list of names by which the
|
of the recipes. A ``PROVIDES`` list is the list of names by which the
|
||||||
recipe can be known. Each recipe's :term:`PROVIDES` list is created
|
recipe can be known. Each recipe's ``PROVIDES`` list is created
|
||||||
implicitly through the recipe's :term:`PN` variable and
|
implicitly through the recipe's :term:`PN` variable and
|
||||||
explicitly through the recipe's :term:`PROVIDES`
|
explicitly through the recipe's :term:`PROVIDES`
|
||||||
variable, which is optional.
|
variable, which is optional.
|
||||||
|
|
||||||
When a recipe uses :term:`PROVIDES`, that recipe's functionality can be
|
When a recipe uses ``PROVIDES``, that recipe's functionality can be
|
||||||
found under an alternative name or names other than the implicit :term:`PN`
|
found under an alternative name or names other than the implicit ``PN``
|
||||||
name. As an example, suppose a recipe named ``keyboard_1.0.bb``
|
name. As an example, suppose a recipe named ``keyboard_1.0.bb``
|
||||||
contained the following::
|
contained the following: ::
|
||||||
|
|
||||||
PROVIDES += "fullkeyboard"
|
PROVIDES += "fullkeyboard"
|
||||||
|
|
||||||
The :term:`PROVIDES`
|
The ``PROVIDES``
|
||||||
list for this recipe becomes "keyboard", which is implicit, and
|
list for this recipe becomes "keyboard", which is implicit, and
|
||||||
"fullkeyboard", which is explicit. Consequently, the functionality found
|
"fullkeyboard", which is explicit. Consequently, the functionality found
|
||||||
in ``keyboard_1.0.bb`` can be found under two different names.
|
in ``keyboard_1.0.bb`` can be found under two different names.
|
||||||
@@ -284,14 +283,14 @@ in ``keyboard_1.0.bb`` can be found under two different names.
|
|||||||
Preferences
|
Preferences
|
||||||
===========
|
===========
|
||||||
|
|
||||||
The :term:`PROVIDES` list is only part of the solution for figuring out a
|
The ``PROVIDES`` list is only part of the solution for figuring out a
|
||||||
target's recipes. Because targets might have multiple providers, BitBake
|
target's recipes. Because targets might have multiple providers, BitBake
|
||||||
needs to prioritize providers by determining provider preferences.
|
needs to prioritize providers by determining provider preferences.
|
||||||
|
|
||||||
A common example in which a target has multiple providers is
|
A common example in which a target has multiple providers is
|
||||||
"virtual/kernel", which is on the :term:`PROVIDES` list for each kernel
|
"virtual/kernel", which is on the ``PROVIDES`` list for each kernel
|
||||||
recipe. Each machine often selects the best kernel provider by using a
|
recipe. Each machine often selects the best kernel provider by using a
|
||||||
line similar to the following in the machine configuration file::
|
line similar to the following in the machine configuration file: ::
|
||||||
|
|
||||||
PREFERRED_PROVIDER_virtual/kernel = "linux-yocto"
|
PREFERRED_PROVIDER_virtual/kernel = "linux-yocto"
|
||||||
|
|
||||||
@@ -309,10 +308,10 @@ specify a particular version. You can influence the order by using the
|
|||||||
:term:`DEFAULT_PREFERENCE` variable.
|
:term:`DEFAULT_PREFERENCE` variable.
|
||||||
|
|
||||||
By default, files have a preference of "0". Setting
|
By default, files have a preference of "0". Setting
|
||||||
:term:`DEFAULT_PREFERENCE` to "-1" makes the recipe unlikely to be used
|
``DEFAULT_PREFERENCE`` to "-1" makes the recipe unlikely to be used
|
||||||
unless it is explicitly referenced. Setting :term:`DEFAULT_PREFERENCE` to
|
unless it is explicitly referenced. Setting ``DEFAULT_PREFERENCE`` to
|
||||||
"1" makes it likely the recipe is used. :term:`PREFERRED_VERSION` overrides
|
"1" makes it likely the recipe is used. ``PREFERRED_VERSION`` overrides
|
||||||
any :term:`DEFAULT_PREFERENCE` setting. :term:`DEFAULT_PREFERENCE` is often used
|
any ``DEFAULT_PREFERENCE`` setting. ``DEFAULT_PREFERENCE`` is often used
|
||||||
to mark newer and more experimental recipe versions until they have
|
to mark newer and more experimental recipe versions until they have
|
||||||
undergone sufficient testing to be considered stable.
|
undergone sufficient testing to be considered stable.
|
||||||
|
|
||||||
@@ -331,7 +330,7 @@ If the first recipe is named ``a_1.1.bb``, then the
|
|||||||
|
|
||||||
Thus, if a recipe named ``a_1.2.bb`` exists, BitBake will choose 1.2 by
|
Thus, if a recipe named ``a_1.2.bb`` exists, BitBake will choose 1.2 by
|
||||||
default. However, if you define the following variable in a ``.conf``
|
default. However, if you define the following variable in a ``.conf``
|
||||||
file that BitBake parses, you can change that preference::
|
file that BitBake parses, you can change that preference: ::
|
||||||
|
|
||||||
PREFERRED_VERSION_a = "1.1"
|
PREFERRED_VERSION_a = "1.1"
|
||||||
|
|
||||||
@@ -394,7 +393,7 @@ ready to run, those tasks have all their dependencies met, and the
|
|||||||
thread threshold has not been exceeded.
|
thread threshold has not been exceeded.
|
||||||
|
|
||||||
It is worth noting that you can greatly speed up the build time by
|
It is worth noting that you can greatly speed up the build time by
|
||||||
properly setting the :term:`BB_NUMBER_THREADS` variable.
|
properly setting the ``BB_NUMBER_THREADS`` variable.
|
||||||
|
|
||||||
As each task completes, a timestamp is written to the directory
|
As each task completes, a timestamp is written to the directory
|
||||||
specified by the :term:`STAMP` variable. On subsequent
|
specified by the :term:`STAMP` variable. On subsequent
|
||||||
@@ -435,7 +434,7 @@ BitBake writes a shell script to
|
|||||||
executes the script. The generated shell script contains all the
|
executes the script. The generated shell script contains all the
|
||||||
exported variables, and the shell functions with all variables expanded.
|
exported variables, and the shell functions with all variables expanded.
|
||||||
Output from the shell script goes to the file
|
Output from the shell script goes to the file
|
||||||
``${``\ :term:`T`\ ``}/log.do_taskname.pid``. Looking at the expanded shell functions in
|
``${T}/log.do_taskname.pid``. Looking at the expanded shell functions in
|
||||||
the run file and the output in the log files is a useful debugging
|
the run file and the output in the log files is a useful debugging
|
||||||
technique.
|
technique.
|
||||||
|
|
||||||
@@ -477,7 +476,7 @@ changes because it should not affect the output for target packages. The
|
|||||||
simplistic approach for excluding the working directory is to set it to
|
simplistic approach for excluding the working directory is to set it to
|
||||||
some fixed value and create the checksum for the "run" script. BitBake
|
some fixed value and create the checksum for the "run" script. BitBake
|
||||||
goes one step better and uses the
|
goes one step better and uses the
|
||||||
:term:`BB_BASEHASH_IGNORE_VARS` variable
|
:term:`BB_HASHBASE_WHITELIST` variable
|
||||||
to define a list of variables that should never be included when
|
to define a list of variables that should never be included when
|
||||||
generating the signatures.
|
generating the signatures.
|
||||||
|
|
||||||
@@ -498,7 +497,7 @@ to the task.
|
|||||||
|
|
||||||
Like the working directory case, situations exist where dependencies
|
Like the working directory case, situations exist where dependencies
|
||||||
should be ignored. For these cases, you can instruct the build process
|
should be ignored. For these cases, you can instruct the build process
|
||||||
to ignore a dependency by using a line like the following::
|
to ignore a dependency by using a line like the following: ::
|
||||||
|
|
||||||
PACKAGE_ARCHS[vardepsexclude] = "MACHINE"
|
PACKAGE_ARCHS[vardepsexclude] = "MACHINE"
|
||||||
|
|
||||||
@@ -508,7 +507,7 @@ even if it does reference it.
|
|||||||
|
|
||||||
Equally, there are cases where we need to add dependencies BitBake is
|
Equally, there are cases where we need to add dependencies BitBake is
|
||||||
not able to find. You can accomplish this by using a line like the
|
not able to find. You can accomplish this by using a line like the
|
||||||
following::
|
following: ::
|
||||||
|
|
||||||
PACKAGE_ARCHS[vardeps] = "MACHINE"
|
PACKAGE_ARCHS[vardeps] = "MACHINE"
|
||||||
|
|
||||||
@@ -523,7 +522,7 @@ it cannot figure out dependencies.
|
|||||||
Thus far, this section has limited discussion to the direct inputs into
|
Thus far, this section has limited discussion to the direct inputs into
|
||||||
a task. Information based on direct inputs is referred to as the
|
a task. Information based on direct inputs is referred to as the
|
||||||
"basehash" in the code. However, there is still the question of a task's
|
"basehash" in the code. However, there is still the question of a task's
|
||||||
indirect inputs --- the things that were already built and present in the
|
indirect inputs - the things that were already built and present in the
|
||||||
build directory. The checksum (or signature) for a particular task needs
|
build directory. The checksum (or signature) for a particular task needs
|
||||||
to add the hashes of all the tasks on which the particular task depends.
|
to add the hashes of all the tasks on which the particular task depends.
|
||||||
Choosing which dependencies to add is a policy decision. However, the
|
Choosing which dependencies to add is a policy decision. However, the
|
||||||
@@ -534,11 +533,11 @@ At the code level, there are a variety of ways both the basehash and the
|
|||||||
dependent task hashes can be influenced. Within the BitBake
|
dependent task hashes can be influenced. Within the BitBake
|
||||||
configuration file, we can give BitBake some extra information to help
|
configuration file, we can give BitBake some extra information to help
|
||||||
it construct the basehash. The following statement effectively results
|
it construct the basehash. The following statement effectively results
|
||||||
in a list of global variable dependency excludes --- variables never
|
in a list of global variable dependency excludes - variables never
|
||||||
included in any checksum. This example uses variables from OpenEmbedded
|
included in any checksum. This example uses variables from OpenEmbedded
|
||||||
to help illustrate the concept::
|
to help illustrate the concept: ::
|
||||||
|
|
||||||
BB_BASEHASH_IGNORE_VARS ?= "TMPDIR FILE PATH PWD BB_TASKHASH BBPATH DL_DIR \
|
BB_HASHBASE_WHITELIST ?= "TMPDIR FILE PATH PWD BB_TASKHASH BBPATH DL_DIR \
|
||||||
SSTATE_DIR THISDIR FILESEXTRAPATHS FILE_DIRNAME HOME LOGNAME SHELL \
|
SSTATE_DIR THISDIR FILESEXTRAPATHS FILE_DIRNAME HOME LOGNAME SHELL \
|
||||||
USER FILESPATH STAGING_DIR_HOST STAGING_DIR_TARGET COREBASE PRSERV_HOST \
|
USER FILESPATH STAGING_DIR_HOST STAGING_DIR_TARGET COREBASE PRSERV_HOST \
|
||||||
PRSERV_DUMPDIR PRSERV_DUMPFILE PRSERV_LOCKDOWN PARALLEL_MAKE \
|
PRSERV_DUMPDIR PRSERV_DUMPFILE PRSERV_LOCKDOWN PARALLEL_MAKE \
|
||||||
@@ -552,22 +551,23 @@ through dependency chains are more complex and are generally
|
|||||||
accomplished with a Python function. The code in
|
accomplished with a Python function. The code in
|
||||||
``meta/lib/oe/sstatesig.py`` shows two examples of this and also
|
``meta/lib/oe/sstatesig.py`` shows two examples of this and also
|
||||||
illustrates how you can insert your own policy into the system if so
|
illustrates how you can insert your own policy into the system if so
|
||||||
desired. This file defines the basic signature generator
|
desired. This file defines the two basic signature generators
|
||||||
OpenEmbedded-Core uses: "OEBasicHash". By default, there
|
OpenEmbedded-Core uses: "OEBasic" and "OEBasicHash". By default, there
|
||||||
is a dummy "noop" signature handler enabled in BitBake. This means that
|
is a dummy "noop" signature handler enabled in BitBake. This means that
|
||||||
behavior is unchanged from previous versions. ``OE-Core`` uses the
|
behavior is unchanged from previous versions. ``OE-Core`` uses the
|
||||||
"OEBasicHash" signature handler by default through this setting in the
|
"OEBasicHash" signature handler by default through this setting in the
|
||||||
``bitbake.conf`` file::
|
``bitbake.conf`` file: ::
|
||||||
|
|
||||||
BB_SIGNATURE_HANDLER ?= "OEBasicHash"
|
BB_SIGNATURE_HANDLER ?= "OEBasicHash"
|
||||||
|
|
||||||
The main feature of the "OEBasicHash" :term:`BB_SIGNATURE_HANDLER` is that
|
The "OEBasicHash" ``BB_SIGNATURE_HANDLER`` is the same as the "OEBasic"
|
||||||
it adds the task hash to the stamp files. Thanks to this, any metadata
|
version but adds the task hash to the stamp files. This results in any
|
||||||
change will change the task hash, automatically causing the task to be run
|
metadata change that changes the task hash, automatically causing the
|
||||||
again. This removes the need to bump :term:`PR` values, and changes to
|
task to be run again. This removes the need to bump
|
||||||
metadata automatically ripple across the build.
|
:term:`PR` values, and changes to metadata automatically
|
||||||
|
ripple across the build.
|
||||||
|
|
||||||
It is also worth noting that the end result of signature
|
It is also worth noting that the end result of these signature
|
||||||
generators is to make some dependency and hash information available to
|
generators is to make some dependency and hash information available to
|
||||||
the build. This information includes:
|
the build. This information includes:
|
||||||
|
|
||||||
@@ -577,7 +577,10 @@ the build. This information includes:
|
|||||||
- ``BB_BASEHASH_``\ *filename:taskname*: The base hashes for each
|
- ``BB_BASEHASH_``\ *filename:taskname*: The base hashes for each
|
||||||
dependent task.
|
dependent task.
|
||||||
|
|
||||||
- :term:`BB_TASKHASH`: The hash of the currently running task.
|
- ``BBHASHDEPS_``\ *filename:taskname*: The task dependencies for
|
||||||
|
each task.
|
||||||
|
|
||||||
|
- ``BB_TASKHASH``: The hash of the currently running task.
|
||||||
|
|
||||||
It is worth noting that BitBake's "-S" option lets you debug BitBake's
|
It is worth noting that BitBake's "-S" option lets you debug BitBake's
|
||||||
processing of signatures. The options passed to -S allow different
|
processing of signatures. The options passed to -S allow different
|
||||||
@@ -586,11 +589,10 @@ or possibly those defined in the metadata/signature handler itself. The
|
|||||||
simplest parameter to pass is "none", which causes a set of signature
|
simplest parameter to pass is "none", which causes a set of signature
|
||||||
information to be written out into ``STAMPS_DIR`` corresponding to the
|
information to be written out into ``STAMPS_DIR`` corresponding to the
|
||||||
targets specified. The other currently available parameter is
|
targets specified. The other currently available parameter is
|
||||||
"printdiff", which causes BitBake to try to establish the most recent
|
"printdiff", which causes BitBake to try to establish the closest
|
||||||
signature match it can (e.g. in the sstate cache) and then run
|
signature match it can (e.g. in the sstate cache) and then run
|
||||||
compare the matched signatures to determine the stamps and delta
|
``bitbake-diffsigs`` over the matches to determine the stamps and delta
|
||||||
where these two stamp trees diverge. This can be used to determine why
|
where these two stamp trees diverge.
|
||||||
tasks need to be re-run in situations where that is not expected.
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@@ -645,6 +647,13 @@ compiled binary. To handle this, BitBake calls the
|
|||||||
each successful setscene task to know whether or not it needs to obtain
|
each successful setscene task to know whether or not it needs to obtain
|
||||||
the dependencies of that task.
|
the dependencies of that task.
|
||||||
|
|
||||||
|
Finally, after all the setscene tasks have executed, BitBake calls the
|
||||||
|
function listed in
|
||||||
|
:term:`BB_SETSCENE_VERIFY_FUNCTION2`
|
||||||
|
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 of the setscene result.
|
||||||
|
|
||||||
You can find more information on setscene metadata in the
|
You can find more information on setscene metadata in the
|
||||||
:ref:`bitbake-user-manual/bitbake-user-manual-metadata:task checksums and setscene`
|
:ref:`bitbake-user-manual/bitbake-user-manual-metadata:task checksums and setscene`
|
||||||
section.
|
section.
|
||||||
@@ -657,7 +666,7 @@ builds are when execute, bitbake also supports user defined
|
|||||||
configuration of the `Python
|
configuration of the `Python
|
||||||
logging <https://docs.python.org/3/library/logging.html>`__ facilities
|
logging <https://docs.python.org/3/library/logging.html>`__ facilities
|
||||||
through the :term:`BB_LOGCONFIG` variable. This
|
through the :term:`BB_LOGCONFIG` variable. This
|
||||||
variable defines a JSON or YAML `logging
|
variable defines a json or yaml `logging
|
||||||
configuration <https://docs.python.org/3/library/logging.config.html>`__
|
configuration <https://docs.python.org/3/library/logging.config.html>`__
|
||||||
that will be intelligently merged into the default configuration. The
|
that will be intelligently merged into the default configuration. The
|
||||||
logging configuration is merged using the following rules:
|
logging configuration is merged using the following rules:
|
||||||
@@ -691,9 +700,9 @@ logging configuration is merged using the following rules:
|
|||||||
adds a filter called ``BitBake.defaultFilter``, both filters will be
|
adds a filter called ``BitBake.defaultFilter``, both filters will be
|
||||||
applied to the logger
|
applied to the logger
|
||||||
|
|
||||||
As a first example, you can create a ``hashequiv.json`` user logging
|
As an example, consider the following user logging configuration file
|
||||||
configuration file to log all Hash Equivalence related messages of ``VERBOSE``
|
which logs all Hash Equivalence related messages of VERBOSE or higher to
|
||||||
or higher priority to a file called ``hashequiv.log``::
|
a file called ``hashequiv.log`` ::
|
||||||
|
|
||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
@@ -722,40 +731,3 @@ or higher priority to a file called ``hashequiv.log``::
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Then set the :term:`BB_LOGCONFIG` variable in ``conf/local.conf``::
|
|
||||||
|
|
||||||
BB_LOGCONFIG = "hashequiv.json"
|
|
||||||
|
|
||||||
Another example is this ``warn.json`` file to log all ``WARNING`` and
|
|
||||||
higher priority messages to a ``warn.log`` file::
|
|
||||||
|
|
||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"formatters": {
|
|
||||||
"warnlogFormatter": {
|
|
||||||
"()": "bb.msg.BBLogFormatter",
|
|
||||||
"format": "%(levelname)s: %(message)s"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"handlers": {
|
|
||||||
"warnlog": {
|
|
||||||
"class": "logging.FileHandler",
|
|
||||||
"formatter": "warnlogFormatter",
|
|
||||||
"level": "WARNING",
|
|
||||||
"filename": "warn.log"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"loggers": {
|
|
||||||
"BitBake": {
|
|
||||||
"handlers": ["warnlog"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"@disable_existing_loggers": false
|
|
||||||
}
|
|
||||||
|
|
||||||
Note that BitBake's helper classes for structured logging are implemented in
|
|
||||||
``lib/bb/msg.py``.
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ and unpacking the files is often optionally followed by patching.
|
|||||||
Patching, however, is not covered by this module.
|
Patching, however, is not covered by this module.
|
||||||
|
|
||||||
The code to execute the first part of this process, a fetch, looks
|
The code to execute the first part of this process, a fetch, looks
|
||||||
something like the following::
|
something like the following: ::
|
||||||
|
|
||||||
src_uri = (d.getVar('SRC_URI') or "").split()
|
src_uri = (d.getVar('SRC_URI') or "").split()
|
||||||
fetcher = bb.fetch2.Fetch(src_uri, d)
|
fetcher = bb.fetch2.Fetch(src_uri, d)
|
||||||
@@ -37,7 +37,7 @@ This code sets up an instance of the fetch class. The instance uses a
|
|||||||
space-separated list of URLs from the :term:`SRC_URI`
|
space-separated list of URLs from the :term:`SRC_URI`
|
||||||
variable and then calls the ``download`` method to download the files.
|
variable and then calls the ``download`` method to download the files.
|
||||||
|
|
||||||
The instantiation of the fetch class is usually followed by::
|
The instantiation of the fetch class is usually followed by: ::
|
||||||
|
|
||||||
rootdir = l.getVar('WORKDIR')
|
rootdir = l.getVar('WORKDIR')
|
||||||
fetcher.unpack(rootdir)
|
fetcher.unpack(rootdir)
|
||||||
@@ -51,7 +51,7 @@ This code unpacks the downloaded files to the specified by ``WORKDIR``.
|
|||||||
examine the OpenEmbedded class file ``base.bbclass``
|
examine the OpenEmbedded class file ``base.bbclass``
|
||||||
.
|
.
|
||||||
|
|
||||||
The :term:`SRC_URI` and ``WORKDIR`` variables are not hardcoded into the
|
The ``SRC_URI`` and ``WORKDIR`` variables are not hardcoded into the
|
||||||
fetcher, since those fetcher methods can be (and are) called with
|
fetcher, since those fetcher methods can be (and are) called with
|
||||||
different variable names. In OpenEmbedded for example, the shared state
|
different variable names. In OpenEmbedded for example, the shared state
|
||||||
(sstate) code uses the fetch module to fetch the sstate files.
|
(sstate) code uses the fetch module to fetch the sstate files.
|
||||||
@@ -64,38 +64,38 @@ URLs by looking for source files in a specific search order:
|
|||||||
:term:`PREMIRRORS` variable.
|
:term:`PREMIRRORS` variable.
|
||||||
|
|
||||||
- *Source URI:* If pre-mirrors fail, BitBake uses the original URL (e.g
|
- *Source URI:* If pre-mirrors fail, BitBake uses the original URL (e.g
|
||||||
from :term:`SRC_URI`).
|
from ``SRC_URI``).
|
||||||
|
|
||||||
- *Mirror Sites:* If fetch failures occur, BitBake next uses mirror
|
- *Mirror Sites:* If fetch failures occur, BitBake next uses mirror
|
||||||
locations as defined by the :term:`MIRRORS` variable.
|
locations as defined by the :term:`MIRRORS` variable.
|
||||||
|
|
||||||
For each URL passed to the fetcher, the fetcher calls the submodule that
|
For each URL passed to the fetcher, the fetcher calls the submodule that
|
||||||
handles that particular URL type. This behavior can be the source of
|
handles that particular URL type. This behavior can be the source of
|
||||||
some confusion when you are providing URLs for the :term:`SRC_URI` variable.
|
some confusion when you are providing URLs for the ``SRC_URI`` variable.
|
||||||
Consider the following two URLs::
|
Consider the following two URLs: ::
|
||||||
|
|
||||||
https://git.yoctoproject.org/git/poky;protocol=git
|
http://git.yoctoproject.org/git/poky;protocol=git
|
||||||
git://git.yoctoproject.org/git/poky;protocol=http
|
git://git.yoctoproject.org/git/poky;protocol=http
|
||||||
|
|
||||||
In the former case, the URL is passed to the ``wget`` fetcher, which does not
|
In the former case, the URL is passed to the ``wget`` fetcher, which does not
|
||||||
understand "git". Therefore, the latter case is the correct form since the Git
|
understand "git". Therefore, the latter case is the correct form since the Git
|
||||||
fetcher does know how to use HTTP as a transport.
|
fetcher does know how to use HTTP as a transport.
|
||||||
|
|
||||||
Here are some examples that show commonly used mirror definitions::
|
Here are some examples that show commonly used mirror definitions: ::
|
||||||
|
|
||||||
PREMIRRORS ?= "\
|
PREMIRRORS ?= "\
|
||||||
bzr://.*/.\* http://somemirror.org/sources/ \
|
bzr://.*/.\* http://somemirror.org/sources/ \\n \
|
||||||
cvs://.*/.\* http://somemirror.org/sources/ \
|
cvs://.*/.\* http://somemirror.org/sources/ \\n \
|
||||||
git://.*/.\* http://somemirror.org/sources/ \
|
git://.*/.\* http://somemirror.org/sources/ \\n \
|
||||||
hg://.*/.\* http://somemirror.org/sources/ \
|
hg://.*/.\* http://somemirror.org/sources/ \\n \
|
||||||
osc://.*/.\* http://somemirror.org/sources/ \
|
osc://.*/.\* http://somemirror.org/sources/ \\n \
|
||||||
p4://.*/.\* http://somemirror.org/sources/ \
|
p4://.*/.\* http://somemirror.org/sources/ \\n \
|
||||||
svn://.*/.\* http://somemirror.org/sources/"
|
svn://.*/.\* http://somemirror.org/sources/ \\n"
|
||||||
|
|
||||||
MIRRORS =+ "\
|
MIRRORS =+ "\
|
||||||
ftp://.*/.\* http://somemirror.org/sources/ \
|
ftp://.*/.\* http://somemirror.org/sources/ \\n \
|
||||||
http://.*/.\* http://somemirror.org/sources/ \
|
http://.*/.\* http://somemirror.org/sources/ \\n \
|
||||||
https://.*/.\* http://somemirror.org/sources/"
|
https://.*/.\* http://somemirror.org/sources/ \\n"
|
||||||
|
|
||||||
It is useful to note that BitBake
|
It is useful to note that BitBake
|
||||||
supports cross-URLs. It is possible to mirror a Git repository on an
|
supports cross-URLs. It is possible to mirror a Git repository on an
|
||||||
@@ -110,26 +110,26 @@ which is specified by the :term:`DL_DIR` variable.
|
|||||||
File integrity is of key importance for reproducing builds. For
|
File integrity is of key importance for reproducing builds. For
|
||||||
non-local archive downloads, the fetcher code can verify SHA-256 and MD5
|
non-local archive downloads, the fetcher code can verify SHA-256 and MD5
|
||||||
checksums to ensure the archives have been downloaded correctly. You can
|
checksums to ensure the archives have been downloaded correctly. You can
|
||||||
specify these checksums by using the :term:`SRC_URI` variable with the
|
specify these checksums by using the ``SRC_URI`` variable with the
|
||||||
appropriate varflags as follows::
|
appropriate varflags as follows: ::
|
||||||
|
|
||||||
SRC_URI[md5sum] = "value"
|
SRC_URI[md5sum] = "value"
|
||||||
SRC_URI[sha256sum] = "value"
|
SRC_URI[sha256sum] = "value"
|
||||||
|
|
||||||
You can also specify the checksums as
|
You can also specify the checksums as
|
||||||
parameters on the :term:`SRC_URI` as shown below::
|
parameters on the ``SRC_URI`` as shown below: ::
|
||||||
|
|
||||||
SRC_URI = "http://example.com/foobar.tar.bz2;md5sum=4a8e0f237e961fd7785d19d07fdb994d"
|
SRC_URI = "http://example.com/foobar.tar.bz2;md5sum=4a8e0f237e961fd7785d19d07fdb994d"
|
||||||
|
|
||||||
If multiple URIs exist, you can specify the checksums either directly as
|
If multiple URIs exist, you can specify the checksums either directly as
|
||||||
in the previous example, or you can name the URLs. The following syntax
|
in the previous example, or you can name the URLs. The following syntax
|
||||||
shows how you name the URIs::
|
shows how you name the URIs: ::
|
||||||
|
|
||||||
SRC_URI = "http://example.com/foobar.tar.bz2;name=foo"
|
SRC_URI = "http://example.com/foobar.tar.bz2;name=foo"
|
||||||
SRC_URI[foo.md5sum] = 4a8e0f237e961fd7785d19d07fdb994d
|
SRC_URI[foo.md5sum] = 4a8e0f237e961fd7785d19d07fdb994d
|
||||||
|
|
||||||
After a file has been downloaded and
|
After a file has been downloaded and
|
||||||
has had its checksum checked, a ".done" stamp is placed in :term:`DL_DIR`.
|
has had its checksum checked, a ".done" stamp is placed in ``DL_DIR``.
|
||||||
BitBake uses this stamp during subsequent builds to avoid downloading or
|
BitBake uses this stamp during subsequent builds to avoid downloading or
|
||||||
comparing a checksum for the file again.
|
comparing a checksum for the file again.
|
||||||
|
|
||||||
@@ -144,10 +144,6 @@ download without a checksum triggers an error message. The
|
|||||||
make any attempted network access a fatal error, which is useful for
|
make any attempted network access a fatal error, which is useful for
|
||||||
checking that mirrors are complete as well as other things.
|
checking that mirrors are complete as well as other things.
|
||||||
|
|
||||||
If :term:`BB_CHECK_SSL_CERTS` is set to ``0`` then SSL certificate checking will
|
|
||||||
be disabled. This variable defaults to ``1`` so SSL certificates are normally
|
|
||||||
checked.
|
|
||||||
|
|
||||||
.. _bb-the-unpack:
|
.. _bb-the-unpack:
|
||||||
|
|
||||||
The Unpack
|
The Unpack
|
||||||
@@ -167,8 +163,8 @@ govern the behavior of the unpack stage:
|
|||||||
- *dos:* Applies to ``.zip`` and ``.jar`` files and specifies whether
|
- *dos:* Applies to ``.zip`` and ``.jar`` files and specifies whether
|
||||||
to use DOS line ending conversion on text files.
|
to use DOS line ending conversion on text files.
|
||||||
|
|
||||||
- *striplevel:* Strip specified number of leading components (levels)
|
- *basepath:* Instructs the unpack stage to strip the specified
|
||||||
from file names on extraction
|
directories from the source path when unpacking.
|
||||||
|
|
||||||
- *subdir:* Unpacks the specific URL to the specified subdirectory
|
- *subdir:* Unpacks the specific URL to the specified subdirectory
|
||||||
within the root directory.
|
within the root directory.
|
||||||
@@ -208,7 +204,7 @@ time the ``download()`` method is called.
|
|||||||
If you specify a directory, the entire directory is unpacked.
|
If you specify a directory, the entire directory is unpacked.
|
||||||
|
|
||||||
Here are a couple of example URLs, the first relative and the second
|
Here are a couple of example URLs, the first relative and the second
|
||||||
absolute::
|
absolute: ::
|
||||||
|
|
||||||
SRC_URI = "file://relativefile.patch"
|
SRC_URI = "file://relativefile.patch"
|
||||||
SRC_URI = "file:///Users/ich/very_important_software"
|
SRC_URI = "file:///Users/ich/very_important_software"
|
||||||
@@ -229,12 +225,7 @@ downloaded file is useful for avoiding collisions in
|
|||||||
:term:`DL_DIR` when dealing with multiple files that
|
:term:`DL_DIR` when dealing with multiple files that
|
||||||
have the same name.
|
have the same name.
|
||||||
|
|
||||||
If a username and password are specified in the ``SRC_URI``, a Basic
|
Some example URLs are as follows: ::
|
||||||
Authorization header will be added to each request, including across redirects.
|
|
||||||
To instead limit the Authorization header to the first request, add
|
|
||||||
"redirectauth=0" to the list of parameters.
|
|
||||||
|
|
||||||
Some example URLs are as follows::
|
|
||||||
|
|
||||||
SRC_URI = "http://oe.handhelds.org/not_there.aac"
|
SRC_URI = "http://oe.handhelds.org/not_there.aac"
|
||||||
SRC_URI = "ftp://oe.handhelds.org/not_there_as_well.aac"
|
SRC_URI = "ftp://oe.handhelds.org/not_there_as_well.aac"
|
||||||
@@ -244,13 +235,15 @@ Some example URLs are as follows::
|
|||||||
|
|
||||||
Because URL parameters are delimited by semi-colons, this can
|
Because URL parameters are delimited by semi-colons, this can
|
||||||
introduce ambiguity when parsing URLs that also contain semi-colons,
|
introduce ambiguity when parsing URLs that also contain semi-colons,
|
||||||
for example::
|
for example:
|
||||||
|
::
|
||||||
|
|
||||||
SRC_URI = "http://abc123.org/git/?p=gcc/gcc.git;a=snapshot;h=a5dd47"
|
SRC_URI = "http://abc123.org/git/?p=gcc/gcc.git;a=snapshot;h=a5dd47"
|
||||||
|
|
||||||
|
|
||||||
Such URLs should should be modified by replacing semi-colons with '&'
|
Such URLs should should be modified by replacing semi-colons with '&'
|
||||||
characters::
|
characters:
|
||||||
|
::
|
||||||
|
|
||||||
SRC_URI = "http://abc123.org/git/?p=gcc/gcc.git&a=snapshot&h=a5dd47"
|
SRC_URI = "http://abc123.org/git/?p=gcc/gcc.git&a=snapshot&h=a5dd47"
|
||||||
|
|
||||||
@@ -258,7 +251,8 @@ Some example URLs are as follows::
|
|||||||
In most cases this should work. Treating semi-colons and '&' in
|
In most cases this should work. Treating semi-colons and '&' in
|
||||||
queries identically is recommended by the World Wide Web Consortium
|
queries identically is recommended by the World Wide Web Consortium
|
||||||
(W3C). Note that due to the nature of the URL, you may have to
|
(W3C). Note that due to the nature of the URL, you may have to
|
||||||
specify the name of the downloaded file as well::
|
specify the name of the downloaded file as well:
|
||||||
|
::
|
||||||
|
|
||||||
SRC_URI = "http://abc123.org/git/?p=gcc/gcc.git&a=snapshot&h=a5dd47;downloadfilename=myfile.bz2"
|
SRC_URI = "http://abc123.org/git/?p=gcc/gcc.git&a=snapshot&h=a5dd47;downloadfilename=myfile.bz2"
|
||||||
|
|
||||||
@@ -327,7 +321,7 @@ The supported parameters are as follows:
|
|||||||
|
|
||||||
- *"port":* The port to which the CVS server connects.
|
- *"port":* The port to which the CVS server connects.
|
||||||
|
|
||||||
Some example URLs are as follows::
|
Some example URLs are as follows: ::
|
||||||
|
|
||||||
SRC_URI = "cvs://CVSROOT;module=mymodule;tag=some-version;method=ext"
|
SRC_URI = "cvs://CVSROOT;module=mymodule;tag=some-version;method=ext"
|
||||||
SRC_URI = "cvs://CVSROOT;module=mymodule;date=20060126;localdir=usethat"
|
SRC_URI = "cvs://CVSROOT;module=mymodule;date=20060126;localdir=usethat"
|
||||||
@@ -369,7 +363,7 @@ The supported parameters are as follows:
|
|||||||
username is different than the username used in the main URL, which
|
username is different than the username used in the main URL, which
|
||||||
is passed to the subversion command.
|
is passed to the subversion command.
|
||||||
|
|
||||||
Following are three examples using svn::
|
Following are three examples using svn: ::
|
||||||
|
|
||||||
SRC_URI = "svn://myrepos/proj1;module=vip;protocol=http;rev=667"
|
SRC_URI = "svn://myrepos/proj1;module=vip;protocol=http;rev=667"
|
||||||
SRC_URI = "svn://myrepos/proj1;module=opie;protocol=svn+ssh"
|
SRC_URI = "svn://myrepos/proj1;module=opie;protocol=svn+ssh"
|
||||||
@@ -396,19 +390,6 @@ This fetcher supports the following parameters:
|
|||||||
protocol is "file". You can also use "http", "https", "ssh" and
|
protocol is "file". You can also use "http", "https", "ssh" and
|
||||||
"rsync".
|
"rsync".
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
When ``protocol`` is "ssh", the URL expected in :term:`SRC_URI` differs
|
|
||||||
from the one that is typically passed to ``git clone`` command and provided
|
|
||||||
by the Git server to fetch from. For example, the URL returned by GitLab
|
|
||||||
server for ``mesa`` when cloning over SSH is
|
|
||||||
``git@gitlab.freedesktop.org:mesa/mesa.git``, however the expected URL in
|
|
||||||
:term:`SRC_URI` is the following::
|
|
||||||
|
|
||||||
SRC_URI = "git://git@gitlab.freedesktop.org/mesa/mesa.git;branch=main;protocol=ssh;..."
|
|
||||||
|
|
||||||
Note the ``:`` character changed for a ``/`` before the path to the project.
|
|
||||||
|
|
||||||
- *"nocheckout":* Tells the fetcher to not checkout source code when
|
- *"nocheckout":* Tells the fetcher to not checkout source code when
|
||||||
unpacking when set to "1". Set this option for the URL where there is
|
unpacking when set to "1". Set this option for the URL where there is
|
||||||
a custom routine to checkout code. The default is "0".
|
a custom routine to checkout code. The default is "0".
|
||||||
@@ -424,17 +405,17 @@ This fetcher supports the following parameters:
|
|||||||
|
|
||||||
- *"nobranch":* Tells the fetcher to not check the SHA validation for
|
- *"nobranch":* Tells the fetcher to not check the SHA validation for
|
||||||
the branch when set to "1". The default is "0". Set this option for
|
the branch when set to "1". The default is "0". Set this option for
|
||||||
the recipe that refers to the commit that is valid for any namespace
|
the recipe that refers to the commit that is valid for a tag instead
|
||||||
(branch, tag, ...) instead of the branch.
|
of the branch.
|
||||||
|
|
||||||
- *"bareclone":* Tells the fetcher to clone a bare clone into the
|
- *"bareclone":* Tells the fetcher to clone a bare clone into the
|
||||||
destination directory without checking out a working tree. Only the
|
destination directory without checking out a working tree. Only the
|
||||||
raw Git metadata is provided. This parameter implies the "nocheckout"
|
raw Git metadata is provided. This parameter implies the "nocheckout"
|
||||||
parameter as well.
|
parameter as well.
|
||||||
|
|
||||||
- *"branch":* The branch(es) of the Git tree to clone. Unless
|
- *"branch":* The branch(es) of the Git tree to clone. If unset, this
|
||||||
"nobranch" is set to "1", this is a mandatory parameter. The number of
|
is assumed to be "master". The number of branch parameters much match
|
||||||
branch parameters must match the number of name parameters.
|
the number of name parameters.
|
||||||
|
|
||||||
- *"rev":* The revision to use for the checkout. The default is
|
- *"rev":* The revision to use for the checkout. The default is
|
||||||
"master".
|
"master".
|
||||||
@@ -455,35 +436,10 @@ This fetcher supports the following parameters:
|
|||||||
parameter implies no branch and only works when the transfer protocol
|
parameter implies no branch and only works when the transfer protocol
|
||||||
is ``file://``.
|
is ``file://``.
|
||||||
|
|
||||||
Here are some example URLs::
|
Here are some example URLs: ::
|
||||||
|
|
||||||
SRC_URI = "git://github.com/fronteed/icheck.git;protocol=https;branch=${PV};tag=${PV}"
|
SRC_URI = "git://git.oe.handhelds.org/git/vip.git;tag=version-1"
|
||||||
SRC_URI = "git://github.com/asciidoc/asciidoc-py;protocol=https;branch=main"
|
SRC_URI = "git://git.oe.handhelds.org/git/vip.git;protocol=http"
|
||||||
SRC_URI = "git://git@gitlab.freedesktop.org/mesa/mesa.git;branch=main;protocol=ssh;..."
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
When using ``git`` as the fetcher of the main source code of your software,
|
|
||||||
``S`` should be set accordingly::
|
|
||||||
|
|
||||||
S = "${WORKDIR}/git"
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Specifying passwords directly in ``git://`` urls is not supported.
|
|
||||||
There are several reasons: :term:`SRC_URI` is often written out to logs and
|
|
||||||
other places, and that could easily leak passwords; it is also all too
|
|
||||||
easy to share metadata without removing passwords. SSH keys, ``~/.netrc``
|
|
||||||
and ``~/.ssh/config`` files can be used as alternatives.
|
|
||||||
|
|
||||||
Using tags with the git fetcher may cause surprising behaviour. Bitbake needs to
|
|
||||||
resolve the tag to a specific revision and to do that, it has to connect to and use
|
|
||||||
the upstream repository. This is because the revision the tags point at can change and
|
|
||||||
we've seen cases of this happening in well known public repositories. This can mean
|
|
||||||
many more network connections than expected and recipes may be reparsed at every build.
|
|
||||||
Source mirrors will also be bypassed as the upstream repository is the only source
|
|
||||||
of truth to resolve the revision accurately. For these reasons, whilst the fetcher
|
|
||||||
can support tags, we recommend being specific about revisions in recipes.
|
|
||||||
|
|
||||||
.. _gitsm-fetcher:
|
.. _gitsm-fetcher:
|
||||||
|
|
||||||
@@ -519,7 +475,7 @@ repository.
|
|||||||
|
|
||||||
To use this fetcher, make sure your recipe has proper
|
To use this fetcher, make sure your recipe has proper
|
||||||
:term:`SRC_URI`, :term:`SRCREV`, and
|
:term:`SRC_URI`, :term:`SRCREV`, and
|
||||||
:term:`PV` settings. Here is an example::
|
:term:`PV` settings. Here is an example: ::
|
||||||
|
|
||||||
SRC_URI = "ccrc://cc.example.org/ccrc;vob=/example_vob;module=/example_module"
|
SRC_URI = "ccrc://cc.example.org/ccrc;vob=/example_vob;module=/example_module"
|
||||||
SRCREV = "EXAMPLE_CLEARCASE_TAG"
|
SRCREV = "EXAMPLE_CLEARCASE_TAG"
|
||||||
@@ -528,7 +484,7 @@ To use this fetcher, make sure your recipe has proper
|
|||||||
The fetcher uses the ``rcleartool`` or
|
The fetcher uses the ``rcleartool`` or
|
||||||
``cleartool`` remote client, depending on which one is available.
|
``cleartool`` remote client, depending on which one is available.
|
||||||
|
|
||||||
Following are options for the :term:`SRC_URI` statement:
|
Following are options for the ``SRC_URI`` statement:
|
||||||
|
|
||||||
- *vob*: The name, which must include the prepending "/" character,
|
- *vob*: The name, which must include the prepending "/" character,
|
||||||
of the ClearCase VOB. This option is required.
|
of the ClearCase VOB. This option is required.
|
||||||
@@ -541,7 +497,7 @@ Following are options for the :term:`SRC_URI` statement:
|
|||||||
The module and vob options are combined to create the load rule in the
|
The module and vob options are combined to create the load rule in the
|
||||||
view config spec. As an example, consider the vob and module values from
|
view config spec. As an example, consider the vob and module values from
|
||||||
the SRC_URI statement at the start of this section. Combining those values
|
the SRC_URI statement at the start of this section. Combining those values
|
||||||
results in the following::
|
results in the following: ::
|
||||||
|
|
||||||
load /example_vob/example_module
|
load /example_vob/example_module
|
||||||
|
|
||||||
@@ -590,10 +546,10 @@ password if you do not wish to keep those values in a recipe itself. If
|
|||||||
you choose not to use ``P4CONFIG``, or to explicitly set variables that
|
you choose not to use ``P4CONFIG``, or to explicitly set variables that
|
||||||
``P4CONFIG`` can contain, you can specify the ``P4PORT`` value, which is
|
``P4CONFIG`` can contain, you can specify the ``P4PORT`` value, which is
|
||||||
the server's URL and port number, and you can specify a username and
|
the server's URL and port number, and you can specify a username and
|
||||||
password directly in your recipe within :term:`SRC_URI`.
|
password directly in your recipe within ``SRC_URI``.
|
||||||
|
|
||||||
Here is an example that relies on ``P4CONFIG`` to specify the server URL
|
Here is an example that relies on ``P4CONFIG`` to specify the server URL
|
||||||
and port, username, and password, and fetches the Head Revision::
|
and port, username, and password, and fetches the Head Revision: ::
|
||||||
|
|
||||||
SRC_URI = "p4://example-depot/main/source/..."
|
SRC_URI = "p4://example-depot/main/source/..."
|
||||||
SRCREV = "${AUTOREV}"
|
SRCREV = "${AUTOREV}"
|
||||||
@@ -601,7 +557,7 @@ and port, username, and password, and fetches the Head Revision::
|
|||||||
S = "${WORKDIR}/p4"
|
S = "${WORKDIR}/p4"
|
||||||
|
|
||||||
Here is an example that specifies the server URL and port, username, and
|
Here is an example that specifies the server URL and port, username, and
|
||||||
password, and fetches a Revision based on a Label::
|
password, and fetches a Revision based on a Label: ::
|
||||||
|
|
||||||
P4PORT = "tcp:p4server.example.net:1666"
|
P4PORT = "tcp:p4server.example.net:1666"
|
||||||
SRC_URI = "p4://user:passwd@example-depot/main/source/..."
|
SRC_URI = "p4://user:passwd@example-depot/main/source/..."
|
||||||
@@ -627,7 +583,7 @@ paths locally is desirable, the fetcher supports two parameters:
|
|||||||
paths locally for the specified location, even in combination with the
|
paths locally for the specified location, even in combination with the
|
||||||
``module`` parameter.
|
``module`` parameter.
|
||||||
|
|
||||||
Here is an example use of the the ``module`` parameter::
|
Here is an example use of the the ``module`` parameter: ::
|
||||||
|
|
||||||
SRC_URI = "p4://user:passwd@example-depot/main;module=source/..."
|
SRC_URI = "p4://user:passwd@example-depot/main;module=source/..."
|
||||||
|
|
||||||
@@ -635,7 +591,7 @@ In this case, the content of the top-level directory ``source/`` will be fetched
|
|||||||
to ``${P4DIR}``, including the directory itself. The top-level directory will
|
to ``${P4DIR}``, including the directory itself. The top-level directory will
|
||||||
be accesible at ``${P4DIR}/source/``.
|
be accesible at ``${P4DIR}/source/``.
|
||||||
|
|
||||||
Here is an example use of the the ``remotepath`` parameter::
|
Here is an example use of the the ``remotepath`` parameter: ::
|
||||||
|
|
||||||
SRC_URI = "p4://user:passwd@example-depot/main;module=source/...;remotepath=keep"
|
SRC_URI = "p4://user:passwd@example-depot/main;module=source/...;remotepath=keep"
|
||||||
|
|
||||||
@@ -663,166 +619,11 @@ This fetcher supports the following parameters:
|
|||||||
|
|
||||||
- *"manifest":* Name of the manifest file (default: ``default.xml``).
|
- *"manifest":* Name of the manifest file (default: ``default.xml``).
|
||||||
|
|
||||||
Here are some example URLs::
|
Here are some example URLs: ::
|
||||||
|
|
||||||
SRC_URI = "repo://REPOROOT;protocol=git;branch=some_branch;manifest=my_manifest.xml"
|
SRC_URI = "repo://REPOROOT;protocol=git;branch=some_branch;manifest=my_manifest.xml"
|
||||||
SRC_URI = "repo://REPOROOT;protocol=file;branch=some_branch;manifest=my_manifest.xml"
|
SRC_URI = "repo://REPOROOT;protocol=file;branch=some_branch;manifest=my_manifest.xml"
|
||||||
|
|
||||||
.. _az-fetcher:
|
|
||||||
|
|
||||||
Az Fetcher (``az://``)
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
This submodule fetches data from an
|
|
||||||
`Azure Storage account <https://docs.microsoft.com/en-us/azure/storage/>`__ ,
|
|
||||||
it inherits its functionality from the HTTP wget fetcher, but modifies its
|
|
||||||
behavior to accomodate the usage of a
|
|
||||||
`Shared Access Signature (SAS) <https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview>`__
|
|
||||||
for non-public data.
|
|
||||||
|
|
||||||
Such functionality is set by the variable:
|
|
||||||
|
|
||||||
- :term:`AZ_SAS`: The Azure Storage Shared Access Signature provides secure
|
|
||||||
delegate access to resources, if this variable is set, the Az Fetcher will
|
|
||||||
use it when fetching artifacts from the cloud.
|
|
||||||
|
|
||||||
You can specify the AZ_SAS variable as shown below::
|
|
||||||
|
|
||||||
AZ_SAS = "se=2021-01-01&sp=r&sv=2018-11-09&sr=c&skoid=<skoid>&sig=<signature>"
|
|
||||||
|
|
||||||
Here is an example URL::
|
|
||||||
|
|
||||||
SRC_URI = "az://<azure-storage-account>.blob.core.windows.net/<foo_container>/<bar_file>"
|
|
||||||
|
|
||||||
It can also be used when setting mirrors definitions using the :term:`PREMIRRORS` variable.
|
|
||||||
|
|
||||||
.. _gcp-fetcher:
|
|
||||||
|
|
||||||
GCP Fetcher (``gs://``)
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
This submodule fetches data from a
|
|
||||||
`Google Cloud Storage Bucket <https://cloud.google.com/storage/docs/buckets>`__.
|
|
||||||
It uses the `Google Cloud Storage Python Client <https://cloud.google.com/python/docs/reference/storage/latest>`__
|
|
||||||
to check the status of objects in the bucket and download them.
|
|
||||||
The use of the Python client makes it substantially faster than using command
|
|
||||||
line tools such as gsutil.
|
|
||||||
|
|
||||||
The fetcher requires the Google Cloud Storage Python Client to be installed, along
|
|
||||||
with the gsutil tool.
|
|
||||||
|
|
||||||
The fetcher requires that the machine has valid credentials for accessing the
|
|
||||||
chosen bucket. Instructions for authentication can be found in the
|
|
||||||
`Google Cloud documentation <https://cloud.google.com/docs/authentication/provide-credentials-adc#local-dev>`__.
|
|
||||||
|
|
||||||
If it used from the OpenEmbedded build system, the fetcher can be used for
|
|
||||||
fetching sstate artifacts from a GCS bucket by specifying the
|
|
||||||
``SSTATE_MIRRORS`` variable as shown below::
|
|
||||||
|
|
||||||
SSTATE_MIRRORS ?= "\
|
|
||||||
file://.* gs://<bucket name>/PATH \
|
|
||||||
"
|
|
||||||
|
|
||||||
The fetcher can also be used in recipes::
|
|
||||||
|
|
||||||
SRC_URI = "gs://<bucket name>/<foo_container>/<bar_file>"
|
|
||||||
|
|
||||||
However, the checksum of the file should be also be provided::
|
|
||||||
|
|
||||||
SRC_URI[sha256sum] = "<sha256 string>"
|
|
||||||
|
|
||||||
.. _crate-fetcher:
|
|
||||||
|
|
||||||
Crate Fetcher (``crate://``)
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
This submodule fetches code for
|
|
||||||
`Rust language "crates" <https://doc.rust-lang.org/reference/glossary.html?highlight=crate#crate>`__
|
|
||||||
corresponding to Rust libraries and programs to compile. Such crates are typically shared
|
|
||||||
on https://crates.io/ but this fetcher supports other crate registries too.
|
|
||||||
|
|
||||||
The format for the :term:`SRC_URI` setting must be::
|
|
||||||
|
|
||||||
SRC_URI = "crate://REGISTRY/NAME/VERSION"
|
|
||||||
|
|
||||||
Here is an example URL::
|
|
||||||
|
|
||||||
SRC_URI = "crate://crates.io/glob/0.2.11"
|
|
||||||
|
|
||||||
.. _npm-fetcher:
|
|
||||||
|
|
||||||
NPM Fetcher (``npm://``)
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
This submodule fetches source code from an
|
|
||||||
`NPM <https://en.wikipedia.org/wiki/Npm_(software)>`__
|
|
||||||
Javascript package registry.
|
|
||||||
|
|
||||||
The format for the :term:`SRC_URI` setting must be::
|
|
||||||
|
|
||||||
SRC_URI = "npm://some.registry.url;ParameterA=xxx;ParameterB=xxx;..."
|
|
||||||
|
|
||||||
This fetcher supports the following parameters:
|
|
||||||
|
|
||||||
- *"package":* The NPM package name. This is a mandatory parameter.
|
|
||||||
|
|
||||||
- *"version":* The NPM package version. This is a mandatory parameter.
|
|
||||||
|
|
||||||
- *"downloadfilename":* Specifies the filename used when storing the downloaded file.
|
|
||||||
|
|
||||||
- *"destsuffix":* Specifies the directory to use to unpack the package (default: ``npm``).
|
|
||||||
|
|
||||||
Note that NPM fetcher only fetches the package source itself. The dependencies
|
|
||||||
can be fetched through the `npmsw-fetcher`_.
|
|
||||||
|
|
||||||
Here is an example URL with both fetchers::
|
|
||||||
|
|
||||||
SRC_URI = " \
|
|
||||||
npm://registry.npmjs.org/;package=cute-files;version=${PV} \
|
|
||||||
npmsw://${THISDIR}/${BPN}/npm-shrinkwrap.json \
|
|
||||||
"
|
|
||||||
|
|
||||||
See :yocto_docs:`Creating Node Package Manager (NPM) Packages
|
|
||||||
</dev-manual/packages.html#creating-node-package-manager-npm-packages>`
|
|
||||||
in the Yocto Project manual for details about using
|
|
||||||
:yocto_docs:`devtool <https://docs.yoctoproject.org/ref-manual/devtool-reference.html>`
|
|
||||||
to automatically create a recipe from an NPM URL.
|
|
||||||
|
|
||||||
.. _npmsw-fetcher:
|
|
||||||
|
|
||||||
NPM shrinkwrap Fetcher (``npmsw://``)
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
This submodule fetches source code from an
|
|
||||||
`NPM shrinkwrap <https://docs.npmjs.com/cli/v8/commands/npm-shrinkwrap>`__
|
|
||||||
description file, which lists the dependencies
|
|
||||||
of an NPM package while locking their versions.
|
|
||||||
|
|
||||||
The format for the :term:`SRC_URI` setting must be::
|
|
||||||
|
|
||||||
SRC_URI = "npmsw://some.registry.url;ParameterA=xxx;ParameterB=xxx;..."
|
|
||||||
|
|
||||||
This fetcher supports the following parameters:
|
|
||||||
|
|
||||||
- *"dev":* Set this parameter to ``1`` to install "devDependencies".
|
|
||||||
|
|
||||||
- *"destsuffix":* Specifies the directory to use to unpack the dependencies
|
|
||||||
(``${S}`` by default).
|
|
||||||
|
|
||||||
Note that the shrinkwrap file can also be provided by the recipe for
|
|
||||||
the package which has such dependencies, for example::
|
|
||||||
|
|
||||||
SRC_URI = " \
|
|
||||||
npm://registry.npmjs.org/;package=cute-files;version=${PV} \
|
|
||||||
npmsw://${THISDIR}/${BPN}/npm-shrinkwrap.json \
|
|
||||||
"
|
|
||||||
|
|
||||||
Such a file can automatically be generated using
|
|
||||||
:yocto_docs:`devtool <https://docs.yoctoproject.org/ref-manual/devtool-reference.html>`
|
|
||||||
as described in the :yocto_docs:`Creating Node Package Manager (NPM) Packages
|
|
||||||
</dev-manual/packages.html#creating-node-package-manager-npm-packages>`
|
|
||||||
section of the Yocto Project.
|
|
||||||
|
|
||||||
Other Fetchers
|
Other Fetchers
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
@@ -832,9 +633,9 @@ Fetch submodules also exist for the following:
|
|||||||
|
|
||||||
- Mercurial (``hg://``)
|
- Mercurial (``hg://``)
|
||||||
|
|
||||||
- OSC (``osc://``)
|
- npm (``npm://``)
|
||||||
|
|
||||||
- S3 (``s3://``)
|
- OSC (``osc://``)
|
||||||
|
|
||||||
- Secure FTP (``sftp://``)
|
- Secure FTP (``sftp://``)
|
||||||
|
|
||||||
@@ -848,4 +649,4 @@ submodules. However, you might find the code helpful and readable.
|
|||||||
Auto Revisions
|
Auto Revisions
|
||||||
==============
|
==============
|
||||||
|
|
||||||
We need to document ``AUTOREV`` and :term:`SRCREV_FORMAT` here.
|
We need to document ``AUTOREV`` and ``SRCREV_FORMAT`` here.
|
||||||
|
|||||||
@@ -18,32 +18,28 @@ it.
|
|||||||
Obtaining BitBake
|
Obtaining BitBake
|
||||||
=================
|
=================
|
||||||
|
|
||||||
See the :ref:`bitbake-user-manual/bitbake-user-manual-intro:obtaining bitbake` section for
|
See the :ref:`bitbake-user-manual/bitbake-user-manual-hello:obtaining bitbake` section for
|
||||||
information on how to obtain BitBake. Once you have the source code on
|
information on how to obtain BitBake. Once you have the source code on
|
||||||
your machine, the BitBake directory appears as follows::
|
your machine, the BitBake directory appears as follows: ::
|
||||||
|
|
||||||
$ ls -al
|
$ ls -al
|
||||||
total 108
|
total 100
|
||||||
drwxr-xr-x 9 fawkh 10000 4096 feb 24 12:10 .
|
drwxrwxr-x. 9 wmat wmat 4096 Jan 31 13:44 .
|
||||||
drwx------ 36 fawkh 10000 4096 mar 2 17:00 ..
|
drwxrwxr-x. 3 wmat wmat 4096 Feb 4 10:45 ..
|
||||||
-rw-r--r-- 1 fawkh 10000 365 feb 24 12:10 AUTHORS
|
-rw-rw-r--. 1 wmat wmat 365 Nov 26 04:55 AUTHORS
|
||||||
drwxr-xr-x 2 fawkh 10000 4096 feb 24 12:10 bin
|
drwxrwxr-x. 2 wmat wmat 4096 Nov 26 04:55 bin
|
||||||
-rw-r--r-- 1 fawkh 10000 16501 feb 24 12:10 ChangeLog
|
drwxrwxr-x. 4 wmat wmat 4096 Jan 31 13:44 build
|
||||||
drwxr-xr-x 2 fawkh 10000 4096 feb 24 12:10 classes
|
-rw-rw-r--. 1 wmat wmat 16501 Nov 26 04:55 ChangeLog
|
||||||
drwxr-xr-x 2 fawkh 10000 4096 feb 24 12:10 conf
|
drwxrwxr-x. 2 wmat wmat 4096 Nov 26 04:55 classes
|
||||||
drwxr-xr-x 5 fawkh 10000 4096 feb 24 12:10 contrib
|
drwxrwxr-x. 2 wmat wmat 4096 Nov 26 04:55 conf
|
||||||
drwxr-xr-x 6 fawkh 10000 4096 feb 24 12:10 doc
|
drwxrwxr-x. 3 wmat wmat 4096 Nov 26 04:55 contrib
|
||||||
drwxr-xr-x 8 fawkh 10000 4096 mar 2 16:26 .git
|
-rw-rw-r--. 1 wmat wmat 17987 Nov 26 04:55 COPYING
|
||||||
-rw-r--r-- 1 fawkh 10000 31 feb 24 12:10 .gitattributes
|
drwxrwxr-x. 3 wmat wmat 4096 Nov 26 04:55 doc
|
||||||
-rw-r--r-- 1 fawkh 10000 392 feb 24 12:10 .gitignore
|
-rw-rw-r--. 1 wmat wmat 69 Nov 26 04:55 .gitignore
|
||||||
drwxr-xr-x 13 fawkh 10000 4096 feb 24 12:11 lib
|
-rw-rw-r--. 1 wmat wmat 849 Nov 26 04:55 HEADER
|
||||||
-rw-r--r-- 1 fawkh 10000 1224 feb 24 12:10 LICENSE
|
drwxrwxr-x. 5 wmat wmat 4096 Jan 31 13:44 lib
|
||||||
-rw-r--r-- 1 fawkh 10000 15394 feb 24 12:10 LICENSE.GPL-2.0-only
|
-rw-rw-r--. 1 wmat wmat 195 Nov 26 04:55 MANIFEST.in
|
||||||
-rw-r--r-- 1 fawkh 10000 1286 feb 24 12:10 LICENSE.MIT
|
-rw-rw-r--. 1 wmat wmat 2887 Nov 26 04:55 TODO
|
||||||
-rw-r--r-- 1 fawkh 10000 229 feb 24 12:10 MANIFEST.in
|
|
||||||
-rw-r--r-- 1 fawkh 10000 2413 feb 24 12:10 README
|
|
||||||
-rw-r--r-- 1 fawkh 10000 43 feb 24 12:10 toaster-requirements.txt
|
|
||||||
-rw-r--r-- 1 fawkh 10000 2887 feb 24 12:10 TODO
|
|
||||||
|
|
||||||
At this point, you should have BitBake cloned to a directory that
|
At this point, you should have BitBake cloned to a directory that
|
||||||
matches the previous listing except for dates and user names.
|
matches the previous listing except for dates and user names.
|
||||||
@@ -53,10 +49,10 @@ Setting Up the BitBake Environment
|
|||||||
|
|
||||||
First, you need to be sure that you can run BitBake. Set your working
|
First, you need to be sure that you can run BitBake. Set your working
|
||||||
directory to where your local BitBake files are and run the following
|
directory to where your local BitBake files are and run the following
|
||||||
command::
|
command: ::
|
||||||
|
|
||||||
$ ./bin/bitbake --version
|
$ ./bin/bitbake --version
|
||||||
BitBake Build Tool Core version 2.3.1
|
BitBake Build Tool Core version 1.23.0, bitbake version 1.23.0
|
||||||
|
|
||||||
The console output tells you what version
|
The console output tells you what version
|
||||||
you are running.
|
you are running.
|
||||||
@@ -65,14 +61,14 @@ The recommended method to run BitBake is from a directory of your
|
|||||||
choice. To be able to run BitBake from any directory, you need to add
|
choice. To be able to run BitBake from any directory, you need to add
|
||||||
the executable binary to your binary to your shell's environment
|
the executable binary to your binary to your shell's environment
|
||||||
``PATH`` variable. First, look at your current ``PATH`` variable by
|
``PATH`` variable. First, look at your current ``PATH`` variable by
|
||||||
entering the following::
|
entering the following: ::
|
||||||
|
|
||||||
$ echo $PATH
|
$ echo $PATH
|
||||||
|
|
||||||
Next, add the directory location
|
Next, add the directory location
|
||||||
for the BitBake binary to the ``PATH``. Here is an example that adds the
|
for the BitBake binary to the ``PATH``. Here is an example that adds the
|
||||||
``/home/scott-lenovo/bitbake/bin`` directory to the front of the
|
``/home/scott-lenovo/bitbake/bin`` directory to the front of the
|
||||||
``PATH`` variable::
|
``PATH`` variable: ::
|
||||||
|
|
||||||
$ export PATH=/home/scott-lenovo/bitbake/bin:$PATH
|
$ export PATH=/home/scott-lenovo/bitbake/bin:$PATH
|
||||||
|
|
||||||
@@ -103,7 +99,7 @@ discussion mailing list about the BitBake build tool.
|
|||||||
|
|
||||||
This example was inspired by and drew heavily from
|
This example was inspired by and drew heavily from
|
||||||
`Mailing List post - The BitBake equivalent of "Hello, World!"
|
`Mailing List post - The BitBake equivalent of "Hello, World!"
|
||||||
<https://www.mail-archive.com/yocto@yoctoproject.org/msg09379.html>`_.
|
<http://www.mail-archive.com/yocto@yoctoproject.org/msg09379.html>`_.
|
||||||
|
|
||||||
As stated earlier, the goal of this example is to eventually compile
|
As stated earlier, the goal of this example is to eventually compile
|
||||||
"Hello World". However, it is unknown what BitBake needs and what you
|
"Hello World". However, it is unknown what BitBake needs and what you
|
||||||
@@ -120,7 +116,7 @@ Following is the complete "Hello World" example.
|
|||||||
|
|
||||||
#. **Create a Project Directory:** First, set up a directory for the
|
#. **Create a Project Directory:** First, set up a directory for the
|
||||||
"Hello World" project. Here is how you can do so in your home
|
"Hello World" project. Here is how you can do so in your home
|
||||||
directory::
|
directory: ::
|
||||||
|
|
||||||
$ mkdir ~/hello
|
$ mkdir ~/hello
|
||||||
$ cd ~/hello
|
$ cd ~/hello
|
||||||
@@ -131,26 +127,41 @@ Following is the complete "Hello World" example.
|
|||||||
directory is a good way to isolate your project.
|
directory is a good way to isolate your project.
|
||||||
|
|
||||||
#. **Run BitBake:** At this point, you have nothing but a project
|
#. **Run BitBake:** At this point, you have nothing but a project
|
||||||
directory. Run the ``bitbake`` command and see what it does::
|
directory. Run the ``bitbake`` command and see what it does: ::
|
||||||
|
|
||||||
$ bitbake
|
$ bitbake
|
||||||
ERROR: The BBPATH variable is not set and bitbake did not find a conf/bblayers.conf file in the expected location.
|
The BBPATH variable is not set and bitbake did not
|
||||||
|
find a conf/bblayers.conf file in the expected location.
|
||||||
Maybe you accidentally invoked bitbake from the wrong directory?
|
Maybe you accidentally invoked bitbake from the wrong directory?
|
||||||
|
DEBUG: Removed the following variables from the environment:
|
||||||
|
GNOME_DESKTOP_SESSION_ID, XDG_CURRENT_DESKTOP,
|
||||||
|
GNOME_KEYRING_CONTROL, DISPLAY, SSH_AGENT_PID, LANG, no_proxy,
|
||||||
|
XDG_SESSION_PATH, XAUTHORITY, SESSION_MANAGER, SHLVL,
|
||||||
|
MANDATORY_PATH, COMPIZ_CONFIG_PROFILE, WINDOWID, EDITOR,
|
||||||
|
GPG_AGENT_INFO, SSH_AUTH_SOCK, GDMSESSION, GNOME_KEYRING_PID,
|
||||||
|
XDG_SEAT_PATH, XDG_CONFIG_DIRS, LESSOPEN, DBUS_SESSION_BUS_ADDRESS,
|
||||||
|
_, XDG_SESSION_COOKIE, DESKTOP_SESSION, LESSCLOSE, DEFAULTS_PATH,
|
||||||
|
UBUNTU_MENUPROXY, OLDPWD, XDG_DATA_DIRS, COLORTERM, LS_COLORS
|
||||||
|
|
||||||
|
The majority of this output is specific to environment variables that
|
||||||
|
are not directly relevant to BitBake. However, the very first
|
||||||
|
message regarding the ``BBPATH`` variable and the
|
||||||
|
``conf/bblayers.conf`` file is relevant.
|
||||||
|
|
||||||
When you run BitBake, it begins looking for metadata files. The
|
When you run BitBake, it begins looking for metadata files. The
|
||||||
:term:`BBPATH` variable is what tells BitBake where
|
:term:`BBPATH` variable is what tells BitBake where
|
||||||
to look for those files. :term:`BBPATH` is not set and you need to set
|
to look for those files. ``BBPATH`` is not set and you need to set
|
||||||
it. Without :term:`BBPATH`, BitBake cannot find any configuration files
|
it. Without ``BBPATH``, BitBake cannot find any configuration files
|
||||||
(``.conf``) or recipe files (``.bb``) at all. BitBake also cannot
|
(``.conf``) or recipe files (``.bb``) at all. BitBake also cannot
|
||||||
find the ``bitbake.conf`` file.
|
find the ``bitbake.conf`` file.
|
||||||
|
|
||||||
#. **Setting BBPATH:** For this example, you can set :term:`BBPATH` in
|
#. **Setting BBPATH:** For this example, you can set ``BBPATH`` in
|
||||||
the same manner that you set ``PATH`` earlier in the appendix. You
|
the same manner that you set ``PATH`` earlier in the appendix. You
|
||||||
should realize, though, that it is much more flexible to set the
|
should realize, though, that it is much more flexible to set the
|
||||||
:term:`BBPATH` variable up in a configuration file for each project.
|
``BBPATH`` variable up in a configuration file for each project.
|
||||||
|
|
||||||
From your shell, enter the following commands to set and export the
|
From your shell, enter the following commands to set and export the
|
||||||
:term:`BBPATH` variable::
|
``BBPATH`` variable: ::
|
||||||
|
|
||||||
$ BBPATH="projectdirectory"
|
$ BBPATH="projectdirectory"
|
||||||
$ export BBPATH
|
$ export BBPATH
|
||||||
@@ -164,18 +175,24 @@ Following is the complete "Hello World" example.
|
|||||||
("~") character as BitBake does not expand that character as the
|
("~") character as BitBake does not expand that character as the
|
||||||
shell would.
|
shell would.
|
||||||
|
|
||||||
#. **Run BitBake:** Now that you have :term:`BBPATH` defined, run the
|
#. **Run BitBake:** Now that you have ``BBPATH`` defined, run the
|
||||||
``bitbake`` command again::
|
``bitbake`` command again: ::
|
||||||
|
|
||||||
$ bitbake
|
$ bitbake
|
||||||
ERROR: Unable to parse /home/scott-lenovo/bitbake/lib/bb/parse/__init__.py
|
ERROR: Traceback (most recent call last):
|
||||||
Traceback (most recent call last):
|
File "/home/scott-lenovo/bitbake/lib/bb/cookerdata.py", line 163, in wrapped
|
||||||
File "/home/scott-lenovo/bitbake/lib/bb/parse/__init__.py", line 127, in resolve_file(fn='conf/bitbake.conf', d=<bb.data_smart.DataSmart object at 0x7f22919a3df0>):
|
return func(fn, *args)
|
||||||
if not newfn:
|
File "/home/scott-lenovo/bitbake/lib/bb/cookerdata.py", line 173, in parse_config_file
|
||||||
> raise IOError(errno.ENOENT, "file %s not found in %s" % (fn, bbpath))
|
return bb.parse.handle(fn, data, include)
|
||||||
fn = newfn
|
File "/home/scott-lenovo/bitbake/lib/bb/parse/__init__.py", line 99, in handle
|
||||||
FileNotFoundError: [Errno 2] file conf/bitbake.conf not found in <projectdirectory>
|
return h['handle'](fn, data, include)
|
||||||
|
File "/home/scott-lenovo/bitbake/lib/bb/parse/parse_py/ConfHandler.py", line 120, in handle
|
||||||
|
abs_fn = resolve_file(fn, data)
|
||||||
|
File "/home/scott-lenovo/bitbake/lib/bb/parse/__init__.py", line 117, in resolve_file
|
||||||
|
raise IOError("file %s not found in %s" % (fn, bbpath))
|
||||||
|
IOError: file conf/bitbake.conf not found in /home/scott-lenovo/hello
|
||||||
|
|
||||||
|
ERROR: Unable to parse conf/bitbake.conf: file conf/bitbake.conf not found in /home/scott-lenovo/hello
|
||||||
|
|
||||||
This sample output shows that BitBake could not find the
|
This sample output shows that BitBake could not find the
|
||||||
``conf/bitbake.conf`` file in the project directory. This file is
|
``conf/bitbake.conf`` file in the project directory. This file is
|
||||||
@@ -188,18 +205,18 @@ Following is the complete "Hello World" example.
|
|||||||
recipe files. For this example, you need to create the file in your
|
recipe files. For this example, you need to create the file in your
|
||||||
project directory and define some key BitBake variables. For more
|
project directory and define some key BitBake variables. For more
|
||||||
information on the ``bitbake.conf`` file, see
|
information on the ``bitbake.conf`` file, see
|
||||||
https://git.openembedded.org/bitbake/tree/conf/bitbake.conf.
|
http://git.openembedded.org/bitbake/tree/conf/bitbake.conf.
|
||||||
|
|
||||||
Use the following commands to create the ``conf`` directory in the
|
Use the following commands to create the ``conf`` directory in the
|
||||||
project directory::
|
project directory: ::
|
||||||
|
|
||||||
$ mkdir conf
|
$ mkdir conf
|
||||||
|
|
||||||
From within the ``conf`` directory,
|
From within the ``conf`` directory,
|
||||||
use some editor to create the ``bitbake.conf`` so that it contains
|
use some editor to create the ``bitbake.conf`` so that it contains
|
||||||
the following::
|
the following: ::
|
||||||
|
|
||||||
PN = "${@bb.parse.vars_from_file(d.getVar('FILE', False),d)[0] or 'defaultpkgname'}"
|
PN = "${@bb.parse.BBHandler.vars_from_file(d.getVar('FILE', False),d)[0] or 'defaultpkgname'}"
|
||||||
|
|
||||||
TMPDIR = "${TOPDIR}/tmp"
|
TMPDIR = "${TOPDIR}/tmp"
|
||||||
CACHE = "${TMPDIR}/cache"
|
CACHE = "${TMPDIR}/cache"
|
||||||
@@ -209,12 +226,12 @@ Following is the complete "Hello World" example.
|
|||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Without a value for :term:`PN`, the variables :term:`STAMP`, :term:`T`, and :term:`B`, prevent more
|
Without a value for PN , the variables STAMP , T , and B , prevent more
|
||||||
than one recipe from working. You can fix this by either setting :term:`PN` to
|
than one recipe from working. You can fix this by either setting PN to
|
||||||
have a value similar to what OpenEmbedded and BitBake use in the default
|
have a value similar to what OpenEmbedded and BitBake use in the default
|
||||||
``bitbake.conf`` file (see previous example). Or, by manually updating each
|
bitbake.conf file (see previous example). Or, by manually updating each
|
||||||
recipe to set :term:`PN`. You will also need to include :term:`PN` as part of the :term:`STAMP`,
|
recipe to set PN . You will also need to include PN as part of the STAMP
|
||||||
:term:`T`, and :term:`B` variable definitions in the ``local.conf`` file.
|
, T , and B variable definitions in the local.conf file.
|
||||||
|
|
||||||
The ``TMPDIR`` variable establishes a directory that BitBake uses
|
The ``TMPDIR`` variable establishes a directory that BitBake uses
|
||||||
for build output and intermediate files other than the cached
|
for build output and intermediate files other than the cached
|
||||||
@@ -234,17 +251,21 @@ Following is the complete "Hello World" example.
|
|||||||
glossary.
|
glossary.
|
||||||
|
|
||||||
#. **Run BitBake:** After making sure that the ``conf/bitbake.conf`` file
|
#. **Run BitBake:** After making sure that the ``conf/bitbake.conf`` file
|
||||||
exists, you can run the ``bitbake`` command again::
|
exists, you can run the ``bitbake`` command again: ::
|
||||||
|
|
||||||
$ bitbake
|
$ bitbake
|
||||||
ERROR: Unable to parse /home/scott-lenovo/bitbake/lib/bb/parse/parse_py/BBHandler.py
|
ERROR: Traceback (most recent call last):
|
||||||
Traceback (most recent call last):
|
File "/home/scott-lenovo/bitbake/lib/bb/cookerdata.py", line 163, in wrapped
|
||||||
File "/home/scott-lenovo/bitbake/lib/bb/parse/parse_py/BBHandler.py", line 67, in inherit(files=['base'], fn='configuration INHERITs', lineno=0, d=<bb.data_smart.DataSmart object at 0x7fab6815edf0>):
|
return func(fn, *args)
|
||||||
if not os.path.exists(file):
|
File "/home/scott-lenovo/bitbake/lib/bb/cookerdata.py", line 177, in _inherit
|
||||||
> raise ParseError("Could not inherit file %s" % (file), fn, lineno)
|
bb.parse.BBHandler.inherit(bbclass, "configuration INHERITs", 0, data)
|
||||||
|
File "/home/scott-lenovo/bitbake/lib/bb/parse/parse_py/BBHandler.py", line 92, in inherit
|
||||||
bb.parse.ParseError: ParseError in configuration INHERITs: Could not inherit file classes/base.bbclass
|
include(fn, file, lineno, d, "inherit")
|
||||||
|
File "/home/scott-lenovo/bitbake/lib/bb/parse/parse_py/ConfHandler.py", line 100, in include
|
||||||
|
raise ParseError("Could not %(error_out)s file %(fn)s" % vars(), oldfn, lineno)
|
||||||
|
ParseError: ParseError in configuration INHERITs: Could not inherit file classes/base.bbclass
|
||||||
|
|
||||||
|
ERROR: Unable to parse base: ParseError in configuration INHERITs: Could not inherit file classes/base.bbclass
|
||||||
|
|
||||||
In the sample output,
|
In the sample output,
|
||||||
BitBake could not find the ``classes/base.bbclass`` file. You need
|
BitBake could not find the ``classes/base.bbclass`` file. You need
|
||||||
@@ -257,23 +278,20 @@ Following is the complete "Hello World" example.
|
|||||||
in the ``classes`` directory of the project (i.e ``hello/classes``
|
in the ``classes`` directory of the project (i.e ``hello/classes``
|
||||||
in this example).
|
in this example).
|
||||||
|
|
||||||
Create the ``classes`` directory as follows::
|
Create the ``classes`` directory as follows: ::
|
||||||
|
|
||||||
$ cd $HOME/hello
|
$ cd $HOME/hello
|
||||||
$ mkdir classes
|
$ mkdir classes
|
||||||
|
|
||||||
Move to the ``classes`` directory and then create the
|
Move to the ``classes`` directory and then create the
|
||||||
``base.bbclass`` file by inserting this single line::
|
``base.bbclass`` file by inserting this single line: addtask build
|
||||||
|
|
||||||
addtask build
|
|
||||||
|
|
||||||
The minimal task that BitBake runs is the ``do_build`` task. This is
|
The minimal task that BitBake runs is the ``do_build`` task. This is
|
||||||
all the example needs in order to build the project. Of course, the
|
all the example needs in order to build the project. Of course, the
|
||||||
``base.bbclass`` can have much more depending on which build
|
``base.bbclass`` can have much more depending on which build
|
||||||
environments BitBake is supporting.
|
environments BitBake is supporting.
|
||||||
|
|
||||||
#. **Run BitBake:** After making sure that the ``classes/base.bbclass``
|
#. **Run BitBake:** After making sure that the ``classes/base.bbclass``
|
||||||
file exists, you can run the ``bitbake`` command again::
|
file exists, you can run the ``bitbake`` command again: ::
|
||||||
|
|
||||||
$ bitbake
|
$ bitbake
|
||||||
Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.
|
Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.
|
||||||
@@ -296,7 +314,7 @@ Following is the complete "Hello World" example.
|
|||||||
Minimally, you need a recipe file and a layer configuration file in
|
Minimally, you need a recipe file and a layer configuration file in
|
||||||
your layer. The configuration file needs to be in the ``conf``
|
your layer. The configuration file needs to be in the ``conf``
|
||||||
directory inside the layer. Use these commands to set up the layer
|
directory inside the layer. Use these commands to set up the layer
|
||||||
and the ``conf`` directory::
|
and the ``conf`` directory: ::
|
||||||
|
|
||||||
$ cd $HOME
|
$ cd $HOME
|
||||||
$ mkdir mylayer
|
$ mkdir mylayer
|
||||||
@@ -304,29 +322,20 @@ Following is the complete "Hello World" example.
|
|||||||
$ mkdir conf
|
$ mkdir conf
|
||||||
|
|
||||||
Move to the ``conf`` directory and create a ``layer.conf`` file that has the
|
Move to the ``conf`` directory and create a ``layer.conf`` file that has the
|
||||||
following::
|
following: ::
|
||||||
|
|
||||||
BBPATH .= ":${LAYERDIR}"
|
BBPATH .= ":${LAYERDIR}"
|
||||||
BBFILES += "${LAYERDIR}/*.bb"
|
BBFILES += "${LAYERDIR}/\*.bb"
|
||||||
BBFILE_COLLECTIONS += "mylayer"
|
BBFILE_COLLECTIONS += "mylayer"
|
||||||
BBFILE_PATTERN_mylayer := "^${LAYERDIR_RE}/"
|
`BBFILE_PATTERN_mylayer := "^${LAYERDIR_RE}/"
|
||||||
LAYERSERIES_CORENAMES = "hello_world_example"
|
|
||||||
LAYERSERIES_COMPAT_mylayer = "hello_world_example"
|
|
||||||
|
|
||||||
For information on these variables, click on :term:`BBFILES`,
|
For information on these variables, click on :term:`BBFILES`,
|
||||||
:term:`LAYERDIR`, :term:`BBFILE_COLLECTIONS`, :term:`BBFILE_PATTERN_mylayer <BBFILE_PATTERN>`
|
:term:`LAYERDIR`, :term:`BBFILE_COLLECTIONS` or :term:`BBFILE_PATTERN_mylayer <BBFILE_PATTERN>`
|
||||||
or :term:`LAYERSERIES_COMPAT` to go to the definitions in the glossary.
|
to go to the definitions in the glossary.
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
We are setting both ``LAYERSERIES_CORENAMES`` and :term:`LAYERSERIES_COMPAT` in this particular case, because we
|
|
||||||
are using bitbake without OpenEmbedded.
|
|
||||||
You should usually just use :term:`LAYERSERIES_COMPAT` to specify the OE-Core versions for which your layer
|
|
||||||
is compatible, and add the meta-openembedded layer to your project.
|
|
||||||
|
|
||||||
You need to create the recipe file next. Inside your layer at the
|
You need to create the recipe file next. Inside your layer at the
|
||||||
top-level, use an editor and create a recipe file named
|
top-level, use an editor and create a recipe file named
|
||||||
``printhello.bb`` that has the following::
|
``printhello.bb`` that has the following: ::
|
||||||
|
|
||||||
DESCRIPTION = "Prints Hello World"
|
DESCRIPTION = "Prints Hello World"
|
||||||
PN = 'printhello'
|
PN = 'printhello'
|
||||||
@@ -347,7 +356,7 @@ Following is the complete "Hello World" example.
|
|||||||
follow the links to the glossary.
|
follow the links to the glossary.
|
||||||
|
|
||||||
#. **Run BitBake With a Target:** Now that a BitBake target exists, run
|
#. **Run BitBake With a Target:** Now that a BitBake target exists, run
|
||||||
the command and provide that target::
|
the command and provide that target: ::
|
||||||
|
|
||||||
$ cd $HOME/hello
|
$ cd $HOME/hello
|
||||||
$ bitbake printhello
|
$ bitbake printhello
|
||||||
@@ -367,7 +376,7 @@ Following is the complete "Hello World" example.
|
|||||||
``hello/conf`` for this example).
|
``hello/conf`` for this example).
|
||||||
|
|
||||||
Set your working directory to the ``hello/conf`` directory and then
|
Set your working directory to the ``hello/conf`` directory and then
|
||||||
create the ``bblayers.conf`` file so that it contains the following::
|
create the ``bblayers.conf`` file so that it contains the following: ::
|
||||||
|
|
||||||
BBLAYERS ?= " \
|
BBLAYERS ?= " \
|
||||||
/home/<you>/mylayer \
|
/home/<you>/mylayer \
|
||||||
@@ -377,17 +386,15 @@ Following is the complete "Hello World" example.
|
|||||||
|
|
||||||
#. **Run BitBake With a Target:** Now that you have supplied the
|
#. **Run BitBake With a Target:** Now that you have supplied the
|
||||||
``bblayers.conf`` file, run the ``bitbake`` command and provide the
|
``bblayers.conf`` file, run the ``bitbake`` command and provide the
|
||||||
target::
|
target: ::
|
||||||
|
|
||||||
$ bitbake printhello
|
$ bitbake printhello
|
||||||
Loading cache: 100% |
|
|
||||||
Loaded 0 entries from dependency cache.
|
|
||||||
Parsing recipes: 100% |##################################################################################|
|
Parsing recipes: 100% |##################################################################################|
|
||||||
|
Time: 00:00:00
|
||||||
Parsing of 1 .bb files complete (0 cached, 1 parsed). 1 targets, 0 skipped, 0 masked, 0 errors.
|
Parsing of 1 .bb files complete (0 cached, 1 parsed). 1 targets, 0 skipped, 0 masked, 0 errors.
|
||||||
NOTE: Resolving any missing task queue dependencies
|
NOTE: Resolving any missing task queue dependencies
|
||||||
Initialising tasks: 100% |###############################################################################|
|
NOTE: Preparing RunQueue
|
||||||
NOTE: No setscene tasks
|
NOTE: Executing RunQueue Tasks
|
||||||
NOTE: Executing Tasks
|
|
||||||
********************
|
********************
|
||||||
* *
|
* *
|
||||||
* Hello, World! *
|
* Hello, World! *
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ Linux software stacks using a task-oriented approach.
|
|||||||
Conceptually, BitBake is similar to GNU Make in some regards but has
|
Conceptually, BitBake is similar to GNU Make in some regards but has
|
||||||
significant differences:
|
significant differences:
|
||||||
|
|
||||||
- BitBake executes tasks according to the provided metadata that builds up
|
- BitBake executes tasks according to provided metadata that builds up
|
||||||
the tasks. Metadata is stored in recipe (``.bb``) and related recipe
|
the tasks. Metadata is stored in recipe (``.bb``) and related recipe
|
||||||
"append" (``.bbappend``) files, configuration (``.conf``) and
|
"append" (``.bbappend``) files, configuration (``.conf``) and
|
||||||
underlying include (``.inc``) files, and in class (``.bbclass``)
|
underlying include (``.inc``) files, and in class (``.bbclass``)
|
||||||
@@ -60,10 +60,11 @@ member Chris Larson split the project into two distinct pieces:
|
|||||||
- OpenEmbedded, a metadata set utilized by BitBake
|
- OpenEmbedded, a metadata set utilized by BitBake
|
||||||
|
|
||||||
Today, BitBake is the primary basis of the
|
Today, BitBake is the primary basis of the
|
||||||
`OpenEmbedded <https://www.openembedded.org/>`__ project, which is being
|
`OpenEmbedded <http://www.openembedded.org/>`__ project, which is being
|
||||||
used to build and maintain Linux distributions such as the `Poky
|
used to build and maintain Linux distributions such as the `Angstrom
|
||||||
Reference Distribution <https://www.yoctoproject.org/software-item/poky/>`__,
|
Distribution <http://www.angstrom-distribution.org/>`__, and which is
|
||||||
developed under the umbrella of the `Yocto Project <https://www.yoctoproject.org>`__.
|
also being used as the build tool for Linux projects such as the `Yocto
|
||||||
|
Project <http://www.yoctoproject.org>`__.
|
||||||
|
|
||||||
Prior to BitBake, no other build tool adequately met the needs of an
|
Prior to BitBake, no other build tool adequately met the needs of an
|
||||||
aspiring embedded Linux distribution. All of the build systems used by
|
aspiring embedded Linux distribution. All of the build systems used by
|
||||||
@@ -247,13 +248,13 @@ underlying, similarly-named recipe files.
|
|||||||
|
|
||||||
When you name an append file, you can use the "``%``" wildcard character
|
When you name an append file, you can use the "``%``" wildcard character
|
||||||
to allow for matching recipe names. For example, suppose you have an
|
to allow for matching recipe names. For example, suppose you have an
|
||||||
append file named as follows::
|
append file named as follows: ::
|
||||||
|
|
||||||
busybox_1.21.%.bbappend
|
busybox_1.21.%.bbappend
|
||||||
|
|
||||||
That append file
|
That append file
|
||||||
would match any ``busybox_1.21.``\ x\ ``.bb`` version of the recipe. So,
|
would match any ``busybox_1.21.``\ x\ ``.bb`` version of the recipe. So,
|
||||||
the append file would match the following recipe names::
|
the append file would match the following recipe names: ::
|
||||||
|
|
||||||
busybox_1.21.1.bb
|
busybox_1.21.1.bb
|
||||||
busybox_1.21.2.bb
|
busybox_1.21.2.bb
|
||||||
@@ -289,7 +290,7 @@ You can obtain BitBake several different ways:
|
|||||||
are using. The metadata is generally backwards compatible but not
|
are using. The metadata is generally backwards compatible but not
|
||||||
forward compatible.
|
forward compatible.
|
||||||
|
|
||||||
Here is an example that clones the BitBake repository::
|
Here is an example that clones the BitBake repository: ::
|
||||||
|
|
||||||
$ git clone git://git.openembedded.org/bitbake
|
$ git clone git://git.openembedded.org/bitbake
|
||||||
|
|
||||||
@@ -297,7 +298,7 @@ You can obtain BitBake several different ways:
|
|||||||
Git repository into a directory called ``bitbake``. Alternatively,
|
Git repository into a directory called ``bitbake``. Alternatively,
|
||||||
you can designate a directory after the ``git clone`` command if you
|
you can designate a directory after the ``git clone`` command if you
|
||||||
want to call the new directory something other than ``bitbake``. Here
|
want to call the new directory something other than ``bitbake``. Here
|
||||||
is an example that names the directory ``bbdev``::
|
is an example that names the directory ``bbdev``: ::
|
||||||
|
|
||||||
$ git clone git://git.openembedded.org/bitbake bbdev
|
$ git clone git://git.openembedded.org/bitbake bbdev
|
||||||
|
|
||||||
@@ -316,9 +317,9 @@ You can obtain BitBake several different ways:
|
|||||||
method for getting BitBake. Cloning the repository makes it easier
|
method for getting BitBake. Cloning the repository makes it easier
|
||||||
to update as patches are added to the stable branches.
|
to update as patches are added to the stable branches.
|
||||||
|
|
||||||
The following example downloads a snapshot of BitBake version 1.17.0::
|
The following example downloads a snapshot of BitBake version 1.17.0: ::
|
||||||
|
|
||||||
$ wget https://git.openembedded.org/bitbake/snapshot/bitbake-1.17.0.tar.gz
|
$ wget http://git.openembedded.org/bitbake/snapshot/bitbake-1.17.0.tar.gz
|
||||||
$ tar zxpvf bitbake-1.17.0.tar.gz
|
$ tar zxpvf bitbake-1.17.0.tar.gz
|
||||||
|
|
||||||
After extraction of the tarball using
|
After extraction of the tarball using
|
||||||
@@ -346,7 +347,7 @@ execution examples.
|
|||||||
Usage and syntax
|
Usage and syntax
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
Following is the usage and syntax for BitBake::
|
Following is the usage and syntax for BitBake: ::
|
||||||
|
|
||||||
$ bitbake -h
|
$ bitbake -h
|
||||||
Usage: bitbake [options] [recipename/target recipe:do_task ...]
|
Usage: bitbake [options] [recipename/target recipe:do_task ...]
|
||||||
@@ -416,8 +417,8 @@ Following is the usage and syntax for BitBake::
|
|||||||
-l DEBUG_DOMAINS, --log-domains=DEBUG_DOMAINS
|
-l DEBUG_DOMAINS, --log-domains=DEBUG_DOMAINS
|
||||||
Show debug logging for the specified logging domains
|
Show debug logging for the specified logging domains
|
||||||
-P, --profile Profile the command and save reports.
|
-P, --profile Profile the command and save reports.
|
||||||
-u UI, --ui=UI The user interface to use (knotty, ncurses, taskexp or
|
-u UI, --ui=UI The user interface to use (knotty, ncurses or taskexp
|
||||||
teamcity - default knotty).
|
- default knotty).
|
||||||
--token=XMLRPCTOKEN Specify the connection token to be used when
|
--token=XMLRPCTOKEN Specify the connection token to be used when
|
||||||
connecting to a remote server.
|
connecting to a remote server.
|
||||||
--revisions-changed Set the exit code depending on whether upstream
|
--revisions-changed Set the exit code depending on whether upstream
|
||||||
@@ -432,9 +433,6 @@ Following is the usage and syntax for BitBake::
|
|||||||
Environment variable BB_SERVER_TIMEOUT.
|
Environment variable BB_SERVER_TIMEOUT.
|
||||||
--no-setscene Do not run any setscene tasks. sstate will be ignored
|
--no-setscene Do not run any setscene tasks. sstate will be ignored
|
||||||
and everything needed, built.
|
and everything needed, built.
|
||||||
--skip-setscene Skip setscene tasks if they would be executed. Tasks
|
|
||||||
previously restored from sstate will be kept, unlike
|
|
||||||
--no-setscene
|
|
||||||
--setscene-only Only run setscene tasks, don't run any real tasks.
|
--setscene-only Only run setscene tasks, don't run any real tasks.
|
||||||
--remote-server=REMOTE_SERVER
|
--remote-server=REMOTE_SERVER
|
||||||
Connect to the specified server.
|
Connect to the specified server.
|
||||||
@@ -471,11 +469,11 @@ default task, which is "build". BitBake obeys inter-task dependencies
|
|||||||
when doing so.
|
when doing so.
|
||||||
|
|
||||||
The following command runs the build task, which is the default task, on
|
The following command runs the build task, which is the default task, on
|
||||||
the ``foo_1.0.bb`` recipe file::
|
the ``foo_1.0.bb`` recipe file: ::
|
||||||
|
|
||||||
$ bitbake -b foo_1.0.bb
|
$ bitbake -b foo_1.0.bb
|
||||||
|
|
||||||
The following command runs the clean task on the ``foo.bb`` recipe file::
|
The following command runs the clean task on the ``foo.bb`` recipe file: ::
|
||||||
|
|
||||||
$ bitbake -b foo.bb -c clean
|
$ bitbake -b foo.bb -c clean
|
||||||
|
|
||||||
@@ -499,13 +497,13 @@ functionality, or when there are multiple versions of a recipe.
|
|||||||
The ``bitbake`` command, when not using "--buildfile" or "-b" only
|
The ``bitbake`` command, when not using "--buildfile" or "-b" only
|
||||||
accepts a "PROVIDES". You cannot provide anything else. By default, a
|
accepts a "PROVIDES". You cannot provide anything else. By default, a
|
||||||
recipe file generally "PROVIDES" its "packagename" as shown in the
|
recipe file generally "PROVIDES" its "packagename" as shown in the
|
||||||
following example::
|
following example: ::
|
||||||
|
|
||||||
$ bitbake foo
|
$ bitbake foo
|
||||||
|
|
||||||
This next example "PROVIDES" the
|
This next example "PROVIDES" the
|
||||||
package name and also uses the "-c" option to tell BitBake to just
|
package name and also uses the "-c" option to tell BitBake to just
|
||||||
execute the ``do_clean`` task::
|
execute the ``do_clean`` task: ::
|
||||||
|
|
||||||
$ bitbake -c clean foo
|
$ bitbake -c clean foo
|
||||||
|
|
||||||
@@ -516,7 +514,7 @@ The BitBake command line supports specifying different tasks for
|
|||||||
individual targets when you specify multiple targets. For example,
|
individual targets when you specify multiple targets. For example,
|
||||||
suppose you had two targets (or recipes) ``myfirstrecipe`` and
|
suppose you had two targets (or recipes) ``myfirstrecipe`` and
|
||||||
``mysecondrecipe`` and you needed BitBake to run ``taskA`` for the first
|
``mysecondrecipe`` and you needed BitBake to run ``taskA`` for the first
|
||||||
recipe and ``taskB`` for the second recipe::
|
recipe and ``taskB`` for the second recipe: ::
|
||||||
|
|
||||||
$ bitbake myfirstrecipe:do_taskA mysecondrecipe:do_taskB
|
$ bitbake myfirstrecipe:do_taskA mysecondrecipe:do_taskB
|
||||||
|
|
||||||
@@ -536,13 +534,13 @@ current working directory:
|
|||||||
- ``pn-buildlist``: Shows a simple list of targets that are to be
|
- ``pn-buildlist``: Shows a simple list of targets that are to be
|
||||||
built.
|
built.
|
||||||
|
|
||||||
To stop depending on common depends, use the ``-I`` depend option and
|
To stop depending on common depends, use the "-I" depend option and
|
||||||
BitBake omits them from the graph. Leaving this information out can
|
BitBake omits them from the graph. Leaving this information out can
|
||||||
produce more readable graphs. This way, you can remove from the graph
|
produce more readable graphs. This way, you can remove from the graph
|
||||||
:term:`DEPENDS` from inherited classes such as ``base.bbclass``.
|
``DEPENDS`` from inherited classes such as ``base.bbclass``.
|
||||||
|
|
||||||
Here are two examples that create dependency graphs. The second example
|
Here are two examples that create dependency graphs. The second example
|
||||||
omits depends common in OpenEmbedded from the graph::
|
omits depends common in OpenEmbedded from the graph: ::
|
||||||
|
|
||||||
$ bitbake -g foo
|
$ bitbake -g foo
|
||||||
|
|
||||||
@@ -566,7 +564,7 @@ for two separate targets:
|
|||||||
.. image:: figures/bb_multiconfig_files.png
|
.. image:: figures/bb_multiconfig_files.png
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
The reason for this required file hierarchy is because the :term:`BBPATH`
|
The reason for this required file hierarchy is because the ``BBPATH``
|
||||||
variable is not constructed until the layers are parsed. Consequently,
|
variable is not constructed until the layers are parsed. Consequently,
|
||||||
using the configuration file as a pre-configuration file is not possible
|
using the configuration file as a pre-configuration file is not possible
|
||||||
unless it is located in the current working directory.
|
unless it is located in the current working directory.
|
||||||
@@ -584,17 +582,17 @@ accomplished by setting the
|
|||||||
configuration files for ``target1`` and ``target2`` defined in the build
|
configuration files for ``target1`` and ``target2`` defined in the build
|
||||||
directory. The following statement in the ``local.conf`` file both
|
directory. The following statement in the ``local.conf`` file both
|
||||||
enables BitBake to perform multiple configuration builds and specifies
|
enables BitBake to perform multiple configuration builds and specifies
|
||||||
the two extra multiconfigs::
|
the two extra multiconfigs: ::
|
||||||
|
|
||||||
BBMULTICONFIG = "target1 target2"
|
BBMULTICONFIG = "target1 target2"
|
||||||
|
|
||||||
Once the target configuration files are in place and BitBake has been
|
Once the target configuration files are in place and BitBake has been
|
||||||
enabled to perform multiple configuration builds, use the following
|
enabled to perform multiple configuration builds, use the following
|
||||||
command form to start the builds::
|
command form to start the builds: ::
|
||||||
|
|
||||||
$ bitbake [mc:multiconfigname:]target [[[mc:multiconfigname:]target] ... ]
|
$ bitbake [mc:multiconfigname:]target [[[mc:multiconfigname:]target] ... ]
|
||||||
|
|
||||||
Here is an example for two extra multiconfigs: ``target1`` and ``target2``::
|
Here is an example for two extra multiconfigs: ``target1`` and ``target2``: ::
|
||||||
|
|
||||||
$ bitbake mc::target mc:target1:target mc:target2:target
|
$ bitbake mc::target mc:target1:target mc:target2:target
|
||||||
|
|
||||||
@@ -615,12 +613,12 @@ multiconfig.
|
|||||||
|
|
||||||
To enable dependencies in a multiple configuration build, you must
|
To enable dependencies in a multiple configuration build, you must
|
||||||
declare the dependencies in the recipe using the following statement
|
declare the dependencies in the recipe using the following statement
|
||||||
form::
|
form: ::
|
||||||
|
|
||||||
task_or_package[mcdepends] = "mc:from_multiconfig:to_multiconfig:recipe_name:task_on_which_to_depend"
|
task_or_package[mcdepends] = "mc:from_multiconfig:to_multiconfig:recipe_name:task_on_which_to_depend"
|
||||||
|
|
||||||
To better show how to use this statement, consider an example with two
|
To better show how to use this statement, consider an example with two
|
||||||
multiconfigs: ``target1`` and ``target2``::
|
multiconfigs: ``target1`` and ``target2``: ::
|
||||||
|
|
||||||
image_task[mcdepends] = "mc:target1:target2:image2:rootfs_task"
|
image_task[mcdepends] = "mc:target1:target2:image2:rootfs_task"
|
||||||
|
|
||||||
@@ -631,7 +629,7 @@ completion of the rootfs_task used to build out image2, which is
|
|||||||
associated with the "target2" multiconfig.
|
associated with the "target2" multiconfig.
|
||||||
|
|
||||||
Once you set up this dependency, you can build the "target1" multiconfig
|
Once you set up this dependency, you can build the "target1" multiconfig
|
||||||
using a BitBake command as follows::
|
using a BitBake command as follows: ::
|
||||||
|
|
||||||
$ bitbake mc:target1:image1
|
$ bitbake mc:target1:image1
|
||||||
|
|
||||||
@@ -641,7 +639,7 @@ the ``rootfs_task`` for the "target2" multiconfig build.
|
|||||||
|
|
||||||
Having a recipe depend on the root filesystem of another build might not
|
Having a recipe depend on the root filesystem of another build might not
|
||||||
seem that useful. Consider this change to the statement in the image1
|
seem that useful. Consider this change to the statement in the image1
|
||||||
recipe::
|
recipe: ::
|
||||||
|
|
||||||
image_task[mcdepends] = "mc:target1:target2:image2:image_task"
|
image_task[mcdepends] = "mc:target1:target2:image2:image_task"
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,91 +0,0 @@
|
|||||||
.. SPDX-License-Identifier: CC-BY-2.5
|
|
||||||
|
|
||||||
================
|
|
||||||
Variable Context
|
|
||||||
================
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
||||||
Variables might only have an impact or can be used in certain contexts. Some
|
|
||||||
should only be used in global files like ``.conf``, while others are intended only
|
|
||||||
for local files like ``.bb``. This chapter aims to describe some important variable
|
|
||||||
contexts.
|
|
||||||
|
|
||||||
.. _ref-varcontext-configuration:
|
|
||||||
|
|
||||||
BitBake's own configuration
|
|
||||||
===========================
|
|
||||||
|
|
||||||
Variables starting with ``BB_`` usually configure the behaviour of BitBake itself.
|
|
||||||
For example, one could configure:
|
|
||||||
|
|
||||||
- System resources, like disk space to be used (:term:`BB_DISKMON_DIRS`),
|
|
||||||
or the number of tasks to be run in parallel by BitBake (:term:`BB_NUMBER_THREADS`).
|
|
||||||
|
|
||||||
- How the fetchers shall behave, e.g., :term:`BB_FETCH_PREMIRRORONLY` is used
|
|
||||||
by BitBake to determine if BitBake's fetcher shall search only
|
|
||||||
:term:`PREMIRRORS` for files.
|
|
||||||
|
|
||||||
Those variables are usually configured globally.
|
|
||||||
|
|
||||||
BitBake configuration
|
|
||||||
=====================
|
|
||||||
|
|
||||||
There are variables:
|
|
||||||
|
|
||||||
- Like :term:`B` or :term:`T`, that are used to specify directories used by
|
|
||||||
BitBake during the build of a particular recipe. Those variables are
|
|
||||||
specified in ``bitbake.conf``. Some, like :term:`B`, are quite often
|
|
||||||
overwritten in recipes.
|
|
||||||
|
|
||||||
- Starting with ``FAKEROOT``, to configure how the ``fakeroot`` command is
|
|
||||||
handled. Those are usually set by ``bitbake.conf`` and might get adapted in a
|
|
||||||
``bbclass``.
|
|
||||||
|
|
||||||
- Detailing where BitBake will store and fetch information from, for
|
|
||||||
data reuse between build runs like :term:`CACHE`, :term:`DL_DIR` or
|
|
||||||
:term:`PERSISTENT_DIR`. Those are usually global.
|
|
||||||
|
|
||||||
|
|
||||||
Layers and files
|
|
||||||
================
|
|
||||||
|
|
||||||
Variables starting with ``LAYER`` configure how BitBake handles layers.
|
|
||||||
Additionally, variables starting with ``BB`` configure how layers and files are
|
|
||||||
handled. For example:
|
|
||||||
|
|
||||||
- :term:`LAYERDEPENDS` is used to configure on which layers a given layer
|
|
||||||
depends.
|
|
||||||
|
|
||||||
- The configured layers are contained in :term:`BBLAYERS` and files in
|
|
||||||
:term:`BBFILES`.
|
|
||||||
|
|
||||||
Those variables are often used in the files ``layer.conf`` and ``bblayers.conf``.
|
|
||||||
|
|
||||||
Recipes and packages
|
|
||||||
====================
|
|
||||||
|
|
||||||
Variables handling recipes and packages can be split into:
|
|
||||||
|
|
||||||
- :term:`PN`, :term:`PV` or :term:`PF` for example, contain information about
|
|
||||||
the name or revision of a recipe or package. Usually, the default set in
|
|
||||||
``bitbake.conf`` is used, but those are from time to time overwritten in
|
|
||||||
recipes.
|
|
||||||
|
|
||||||
- :term:`SUMMARY`, :term:`DESCRIPTION`, :term:`LICENSE` or :term:`HOMEPAGE`
|
|
||||||
contain the expected information and should be set specifically for every
|
|
||||||
recipe.
|
|
||||||
|
|
||||||
- In recipes, variables are also used to control build and runtime
|
|
||||||
dependencies between recipes/packages with other recipes/packages. The
|
|
||||||
most common should be: :term:`PROVIDES`, :term:`RPROVIDES`, :term:`DEPENDS`,
|
|
||||||
and :term:`RDEPENDS`.
|
|
||||||
|
|
||||||
- There are further variables starting with ``SRC`` that specify the sources in
|
|
||||||
a recipe like :term:`SRC_URI` or :term:`SRCDATE`. Those are also usually set
|
|
||||||
in recipes.
|
|
||||||
|
|
||||||
- Which version or provider of a recipe should be given preference when
|
|
||||||
multiple recipes would provide the same item, is controlled by variables
|
|
||||||
starting with ``PREFERRED_``. Those are normally set in the configuration
|
|
||||||
files of a ``MACHINE`` or ``DISTRO``.
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,6 @@
|
|||||||
# import sys
|
# import sys
|
||||||
# sys.path.insert(0, os.path.abspath('.'))
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
import sys
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
current_version = "dev"
|
current_version = "dev"
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ BitBake User Manual
|
|||||||
bitbake-user-manual/bitbake-user-manual-intro
|
bitbake-user-manual/bitbake-user-manual-intro
|
||||||
bitbake-user-manual/bitbake-user-manual-execution
|
bitbake-user-manual/bitbake-user-manual-execution
|
||||||
bitbake-user-manual/bitbake-user-manual-metadata
|
bitbake-user-manual/bitbake-user-manual-metadata
|
||||||
bitbake-user-manual/bitbake-user-manual-ref-variables-context
|
|
||||||
bitbake-user-manual/bitbake-user-manual-fetching
|
bitbake-user-manual/bitbake-user-manual-fetching
|
||||||
bitbake-user-manual/bitbake-user-manual-ref-variables
|
bitbake-user-manual/bitbake-user-manual-ref-variables
|
||||||
bitbake-user-manual/bitbake-user-manual-hello
|
bitbake-user-manual/bitbake-user-manual-hello
|
||||||
|
|||||||
@@ -1,76 +1,32 @@
|
|||||||
.. SPDX-License-Identifier: CC-BY-2.5
|
.. SPDX-License-Identifier: CC-BY-2.5
|
||||||
|
|
||||||
=================================
|
=========================
|
||||||
BitBake Supported Release Manuals
|
Current Release Manuals
|
||||||
=================================
|
=========================
|
||||||
|
|
||||||
*******************************
|
|
||||||
Release Series 4.2 (mickledore)
|
|
||||||
*******************************
|
|
||||||
|
|
||||||
- :yocto_docs:`BitBake 2.4 User Manual </bitbake/2.4/>`
|
|
||||||
|
|
||||||
******************************
|
|
||||||
Release Series 4.0 (kirkstone)
|
|
||||||
******************************
|
|
||||||
|
|
||||||
- :yocto_docs:`BitBake 2.0 User Manual </bitbake/2.0/>`
|
|
||||||
|
|
||||||
****************************
|
****************************
|
||||||
Release Series 3.1 (dunfell)
|
3.1 'dunfell' Release Series
|
||||||
****************************
|
****************************
|
||||||
|
|
||||||
- :yocto_docs:`BitBake 1.46 User Manual </bitbake/1.46/>`
|
|
||||||
|
|
||||||
================================
|
|
||||||
BitBake Outdated Release Manuals
|
|
||||||
================================
|
|
||||||
|
|
||||||
*****************************
|
|
||||||
Release Series 4.1 (langdale)
|
|
||||||
*****************************
|
|
||||||
|
|
||||||
- :yocto_docs:`BitBake 2.2 User Manual </bitbake/2.2/>`
|
|
||||||
|
|
||||||
******************************
|
|
||||||
Release Series 3.4 (honister)
|
|
||||||
******************************
|
|
||||||
|
|
||||||
- :yocto_docs:`BitBake 1.52 User Manual </bitbake/1.52/>`
|
|
||||||
|
|
||||||
******************************
|
|
||||||
Release Series 3.3 (hardknott)
|
|
||||||
******************************
|
|
||||||
|
|
||||||
- :yocto_docs:`BitBake 1.50 User Manual </bitbake/1.50/>`
|
|
||||||
|
|
||||||
*******************************
|
|
||||||
Release Series 3.2 (gatesgarth)
|
|
||||||
*******************************
|
|
||||||
|
|
||||||
- :yocto_docs:`BitBake 1.48 User Manual </bitbake/1.48/>`
|
|
||||||
|
|
||||||
*******************************************
|
|
||||||
Release Series 3.1 (dunfell first versions)
|
|
||||||
*******************************************
|
|
||||||
|
|
||||||
- :yocto_docs:`3.1 BitBake User Manual </3.1/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`3.1 BitBake User Manual </3.1/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
- :yocto_docs:`3.1.1 BitBake User Manual </3.1.1/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`3.1.1 BitBake User Manual </3.1.1/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
- :yocto_docs:`3.1.2 BitBake User Manual </3.1.2/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`3.1.2 BitBake User Manual </3.1.2/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
- :yocto_docs:`3.1.3 BitBake User Manual </3.1.3/bitbake-user-manual/bitbake-user-manual.html>`
|
|
||||||
|
==========================
|
||||||
|
Previous Release Manuals
|
||||||
|
==========================
|
||||||
|
|
||||||
*************************
|
*************************
|
||||||
Release Series 3.0 (zeus)
|
3.0 'zeus' Release Series
|
||||||
*************************
|
*************************
|
||||||
|
|
||||||
- :yocto_docs:`3.0 BitBake User Manual </3.0/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`3.0 BitBake User Manual </3.0/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
- :yocto_docs:`3.0.1 BitBake User Manual </3.0.1/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`3.0.1 BitBake User Manual </3.0.1/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
- :yocto_docs:`3.0.2 BitBake User Manual </3.0.2/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`3.0.2 BitBake User Manual </3.0.2/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
- :yocto_docs:`3.0.3 BitBake User Manual </3.0.3/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`3.0.3 BitBake User Manual </3.0.3/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
- :yocto_docs:`3.0.4 BitBake User Manual </3.0.4/bitbake-user-manual/bitbake-user-manual.html>`
|
|
||||||
|
|
||||||
****************************
|
****************************
|
||||||
Release Series 2.7 (warrior)
|
2.7 'warrior' Release Series
|
||||||
****************************
|
****************************
|
||||||
|
|
||||||
- :yocto_docs:`2.7 BitBake User Manual </2.7/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`2.7 BitBake User Manual </2.7/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
@@ -80,7 +36,7 @@ Release Series 2.7 (warrior)
|
|||||||
- :yocto_docs:`2.7.4 BitBake User Manual </2.7.4/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`2.7.4 BitBake User Manual </2.7.4/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
|
|
||||||
*************************
|
*************************
|
||||||
Release Series 2.6 (thud)
|
2.6 'thud' Release Series
|
||||||
*************************
|
*************************
|
||||||
|
|
||||||
- :yocto_docs:`2.6 BitBake User Manual </2.6/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`2.6 BitBake User Manual </2.6/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
@@ -90,16 +46,16 @@ Release Series 2.6 (thud)
|
|||||||
- :yocto_docs:`2.6.4 BitBake User Manual </2.6.4/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`2.6.4 BitBake User Manual </2.6.4/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
|
|
||||||
*************************
|
*************************
|
||||||
Release Series 2.5 (sumo)
|
2.5 'sumo' Release Series
|
||||||
*************************
|
*************************
|
||||||
|
|
||||||
- :yocto_docs:`2.5 Documentation </2.5>`
|
- :yocto_docs:`2.5 BitBake User Manual </2.5/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
- :yocto_docs:`2.5.1 Documentation </2.5.1>`
|
- :yocto_docs:`2.5.1 BitBake User Manual </2.5.1/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
- :yocto_docs:`2.5.2 Documentation </2.5.2>`
|
- :yocto_docs:`2.5.2 BitBake User Manual </2.5.2/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
- :yocto_docs:`2.5.3 Documentation </2.5.3>`
|
- :yocto_docs:`2.5.3 BitBake User Manual </2.5.3/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
|
|
||||||
**************************
|
**************************
|
||||||
Release Series 2.4 (rocko)
|
2.4 'rocko' Release Series
|
||||||
**************************
|
**************************
|
||||||
|
|
||||||
- :yocto_docs:`2.4 BitBake User Manual </2.4/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`2.4 BitBake User Manual </2.4/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
@@ -109,7 +65,7 @@ Release Series 2.4 (rocko)
|
|||||||
- :yocto_docs:`2.4.4 BitBake User Manual </2.4.4/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`2.4.4 BitBake User Manual </2.4.4/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
|
|
||||||
*************************
|
*************************
|
||||||
Release Series 2.3 (pyro)
|
2.3 'pyro' Release Series
|
||||||
*************************
|
*************************
|
||||||
|
|
||||||
- :yocto_docs:`2.3 BitBake User Manual </2.3/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`2.3 BitBake User Manual </2.3/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
@@ -119,7 +75,7 @@ Release Series 2.3 (pyro)
|
|||||||
- :yocto_docs:`2.3.4 BitBake User Manual </2.3.4/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`2.3.4 BitBake User Manual </2.3.4/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
|
|
||||||
**************************
|
**************************
|
||||||
Release Series 2.2 (morty)
|
2.2 'morty' Release Series
|
||||||
**************************
|
**************************
|
||||||
|
|
||||||
- :yocto_docs:`2.2 BitBake User Manual </2.2/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`2.2 BitBake User Manual </2.2/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
@@ -128,7 +84,7 @@ Release Series 2.2 (morty)
|
|||||||
- :yocto_docs:`2.2.3 BitBake User Manual </2.2.3/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`2.2.3 BitBake User Manual </2.2.3/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
|
|
||||||
****************************
|
****************************
|
||||||
Release Series 2.1 (krogoth)
|
2.1 'krogoth' Release Series
|
||||||
****************************
|
****************************
|
||||||
|
|
||||||
- :yocto_docs:`2.1 BitBake User Manual </2.1/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`2.1 BitBake User Manual </2.1/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
@@ -137,7 +93,7 @@ Release Series 2.1 (krogoth)
|
|||||||
- :yocto_docs:`2.1.3 BitBake User Manual </2.1.3/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`2.1.3 BitBake User Manual </2.1.3/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
|
|
||||||
***************************
|
***************************
|
||||||
Release Series 2.0 (jethro)
|
2.0 'jethro' Release Series
|
||||||
***************************
|
***************************
|
||||||
|
|
||||||
- :yocto_docs:`1.9 BitBake User Manual </1.9/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`1.9 BitBake User Manual </1.9/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
@@ -147,7 +103,7 @@ Release Series 2.0 (jethro)
|
|||||||
- :yocto_docs:`2.0.3 BitBake User Manual </2.0.3/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`2.0.3 BitBake User Manual </2.0.3/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
|
|
||||||
*************************
|
*************************
|
||||||
Release Series 1.8 (fido)
|
1.8 'fido' Release Series
|
||||||
*************************
|
*************************
|
||||||
|
|
||||||
- :yocto_docs:`1.8 BitBake User Manual </1.8/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`1.8 BitBake User Manual </1.8/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
@@ -155,7 +111,7 @@ Release Series 1.8 (fido)
|
|||||||
- :yocto_docs:`1.8.2 BitBake User Manual </1.8.2/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`1.8.2 BitBake User Manual </1.8.2/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
|
|
||||||
**************************
|
**************************
|
||||||
Release Series 1.7 (dizzy)
|
1.7 'dizzy' Release Series
|
||||||
**************************
|
**************************
|
||||||
|
|
||||||
- :yocto_docs:`1.7 BitBake User Manual </1.7/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`1.7 BitBake User Manual </1.7/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
@@ -164,7 +120,7 @@ Release Series 1.7 (dizzy)
|
|||||||
- :yocto_docs:`1.7.3 BitBake User Manual </1.7.3/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`1.7.3 BitBake User Manual </1.7.3/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
|
|
||||||
**************************
|
**************************
|
||||||
Release Series 1.6 (daisy)
|
1.6 'daisy' Release Series
|
||||||
**************************
|
**************************
|
||||||
|
|
||||||
- :yocto_docs:`1.6 BitBake User Manual </1.6/bitbake-user-manual/bitbake-user-manual.html>`
|
- :yocto_docs:`1.6 BitBake User Manual </1.6/bitbake-user-manual/bitbake-user-manual.html>`
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2006 Tim Ansell
|
# Copyright (C) 2006 Tim Ansell
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
# Please Note:
|
# Please Note:
|
||||||
# Be careful when using mutable types (ie Dict and Lists) - operations involving these are SLOW.
|
# 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.
|
# Assign a file to __warn__ to get warnings about slow operations.
|
||||||
|
|||||||
@@ -9,19 +9,12 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "2.8.1"
|
__version__ = "1.48.0"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
if sys.version_info < (3, 8, 0):
|
if sys.version_info < (3, 5, 0):
|
||||||
raise RuntimeError("Sorry, python 3.8.0 or later is required for this version of bitbake")
|
raise RuntimeError("Sorry, python 3.5.0 or later is required for this version of bitbake")
|
||||||
|
|
||||||
if sys.version_info < (3, 10, 0):
|
|
||||||
# With python 3.8 and 3.9, we see errors of "libgcc_s.so.1 must be installed for pthread_cancel to work"
|
|
||||||
# https://stackoverflow.com/questions/64797838/libgcc-s-so-1-must-be-installed-for-pthread-cancel-to-work
|
|
||||||
# https://bugs.ams1.psf.io/issue42888
|
|
||||||
# so ensure libgcc_s is loaded early on
|
|
||||||
import ctypes
|
|
||||||
libgcc_s = ctypes.CDLL('libgcc_s.so.1')
|
|
||||||
|
|
||||||
class BBHandledException(Exception):
|
class BBHandledException(Exception):
|
||||||
"""
|
"""
|
||||||
@@ -36,35 +29,6 @@ class BBHandledException(Exception):
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from collections import namedtuple
|
|
||||||
import multiprocessing as mp
|
|
||||||
|
|
||||||
# Python 3.14 changes the default multiprocessing context from "fork" to
|
|
||||||
# "forkserver". However, bitbake heavily relies on "fork" behavior to
|
|
||||||
# efficiently pass data to the child processes. Places that need this should do:
|
|
||||||
# from bb import multiprocessing
|
|
||||||
# in place of
|
|
||||||
# import multiprocessing
|
|
||||||
|
|
||||||
class MultiprocessingContext(object):
|
|
||||||
"""
|
|
||||||
Multiprocessing proxy object that uses the "fork" context for a property if
|
|
||||||
available, otherwise goes to the main multiprocessing module. This allows
|
|
||||||
it to be a drop-in replacement for the multiprocessing module, but use the
|
|
||||||
fork context
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
super().__setattr__("_ctx", mp.get_context("fork"))
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
if hasattr(self._ctx, name):
|
|
||||||
return getattr(self._ctx, name)
|
|
||||||
return getattr(mp, name)
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
|
||||||
raise AttributeError(f"Unable to set attribute {name}")
|
|
||||||
|
|
||||||
multiprocessing = MultiprocessingContext()
|
|
||||||
|
|
||||||
|
|
||||||
class NullHandler(logging.Handler):
|
class NullHandler(logging.Handler):
|
||||||
@@ -78,28 +42,15 @@ class BBLoggerMixin(object):
|
|||||||
|
|
||||||
def setup_bblogger(self, name):
|
def setup_bblogger(self, name):
|
||||||
if name.split(".")[0] == "BitBake":
|
if name.split(".")[0] == "BitBake":
|
||||||
self.debug = self._debug_helper
|
self.debug = self.bbdebug
|
||||||
|
|
||||||
def _debug_helper(self, *args, **kwargs):
|
|
||||||
return self.bbdebug(1, *args, **kwargs)
|
|
||||||
|
|
||||||
def debug2(self, *args, **kwargs):
|
|
||||||
return self.bbdebug(2, *args, **kwargs)
|
|
||||||
|
|
||||||
def debug3(self, *args, **kwargs):
|
|
||||||
return self.bbdebug(3, *args, **kwargs)
|
|
||||||
|
|
||||||
def bbdebug(self, level, msg, *args, **kwargs):
|
def bbdebug(self, level, msg, *args, **kwargs):
|
||||||
loglevel = logging.DEBUG - level + 1
|
loglevel = logging.DEBUG - level + 1
|
||||||
if not bb.event.worker_pid:
|
if not bb.event.worker_pid:
|
||||||
if self.name in bb.msg.loggerDefaultDomains and loglevel > (bb.msg.loggerDefaultDomains[self.name]):
|
if self.name in bb.msg.loggerDefaultDomains and loglevel > (bb.msg.loggerDefaultDomains[self.name]):
|
||||||
return
|
return
|
||||||
if loglevel < bb.msg.loggerDefaultLogLevel:
|
if loglevel > bb.msg.loggerDefaultLogLevel:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not isinstance(level, int) or not isinstance(msg, str):
|
|
||||||
mainlogger.warning("Invalid arguments in bbdebug: %s" % repr((level, msg,) + args))
|
|
||||||
|
|
||||||
return self.log(loglevel, msg, *args, **kwargs)
|
return self.log(loglevel, msg, *args, **kwargs)
|
||||||
|
|
||||||
def plain(self, msg, *args, **kwargs):
|
def plain(self, msg, *args, **kwargs):
|
||||||
@@ -111,13 +62,6 @@ class BBLoggerMixin(object):
|
|||||||
def verbnote(self, msg, *args, **kwargs):
|
def verbnote(self, msg, *args, **kwargs):
|
||||||
return self.log(logging.INFO + 2, msg, *args, **kwargs)
|
return self.log(logging.INFO + 2, msg, *args, **kwargs)
|
||||||
|
|
||||||
def warnonce(self, msg, *args, **kwargs):
|
|
||||||
return self.log(logging.WARNING - 1, msg, *args, **kwargs)
|
|
||||||
|
|
||||||
def erroronce(self, msg, *args, **kwargs):
|
|
||||||
return self.log(logging.ERROR - 1, msg, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
Logger = logging.getLoggerClass()
|
Logger = logging.getLoggerClass()
|
||||||
class BBLogger(Logger, BBLoggerMixin):
|
class BBLogger(Logger, BBLoggerMixin):
|
||||||
def __init__(self, name, *args, **kwargs):
|
def __init__(self, name, *args, **kwargs):
|
||||||
@@ -184,7 +128,7 @@ def debug(lvl, *args):
|
|||||||
mainlogger.warning("Passed invalid debug level '%s' to bb.debug", lvl)
|
mainlogger.warning("Passed invalid debug level '%s' to bb.debug", lvl)
|
||||||
args = (lvl,) + args
|
args = (lvl,) + args
|
||||||
lvl = 1
|
lvl = 1
|
||||||
mainlogger.bbdebug(lvl, ''.join(args))
|
mainlogger.debug(lvl, ''.join(args))
|
||||||
|
|
||||||
def note(*args):
|
def note(*args):
|
||||||
mainlogger.info(''.join(args))
|
mainlogger.info(''.join(args))
|
||||||
@@ -204,15 +148,9 @@ def verbnote(*args):
|
|||||||
def warn(*args):
|
def warn(*args):
|
||||||
mainlogger.warning(''.join(args))
|
mainlogger.warning(''.join(args))
|
||||||
|
|
||||||
def warnonce(*args):
|
|
||||||
mainlogger.warnonce(''.join(args))
|
|
||||||
|
|
||||||
def error(*args, **kwargs):
|
def error(*args, **kwargs):
|
||||||
mainlogger.error(''.join(args), extra=kwargs)
|
mainlogger.error(''.join(args), extra=kwargs)
|
||||||
|
|
||||||
def erroronce(*args):
|
|
||||||
mainlogger.erroronce(''.join(args))
|
|
||||||
|
|
||||||
def fatal(*args, **kwargs):
|
def fatal(*args, **kwargs):
|
||||||
mainlogger.critical(''.join(args), extra=kwargs)
|
mainlogger.critical(''.join(args), extra=kwargs)
|
||||||
raise BBHandledException()
|
raise BBHandledException()
|
||||||
@@ -256,14 +194,3 @@ def deprecate_import(current, modulename, fromlist, renames = None):
|
|||||||
|
|
||||||
setattr(sys.modules[current], newname, newobj)
|
setattr(sys.modules[current], newname, newobj)
|
||||||
|
|
||||||
TaskData = namedtuple("TaskData", [
|
|
||||||
"pn",
|
|
||||||
"taskname",
|
|
||||||
"fn",
|
|
||||||
"deps",
|
|
||||||
"provides",
|
|
||||||
"taskhash",
|
|
||||||
"unihash",
|
|
||||||
"hashfn",
|
|
||||||
"taskhash_deps",
|
|
||||||
])
|
|
||||||
|
|||||||
@@ -1,215 +0,0 @@
|
|||||||
#! /usr/bin/env python3
|
|
||||||
#
|
|
||||||
# Copyright 2023 by Garmin Ltd. or its subsidiaries
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import ctypes
|
|
||||||
import os
|
|
||||||
import errno
|
|
||||||
import pwd
|
|
||||||
import grp
|
|
||||||
|
|
||||||
libacl = ctypes.CDLL("libacl.so.1", use_errno=True)
|
|
||||||
|
|
||||||
|
|
||||||
ACL_TYPE_ACCESS = 0x8000
|
|
||||||
ACL_TYPE_DEFAULT = 0x4000
|
|
||||||
|
|
||||||
ACL_FIRST_ENTRY = 0
|
|
||||||
ACL_NEXT_ENTRY = 1
|
|
||||||
|
|
||||||
ACL_UNDEFINED_TAG = 0x00
|
|
||||||
ACL_USER_OBJ = 0x01
|
|
||||||
ACL_USER = 0x02
|
|
||||||
ACL_GROUP_OBJ = 0x04
|
|
||||||
ACL_GROUP = 0x08
|
|
||||||
ACL_MASK = 0x10
|
|
||||||
ACL_OTHER = 0x20
|
|
||||||
|
|
||||||
ACL_READ = 0x04
|
|
||||||
ACL_WRITE = 0x02
|
|
||||||
ACL_EXECUTE = 0x01
|
|
||||||
|
|
||||||
acl_t = ctypes.c_void_p
|
|
||||||
acl_entry_t = ctypes.c_void_p
|
|
||||||
acl_permset_t = ctypes.c_void_p
|
|
||||||
acl_perm_t = ctypes.c_uint
|
|
||||||
|
|
||||||
acl_tag_t = ctypes.c_int
|
|
||||||
|
|
||||||
libacl.acl_free.argtypes = [acl_t]
|
|
||||||
|
|
||||||
|
|
||||||
def acl_free(acl):
|
|
||||||
libacl.acl_free(acl)
|
|
||||||
|
|
||||||
|
|
||||||
libacl.acl_get_file.restype = acl_t
|
|
||||||
libacl.acl_get_file.argtypes = [ctypes.c_char_p, ctypes.c_uint]
|
|
||||||
|
|
||||||
|
|
||||||
def acl_get_file(path, typ):
|
|
||||||
acl = libacl.acl_get_file(os.fsencode(path), typ)
|
|
||||||
if acl is None:
|
|
||||||
err = ctypes.get_errno()
|
|
||||||
raise OSError(err, os.strerror(err), str(path))
|
|
||||||
|
|
||||||
return acl
|
|
||||||
|
|
||||||
|
|
||||||
libacl.acl_get_entry.argtypes = [acl_t, ctypes.c_int, ctypes.c_void_p]
|
|
||||||
|
|
||||||
|
|
||||||
def acl_get_entry(acl, entry_id):
|
|
||||||
entry = acl_entry_t()
|
|
||||||
ret = libacl.acl_get_entry(acl, entry_id, ctypes.byref(entry))
|
|
||||||
if ret < 0:
|
|
||||||
err = ctypes.get_errno()
|
|
||||||
raise OSError(err, os.strerror(err))
|
|
||||||
|
|
||||||
if ret == 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return entry
|
|
||||||
|
|
||||||
|
|
||||||
libacl.acl_get_tag_type.argtypes = [acl_entry_t, ctypes.c_void_p]
|
|
||||||
|
|
||||||
|
|
||||||
def acl_get_tag_type(entry_d):
|
|
||||||
tag = acl_tag_t()
|
|
||||||
ret = libacl.acl_get_tag_type(entry_d, ctypes.byref(tag))
|
|
||||||
if ret < 0:
|
|
||||||
err = ctypes.get_errno()
|
|
||||||
raise OSError(err, os.strerror(err))
|
|
||||||
return tag.value
|
|
||||||
|
|
||||||
|
|
||||||
libacl.acl_get_qualifier.restype = ctypes.c_void_p
|
|
||||||
libacl.acl_get_qualifier.argtypes = [acl_entry_t]
|
|
||||||
|
|
||||||
|
|
||||||
def acl_get_qualifier(entry_d):
|
|
||||||
ret = libacl.acl_get_qualifier(entry_d)
|
|
||||||
if ret is None:
|
|
||||||
err = ctypes.get_errno()
|
|
||||||
raise OSError(err, os.strerror(err))
|
|
||||||
return ctypes.c_void_p(ret)
|
|
||||||
|
|
||||||
|
|
||||||
libacl.acl_get_permset.argtypes = [acl_entry_t, ctypes.c_void_p]
|
|
||||||
|
|
||||||
|
|
||||||
def acl_get_permset(entry_d):
|
|
||||||
permset = acl_permset_t()
|
|
||||||
ret = libacl.acl_get_permset(entry_d, ctypes.byref(permset))
|
|
||||||
if ret < 0:
|
|
||||||
err = ctypes.get_errno()
|
|
||||||
raise OSError(err, os.strerror(err))
|
|
||||||
|
|
||||||
return permset
|
|
||||||
|
|
||||||
|
|
||||||
libacl.acl_get_perm.argtypes = [acl_permset_t, acl_perm_t]
|
|
||||||
|
|
||||||
|
|
||||||
def acl_get_perm(permset_d, perm):
|
|
||||||
ret = libacl.acl_get_perm(permset_d, perm)
|
|
||||||
if ret < 0:
|
|
||||||
err = ctypes.get_errno()
|
|
||||||
raise OSError(err, os.strerror(err))
|
|
||||||
return bool(ret)
|
|
||||||
|
|
||||||
|
|
||||||
class Entry(object):
|
|
||||||
def __init__(self, tag, qualifier, mode):
|
|
||||||
self.tag = tag
|
|
||||||
self.qualifier = qualifier
|
|
||||||
self.mode = mode
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
typ = ""
|
|
||||||
qual = ""
|
|
||||||
if self.tag == ACL_USER:
|
|
||||||
typ = "user"
|
|
||||||
qual = pwd.getpwuid(self.qualifier).pw_name
|
|
||||||
elif self.tag == ACL_GROUP:
|
|
||||||
typ = "group"
|
|
||||||
qual = grp.getgrgid(self.qualifier).gr_name
|
|
||||||
elif self.tag == ACL_USER_OBJ:
|
|
||||||
typ = "user"
|
|
||||||
elif self.tag == ACL_GROUP_OBJ:
|
|
||||||
typ = "group"
|
|
||||||
elif self.tag == ACL_MASK:
|
|
||||||
typ = "mask"
|
|
||||||
elif self.tag == ACL_OTHER:
|
|
||||||
typ = "other"
|
|
||||||
|
|
||||||
r = "r" if self.mode & ACL_READ else "-"
|
|
||||||
w = "w" if self.mode & ACL_WRITE else "-"
|
|
||||||
x = "x" if self.mode & ACL_EXECUTE else "-"
|
|
||||||
|
|
||||||
return f"{typ}:{qual}:{r}{w}{x}"
|
|
||||||
|
|
||||||
|
|
||||||
class ACL(object):
|
|
||||||
def __init__(self, acl):
|
|
||||||
self.acl = acl
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
acl_free(self.acl)
|
|
||||||
|
|
||||||
def entries(self):
|
|
||||||
entry_id = ACL_FIRST_ENTRY
|
|
||||||
while True:
|
|
||||||
entry = acl_get_entry(self.acl, entry_id)
|
|
||||||
if entry is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
permset = acl_get_permset(entry)
|
|
||||||
|
|
||||||
mode = 0
|
|
||||||
for m in (ACL_READ, ACL_WRITE, ACL_EXECUTE):
|
|
||||||
if acl_get_perm(permset, m):
|
|
||||||
mode |= m
|
|
||||||
|
|
||||||
qualifier = None
|
|
||||||
tag = acl_get_tag_type(entry)
|
|
||||||
|
|
||||||
if tag == ACL_USER or tag == ACL_GROUP:
|
|
||||||
qual = acl_get_qualifier(entry)
|
|
||||||
qualifier = ctypes.cast(qual, ctypes.POINTER(ctypes.c_int))[0]
|
|
||||||
|
|
||||||
yield Entry(tag, qualifier, mode)
|
|
||||||
|
|
||||||
entry_id = ACL_NEXT_ENTRY
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_path(cls, path, typ):
|
|
||||||
acl = acl_get_file(path, typ)
|
|
||||||
return cls(acl)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
import pwd
|
|
||||||
import grp
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument("path", help="File Path", type=Path)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
acl = ACL.from_path(args.path, ACL_TYPE_ACCESS)
|
|
||||||
for entry in acl.entries():
|
|
||||||
print(str(entry))
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
from .client import AsyncClient, Client, ClientPool
|
|
||||||
from .serv import AsyncServer, AsyncServerConnection
|
|
||||||
from .connection import DEFAULT_MAX_CHUNK
|
|
||||||
from .exceptions import (
|
|
||||||
ClientError,
|
|
||||||
ServerError,
|
|
||||||
ConnectionClosedError,
|
|
||||||
InvokeError,
|
|
||||||
)
|
|
||||||
@@ -1,317 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
|
|
||||||
import abc
|
|
||||||
import asyncio
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import contextlib
|
|
||||||
from threading import Thread
|
|
||||||
from .connection import StreamConnection, WebsocketConnection, DEFAULT_MAX_CHUNK
|
|
||||||
from .exceptions import ConnectionClosedError, InvokeError
|
|
||||||
|
|
||||||
UNIX_PREFIX = "unix://"
|
|
||||||
WS_PREFIX = "ws://"
|
|
||||||
WSS_PREFIX = "wss://"
|
|
||||||
|
|
||||||
ADDR_TYPE_UNIX = 0
|
|
||||||
ADDR_TYPE_TCP = 1
|
|
||||||
ADDR_TYPE_WS = 2
|
|
||||||
|
|
||||||
def parse_address(addr):
|
|
||||||
if addr.startswith(UNIX_PREFIX):
|
|
||||||
return (ADDR_TYPE_UNIX, (addr[len(UNIX_PREFIX) :],))
|
|
||||||
elif addr.startswith(WS_PREFIX) or addr.startswith(WSS_PREFIX):
|
|
||||||
return (ADDR_TYPE_WS, (addr,))
|
|
||||||
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)))
|
|
||||||
|
|
||||||
class AsyncClient(object):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
proto_name,
|
|
||||||
proto_version,
|
|
||||||
logger,
|
|
||||||
timeout=30,
|
|
||||||
server_headers=False,
|
|
||||||
headers={},
|
|
||||||
):
|
|
||||||
self.socket = None
|
|
||||||
self.max_chunk = DEFAULT_MAX_CHUNK
|
|
||||||
self.proto_name = proto_name
|
|
||||||
self.proto_version = proto_version
|
|
||||||
self.logger = logger
|
|
||||||
self.timeout = timeout
|
|
||||||
self.needs_server_headers = server_headers
|
|
||||||
self.server_headers = {}
|
|
||||||
self.headers = headers
|
|
||||||
|
|
||||||
async def connect_tcp(self, address, port):
|
|
||||||
async def connect_sock():
|
|
||||||
reader, writer = await asyncio.open_connection(address, port)
|
|
||||||
return StreamConnection(reader, writer, self.timeout, self.max_chunk)
|
|
||||||
|
|
||||||
self._connect_sock = connect_sock
|
|
||||||
|
|
||||||
async def connect_unix(self, path):
|
|
||||||
async def connect_sock():
|
|
||||||
# AF_UNIX has path length issues so chdir here to workaround
|
|
||||||
cwd = os.getcwd()
|
|
||||||
try:
|
|
||||||
os.chdir(os.path.dirname(path))
|
|
||||||
# The socket must be opened synchronously so that CWD doesn't get
|
|
||||||
# changed out from underneath us so we pass as a sock into asyncio
|
|
||||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0)
|
|
||||||
sock.connect(os.path.basename(path))
|
|
||||||
finally:
|
|
||||||
os.chdir(cwd)
|
|
||||||
reader, writer = await asyncio.open_unix_connection(sock=sock)
|
|
||||||
return StreamConnection(reader, writer, self.timeout, self.max_chunk)
|
|
||||||
|
|
||||||
self._connect_sock = connect_sock
|
|
||||||
|
|
||||||
async def connect_websocket(self, uri):
|
|
||||||
import websockets
|
|
||||||
|
|
||||||
async def connect_sock():
|
|
||||||
websocket = await websockets.connect(
|
|
||||||
uri,
|
|
||||||
ping_interval=None,
|
|
||||||
open_timeout=self.timeout,
|
|
||||||
)
|
|
||||||
return WebsocketConnection(websocket, self.timeout)
|
|
||||||
|
|
||||||
self._connect_sock = connect_sock
|
|
||||||
|
|
||||||
async def setup_connection(self):
|
|
||||||
# Send headers
|
|
||||||
await self.socket.send("%s %s" % (self.proto_name, self.proto_version))
|
|
||||||
await self.socket.send(
|
|
||||||
"needs-headers: %s" % ("true" if self.needs_server_headers else "false")
|
|
||||||
)
|
|
||||||
for k, v in self.headers.items():
|
|
||||||
await self.socket.send("%s: %s" % (k, v))
|
|
||||||
|
|
||||||
# End of headers
|
|
||||||
await self.socket.send("")
|
|
||||||
|
|
||||||
self.server_headers = {}
|
|
||||||
if self.needs_server_headers:
|
|
||||||
while True:
|
|
||||||
line = await self.socket.recv()
|
|
||||||
if not line:
|
|
||||||
# End headers
|
|
||||||
break
|
|
||||||
tag, value = line.split(":", 1)
|
|
||||||
self.server_headers[tag.lower()] = value.strip()
|
|
||||||
|
|
||||||
async def get_header(self, tag, default):
|
|
||||||
await self.connect()
|
|
||||||
return self.server_headers.get(tag, default)
|
|
||||||
|
|
||||||
async def connect(self):
|
|
||||||
if self.socket is None:
|
|
||||||
self.socket = await self._connect_sock()
|
|
||||||
await self.setup_connection()
|
|
||||||
|
|
||||||
async def disconnect(self):
|
|
||||||
if self.socket is not None:
|
|
||||||
await self.socket.close()
|
|
||||||
self.socket = None
|
|
||||||
|
|
||||||
async def close(self):
|
|
||||||
await self.disconnect()
|
|
||||||
|
|
||||||
async def _send_wrapper(self, proc):
|
|
||||||
count = 0
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
await self.connect()
|
|
||||||
return await proc()
|
|
||||||
except (
|
|
||||||
OSError,
|
|
||||||
ConnectionError,
|
|
||||||
ConnectionClosedError,
|
|
||||||
json.JSONDecodeError,
|
|
||||||
UnicodeDecodeError,
|
|
||||||
) as e:
|
|
||||||
self.logger.warning("Error talking to server: %s" % e)
|
|
||||||
if count >= 3:
|
|
||||||
if not isinstance(e, ConnectionError):
|
|
||||||
raise ConnectionError(str(e))
|
|
||||||
raise e
|
|
||||||
await self.close()
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
def check_invoke_error(self, msg):
|
|
||||||
if isinstance(msg, dict) and "invoke-error" in msg:
|
|
||||||
raise InvokeError(msg["invoke-error"]["message"])
|
|
||||||
|
|
||||||
async def invoke(self, msg):
|
|
||||||
async def proc():
|
|
||||||
await self.socket.send_message(msg)
|
|
||||||
return await self.socket.recv_message()
|
|
||||||
|
|
||||||
result = await self._send_wrapper(proc)
|
|
||||||
self.check_invoke_error(result)
|
|
||||||
return result
|
|
||||||
|
|
||||||
async def ping(self):
|
|
||||||
return await self.invoke({"ping": {}})
|
|
||||||
|
|
||||||
async def __aenter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
||||||
await self.close()
|
|
||||||
|
|
||||||
|
|
||||||
class Client(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.client = self._get_async_client()
|
|
||||||
self.loop = asyncio.new_event_loop()
|
|
||||||
|
|
||||||
# Override any pre-existing loop.
|
|
||||||
# Without this, the PR server export selftest triggers a hang
|
|
||||||
# when running with Python 3.7. The drawback is that there is
|
|
||||||
# potential for issues if the PR and hash equiv (or some new)
|
|
||||||
# clients need to both be instantiated in the same process.
|
|
||||||
# This should be revisited if/when Python 3.9 becomes the
|
|
||||||
# minimum required version for BitBake, as it seems not
|
|
||||||
# required (but harmless) with it.
|
|
||||||
asyncio.set_event_loop(self.loop)
|
|
||||||
|
|
||||||
self._add_methods("connect_tcp", "ping")
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def _get_async_client(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _get_downcall_wrapper(self, downcall):
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
return self.loop.run_until_complete(downcall(*args, **kwargs))
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
def _add_methods(self, *methods):
|
|
||||||
for m in methods:
|
|
||||||
downcall = getattr(self.client, m)
|
|
||||||
setattr(self, m, self._get_downcall_wrapper(downcall))
|
|
||||||
|
|
||||||
def connect_unix(self, path):
|
|
||||||
self.loop.run_until_complete(self.client.connect_unix(path))
|
|
||||||
self.loop.run_until_complete(self.client.connect())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_chunk(self):
|
|
||||||
return self.client.max_chunk
|
|
||||||
|
|
||||||
@max_chunk.setter
|
|
||||||
def max_chunk(self, value):
|
|
||||||
self.client.max_chunk = value
|
|
||||||
|
|
||||||
def disconnect(self):
|
|
||||||
self.loop.run_until_complete(self.client.close())
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self.loop:
|
|
||||||
self.loop.run_until_complete(self.client.close())
|
|
||||||
if sys.version_info >= (3, 6):
|
|
||||||
self.loop.run_until_complete(self.loop.shutdown_asyncgens())
|
|
||||||
self.loop.close()
|
|
||||||
self.loop = None
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback):
|
|
||||||
self.close()
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class ClientPool(object):
|
|
||||||
def __init__(self, max_clients):
|
|
||||||
self.avail_clients = []
|
|
||||||
self.num_clients = 0
|
|
||||||
self.max_clients = max_clients
|
|
||||||
self.loop = None
|
|
||||||
self.client_condition = None
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
async def _new_client(self):
|
|
||||||
raise NotImplementedError("Must be implemented in derived class")
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self.client_condition:
|
|
||||||
self.client_condition = None
|
|
||||||
|
|
||||||
if self.loop:
|
|
||||||
self.loop.run_until_complete(self.__close_clients())
|
|
||||||
self.loop.run_until_complete(self.loop.shutdown_asyncgens())
|
|
||||||
self.loop.close()
|
|
||||||
self.loop = None
|
|
||||||
|
|
||||||
def run_tasks(self, tasks):
|
|
||||||
if not self.loop:
|
|
||||||
self.loop = asyncio.new_event_loop()
|
|
||||||
|
|
||||||
thread = Thread(target=self.__thread_main, args=(tasks,))
|
|
||||||
thread.start()
|
|
||||||
thread.join()
|
|
||||||
|
|
||||||
@contextlib.asynccontextmanager
|
|
||||||
async def get_client(self):
|
|
||||||
async with self.client_condition:
|
|
||||||
if self.avail_clients:
|
|
||||||
client = self.avail_clients.pop()
|
|
||||||
elif self.num_clients < self.max_clients:
|
|
||||||
self.num_clients += 1
|
|
||||||
client = await self._new_client()
|
|
||||||
else:
|
|
||||||
while not self.avail_clients:
|
|
||||||
await self.client_condition.wait()
|
|
||||||
client = self.avail_clients.pop()
|
|
||||||
|
|
||||||
try:
|
|
||||||
yield client
|
|
||||||
finally:
|
|
||||||
async with self.client_condition:
|
|
||||||
self.avail_clients.append(client)
|
|
||||||
self.client_condition.notify()
|
|
||||||
|
|
||||||
def __thread_main(self, tasks):
|
|
||||||
async def process_task(task):
|
|
||||||
async with self.get_client() as client:
|
|
||||||
await task(client)
|
|
||||||
|
|
||||||
asyncio.set_event_loop(self.loop)
|
|
||||||
if not self.client_condition:
|
|
||||||
self.client_condition = asyncio.Condition()
|
|
||||||
tasks = [process_task(t) for t in tasks]
|
|
||||||
self.loop.run_until_complete(asyncio.gather(*tasks))
|
|
||||||
|
|
||||||
async def __close_clients(self):
|
|
||||||
for c in self.avail_clients:
|
|
||||||
await c.close()
|
|
||||||
self.avail_clients = []
|
|
||||||
self.num_clients = 0
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback):
|
|
||||||
self.close()
|
|
||||||
return False
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import itertools
|
|
||||||
import json
|
|
||||||
from datetime import datetime
|
|
||||||
from .exceptions import ClientError, ConnectionClosedError
|
|
||||||
|
|
||||||
|
|
||||||
# The Python async server defaults to a 64K receive buffer, so we hardcode our
|
|
||||||
# maximum chunk size. It would be better if the client and server reported to
|
|
||||||
# each other what the maximum chunk sizes were, but that will slow down the
|
|
||||||
# connection setup with a round trip delay so I'd rather not do that unless it
|
|
||||||
# is necessary
|
|
||||||
DEFAULT_MAX_CHUNK = 32 * 1024
|
|
||||||
|
|
||||||
|
|
||||||
def chunkify(msg, max_chunk):
|
|
||||||
if len(msg) < max_chunk - 1:
|
|
||||||
yield "".join((msg, "\n"))
|
|
||||||
else:
|
|
||||||
yield "".join((json.dumps({"chunk-stream": None}), "\n"))
|
|
||||||
|
|
||||||
args = [iter(msg)] * (max_chunk - 1)
|
|
||||||
for m in map("".join, itertools.zip_longest(*args, fillvalue="")):
|
|
||||||
yield "".join(itertools.chain(m, "\n"))
|
|
||||||
yield "\n"
|
|
||||||
|
|
||||||
|
|
||||||
def json_serialize(obj):
|
|
||||||
if isinstance(obj, datetime):
|
|
||||||
return obj.isoformat()
|
|
||||||
raise TypeError("Type %s not serializeable" % type(obj))
|
|
||||||
|
|
||||||
|
|
||||||
class StreamConnection(object):
|
|
||||||
def __init__(self, reader, writer, timeout, max_chunk=DEFAULT_MAX_CHUNK):
|
|
||||||
self.reader = reader
|
|
||||||
self.writer = writer
|
|
||||||
self.timeout = timeout
|
|
||||||
self.max_chunk = max_chunk
|
|
||||||
|
|
||||||
@property
|
|
||||||
def address(self):
|
|
||||||
return self.writer.get_extra_info("peername")
|
|
||||||
|
|
||||||
async def send_message(self, msg):
|
|
||||||
for c in chunkify(json.dumps(msg, default=json_serialize), self.max_chunk):
|
|
||||||
self.writer.write(c.encode("utf-8"))
|
|
||||||
await self.writer.drain()
|
|
||||||
|
|
||||||
async def recv_message(self):
|
|
||||||
l = await self.recv()
|
|
||||||
|
|
||||||
m = json.loads(l)
|
|
||||||
if not m:
|
|
||||||
return m
|
|
||||||
|
|
||||||
if "chunk-stream" in m:
|
|
||||||
lines = []
|
|
||||||
while True:
|
|
||||||
l = await self.recv()
|
|
||||||
if not l:
|
|
||||||
break
|
|
||||||
lines.append(l)
|
|
||||||
|
|
||||||
m = json.loads("".join(lines))
|
|
||||||
|
|
||||||
return m
|
|
||||||
|
|
||||||
async def send(self, msg):
|
|
||||||
self.writer.write(("%s\n" % msg).encode("utf-8"))
|
|
||||||
await self.writer.drain()
|
|
||||||
|
|
||||||
async def recv(self):
|
|
||||||
if self.timeout < 0:
|
|
||||||
line = await self.reader.readline()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
line = await asyncio.wait_for(self.reader.readline(), self.timeout)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
raise ConnectionError("Timed out waiting for data")
|
|
||||||
|
|
||||||
if not line:
|
|
||||||
raise ConnectionClosedError("Connection closed")
|
|
||||||
|
|
||||||
line = line.decode("utf-8")
|
|
||||||
|
|
||||||
if not line.endswith("\n"):
|
|
||||||
raise ConnectionError("Bad message %r" % (line))
|
|
||||||
|
|
||||||
return line.rstrip()
|
|
||||||
|
|
||||||
async def close(self):
|
|
||||||
self.reader = None
|
|
||||||
if self.writer is not None:
|
|
||||||
self.writer.close()
|
|
||||||
self.writer = None
|
|
||||||
|
|
||||||
|
|
||||||
class WebsocketConnection(object):
|
|
||||||
def __init__(self, socket, timeout):
|
|
||||||
self.socket = socket
|
|
||||||
self.timeout = timeout
|
|
||||||
|
|
||||||
@property
|
|
||||||
def address(self):
|
|
||||||
return ":".join(str(s) for s in self.socket.remote_address)
|
|
||||||
|
|
||||||
async def send_message(self, msg):
|
|
||||||
await self.send(json.dumps(msg, default=json_serialize))
|
|
||||||
|
|
||||||
async def recv_message(self):
|
|
||||||
m = await self.recv()
|
|
||||||
return json.loads(m)
|
|
||||||
|
|
||||||
async def send(self, msg):
|
|
||||||
import websockets.exceptions
|
|
||||||
|
|
||||||
try:
|
|
||||||
await self.socket.send(msg)
|
|
||||||
except websockets.exceptions.ConnectionClosed:
|
|
||||||
raise ConnectionClosedError("Connection closed")
|
|
||||||
|
|
||||||
async def recv(self):
|
|
||||||
import websockets.exceptions
|
|
||||||
|
|
||||||
try:
|
|
||||||
if self.timeout < 0:
|
|
||||||
return await self.socket.recv()
|
|
||||||
|
|
||||||
try:
|
|
||||||
return await asyncio.wait_for(self.socket.recv(), self.timeout)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
raise ConnectionError("Timed out waiting for data")
|
|
||||||
except websockets.exceptions.ConnectionClosed:
|
|
||||||
raise ConnectionClosedError("Connection closed")
|
|
||||||
|
|
||||||
async def close(self):
|
|
||||||
if self.socket is not None:
|
|
||||||
await self.socket.close()
|
|
||||||
self.socket = None
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
class ClientError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class InvokeError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ServerError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectionClosedError(Exception):
|
|
||||||
pass
|
|
||||||
@@ -1,391 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
|
|
||||||
import abc
|
|
||||||
import asyncio
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import signal
|
|
||||||
import socket
|
|
||||||
import sys
|
|
||||||
from bb import multiprocessing
|
|
||||||
import logging
|
|
||||||
from .connection import StreamConnection, WebsocketConnection
|
|
||||||
from .exceptions import ClientError, ServerError, ConnectionClosedError, InvokeError
|
|
||||||
|
|
||||||
|
|
||||||
class ClientLoggerAdapter(logging.LoggerAdapter):
|
|
||||||
def process(self, msg, kwargs):
|
|
||||||
return f"[Client {self.extra['address']}] {msg}", kwargs
|
|
||||||
|
|
||||||
|
|
||||||
class AsyncServerConnection(object):
|
|
||||||
# If a handler returns this object (e.g. `return self.NO_RESPONSE`), no
|
|
||||||
# return message will be automatically be sent back to the client
|
|
||||||
NO_RESPONSE = object()
|
|
||||||
|
|
||||||
def __init__(self, socket, proto_name, logger):
|
|
||||||
self.socket = socket
|
|
||||||
self.proto_name = proto_name
|
|
||||||
self.handlers = {
|
|
||||||
"ping": self.handle_ping,
|
|
||||||
}
|
|
||||||
self.logger = ClientLoggerAdapter(
|
|
||||||
logger,
|
|
||||||
{
|
|
||||||
"address": socket.address,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.client_headers = {}
|
|
||||||
|
|
||||||
async def close(self):
|
|
||||||
await self.socket.close()
|
|
||||||
|
|
||||||
async def handle_headers(self, headers):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
async def process_requests(self):
|
|
||||||
try:
|
|
||||||
self.logger.info("Client %r connected" % (self.socket.address,))
|
|
||||||
|
|
||||||
# Read protocol and version
|
|
||||||
client_protocol = await self.socket.recv()
|
|
||||||
if not client_protocol:
|
|
||||||
return
|
|
||||||
|
|
||||||
(client_proto_name, client_proto_version) = client_protocol.split()
|
|
||||||
if client_proto_name != self.proto_name:
|
|
||||||
self.logger.debug("Rejecting invalid protocol %s" % (self.proto_name))
|
|
||||||
return
|
|
||||||
|
|
||||||
self.proto_version = tuple(int(v) for v in client_proto_version.split("."))
|
|
||||||
if not self.validate_proto_version():
|
|
||||||
self.logger.debug(
|
|
||||||
"Rejecting invalid protocol version %s" % (client_proto_version)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Read headers
|
|
||||||
self.client_headers = {}
|
|
||||||
while True:
|
|
||||||
header = await self.socket.recv()
|
|
||||||
if not header:
|
|
||||||
# Empty line. End of headers
|
|
||||||
break
|
|
||||||
tag, value = header.split(":", 1)
|
|
||||||
self.client_headers[tag.lower()] = value.strip()
|
|
||||||
|
|
||||||
if self.client_headers.get("needs-headers", "false") == "true":
|
|
||||||
for k, v in (await self.handle_headers(self.client_headers)).items():
|
|
||||||
await self.socket.send("%s: %s" % (k, v))
|
|
||||||
await self.socket.send("")
|
|
||||||
|
|
||||||
# Handle messages
|
|
||||||
while True:
|
|
||||||
d = await self.socket.recv_message()
|
|
||||||
if d is None:
|
|
||||||
break
|
|
||||||
try:
|
|
||||||
response = await self.dispatch_message(d)
|
|
||||||
except InvokeError as e:
|
|
||||||
await self.socket.send_message(
|
|
||||||
{"invoke-error": {"message": str(e)}}
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
if response is not self.NO_RESPONSE:
|
|
||||||
await self.socket.send_message(response)
|
|
||||||
|
|
||||||
except ConnectionClosedError as e:
|
|
||||||
self.logger.info(str(e))
|
|
||||||
except (ClientError, ConnectionError) as e:
|
|
||||||
self.logger.error(str(e))
|
|
||||||
finally:
|
|
||||||
await self.close()
|
|
||||||
|
|
||||||
async def dispatch_message(self, msg):
|
|
||||||
for k in self.handlers.keys():
|
|
||||||
if k in msg:
|
|
||||||
self.logger.debug("Handling %s" % k)
|
|
||||||
return await self.handlers[k](msg[k])
|
|
||||||
|
|
||||||
raise ClientError("Unrecognized command %r" % msg)
|
|
||||||
|
|
||||||
async def handle_ping(self, request):
|
|
||||||
return {"alive": True}
|
|
||||||
|
|
||||||
|
|
||||||
class StreamServer(object):
|
|
||||||
def __init__(self, handler, logger):
|
|
||||||
self.handler = handler
|
|
||||||
self.logger = logger
|
|
||||||
self.closed = False
|
|
||||||
|
|
||||||
async def handle_stream_client(self, reader, writer):
|
|
||||||
# writer.transport.set_write_buffer_limits(0)
|
|
||||||
socket = StreamConnection(reader, writer, -1)
|
|
||||||
if self.closed:
|
|
||||||
await socket.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
await self.handler(socket)
|
|
||||||
|
|
||||||
async def stop(self):
|
|
||||||
self.closed = True
|
|
||||||
|
|
||||||
|
|
||||||
class TCPStreamServer(StreamServer):
|
|
||||||
def __init__(self, host, port, handler, logger):
|
|
||||||
super().__init__(handler, logger)
|
|
||||||
self.host = host
|
|
||||||
self.port = port
|
|
||||||
|
|
||||||
def start(self, loop):
|
|
||||||
self.server = loop.run_until_complete(
|
|
||||||
asyncio.start_server(self.handle_stream_client, self.host, self.port)
|
|
||||||
)
|
|
||||||
|
|
||||||
for s in self.server.sockets:
|
|
||||||
self.logger.debug("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)
|
|
||||||
|
|
||||||
# Enable keep alives. This prevents broken client connections
|
|
||||||
# from persisting on the server for long periods of time.
|
|
||||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
||||||
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 30)
|
|
||||||
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 15)
|
|
||||||
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 4)
|
|
||||||
|
|
||||||
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])
|
|
||||||
|
|
||||||
return [self.server.wait_closed()]
|
|
||||||
|
|
||||||
async def stop(self):
|
|
||||||
await super().stop()
|
|
||||||
self.server.close()
|
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UnixStreamServer(StreamServer):
|
|
||||||
def __init__(self, path, handler, logger):
|
|
||||||
super().__init__(handler, logger)
|
|
||||||
self.path = path
|
|
||||||
|
|
||||||
def start(self, loop):
|
|
||||||
cwd = os.getcwd()
|
|
||||||
try:
|
|
||||||
# Work around path length limits in AF_UNIX
|
|
||||||
os.chdir(os.path.dirname(self.path))
|
|
||||||
self.server = loop.run_until_complete(
|
|
||||||
asyncio.start_unix_server(
|
|
||||||
self.handle_stream_client, os.path.basename(self.path)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
os.chdir(cwd)
|
|
||||||
|
|
||||||
self.logger.debug("Listening on %r" % self.path)
|
|
||||||
self.address = "unix://%s" % os.path.abspath(self.path)
|
|
||||||
return [self.server.wait_closed()]
|
|
||||||
|
|
||||||
async def stop(self):
|
|
||||||
await super().stop()
|
|
||||||
self.server.close()
|
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
os.unlink(self.path)
|
|
||||||
|
|
||||||
|
|
||||||
class WebsocketsServer(object):
|
|
||||||
def __init__(self, host, port, handler, logger):
|
|
||||||
self.host = host
|
|
||||||
self.port = port
|
|
||||||
self.handler = handler
|
|
||||||
self.logger = logger
|
|
||||||
|
|
||||||
def start(self, loop):
|
|
||||||
import websockets.server
|
|
||||||
|
|
||||||
self.server = loop.run_until_complete(
|
|
||||||
websockets.server.serve(
|
|
||||||
self.client_handler,
|
|
||||||
self.host,
|
|
||||||
self.port,
|
|
||||||
ping_interval=None,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for s in self.server.sockets:
|
|
||||||
self.logger.debug("Listening on %r" % (s.getsockname(),))
|
|
||||||
|
|
||||||
# Enable keep alives. This prevents broken client connections
|
|
||||||
# from persisting on the server for long periods of time.
|
|
||||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
||||||
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 30)
|
|
||||||
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 15)
|
|
||||||
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 4)
|
|
||||||
|
|
||||||
name = self.server.sockets[0].getsockname()
|
|
||||||
if self.server.sockets[0].family == socket.AF_INET6:
|
|
||||||
self.address = "ws://[%s]:%d" % (name[0], name[1])
|
|
||||||
else:
|
|
||||||
self.address = "ws://%s:%d" % (name[0], name[1])
|
|
||||||
|
|
||||||
return [self.server.wait_closed()]
|
|
||||||
|
|
||||||
async def stop(self):
|
|
||||||
self.server.close()
|
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def client_handler(self, websocket):
|
|
||||||
socket = WebsocketConnection(websocket, -1)
|
|
||||||
await self.handler(socket)
|
|
||||||
|
|
||||||
|
|
||||||
class AsyncServer(object):
|
|
||||||
def __init__(self, logger):
|
|
||||||
self.logger = logger
|
|
||||||
self.loop = None
|
|
||||||
self.run_tasks = []
|
|
||||||
|
|
||||||
def start_tcp_server(self, host, port):
|
|
||||||
self.server = TCPStreamServer(host, port, self._client_handler, self.logger)
|
|
||||||
|
|
||||||
def start_unix_server(self, path):
|
|
||||||
self.server = UnixStreamServer(path, self._client_handler, self.logger)
|
|
||||||
|
|
||||||
def start_websocket_server(self, host, port):
|
|
||||||
self.server = WebsocketsServer(host, port, self._client_handler, self.logger)
|
|
||||||
|
|
||||||
async def _client_handler(self, socket):
|
|
||||||
address = socket.address
|
|
||||||
try:
|
|
||||||
client = self.accept_client(socket)
|
|
||||||
await client.process_requests()
|
|
||||||
except Exception as e:
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
self.logger.error(
|
|
||||||
"Error from client %s: %s" % (address, str(e)), exc_info=True
|
|
||||||
)
|
|
||||||
traceback.print_exc()
|
|
||||||
finally:
|
|
||||||
self.logger.debug("Client %s disconnected", address)
|
|
||||||
await socket.close()
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def accept_client(self, socket):
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def stop(self):
|
|
||||||
self.logger.debug("Stopping server")
|
|
||||||
await self.server.stop()
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
tasks = self.server.start(self.loop)
|
|
||||||
self.address = self.server.address
|
|
||||||
return tasks
|
|
||||||
|
|
||||||
def signal_handler(self):
|
|
||||||
self.logger.debug("Got exit signal")
|
|
||||||
self.loop.create_task(self.stop())
|
|
||||||
|
|
||||||
def _serve_forever(self, tasks):
|
|
||||||
try:
|
|
||||||
self.loop.add_signal_handler(signal.SIGTERM, self.signal_handler)
|
|
||||||
self.loop.add_signal_handler(signal.SIGINT, self.signal_handler)
|
|
||||||
self.loop.add_signal_handler(signal.SIGQUIT, self.signal_handler)
|
|
||||||
signal.pthread_sigmask(signal.SIG_UNBLOCK, [signal.SIGTERM])
|
|
||||||
|
|
||||||
self.loop.run_until_complete(asyncio.gather(*tasks))
|
|
||||||
|
|
||||||
self.logger.debug("Server shutting down")
|
|
||||||
finally:
|
|
||||||
self.server.cleanup()
|
|
||||||
|
|
||||||
def serve_forever(self):
|
|
||||||
"""
|
|
||||||
Serve requests in the current process
|
|
||||||
"""
|
|
||||||
self._create_loop()
|
|
||||||
tasks = self.start()
|
|
||||||
self._serve_forever(tasks)
|
|
||||||
self.loop.close()
|
|
||||||
|
|
||||||
def _create_loop(self):
|
|
||||||
# Create loop and override any loop that may have existed in
|
|
||||||
# a parent process. It is possible that the usecases of
|
|
||||||
# serve_forever might be constrained enough to allow using
|
|
||||||
# get_event_loop here, but better safe than sorry for now.
|
|
||||||
self.loop = asyncio.new_event_loop()
|
|
||||||
asyncio.set_event_loop(self.loop)
|
|
||||||
|
|
||||||
def serve_as_process(self, *, prefunc=None, args=(), log_level=None):
|
|
||||||
"""
|
|
||||||
Serve requests in a child process
|
|
||||||
"""
|
|
||||||
|
|
||||||
def run(queue):
|
|
||||||
# Create loop and override any loop that may have existed
|
|
||||||
# in a parent process. Without doing this and instead
|
|
||||||
# using get_event_loop, at the very minimum the hashserv
|
|
||||||
# unit tests will hang when running the second test.
|
|
||||||
# This happens since get_event_loop in the spawned server
|
|
||||||
# process for the second testcase ends up with the loop
|
|
||||||
# from the hashserv client created in the unit test process
|
|
||||||
# when running the first testcase. The problem is somewhat
|
|
||||||
# more general, though, as any potential use of asyncio in
|
|
||||||
# Cooker could create a loop that needs to replaced in this
|
|
||||||
# new process.
|
|
||||||
self._create_loop()
|
|
||||||
try:
|
|
||||||
self.address = None
|
|
||||||
tasks = self.start()
|
|
||||||
finally:
|
|
||||||
# Always put the server address to wake up the parent task
|
|
||||||
queue.put(self.address)
|
|
||||||
queue.close()
|
|
||||||
|
|
||||||
if prefunc is not None:
|
|
||||||
prefunc(self, *args)
|
|
||||||
|
|
||||||
if log_level is not None:
|
|
||||||
self.logger.setLevel(log_level)
|
|
||||||
|
|
||||||
self._serve_forever(tasks)
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 6):
|
|
||||||
self.loop.run_until_complete(self.loop.shutdown_asyncgens())
|
|
||||||
self.loop.close()
|
|
||||||
|
|
||||||
queue = multiprocessing.Queue()
|
|
||||||
|
|
||||||
# Temporarily block SIGTERM. The server process will inherit this
|
|
||||||
# block which will ensure it doesn't receive the SIGTERM until the
|
|
||||||
# handler is ready for it
|
|
||||||
mask = signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGTERM])
|
|
||||||
try:
|
|
||||||
self.process = multiprocessing.Process(target=run, args=(queue,))
|
|
||||||
self.process.start()
|
|
||||||
|
|
||||||
self.address = queue.get()
|
|
||||||
queue.close()
|
|
||||||
queue.join_thread()
|
|
||||||
|
|
||||||
return self.process
|
|
||||||
finally:
|
|
||||||
signal.pthread_sigmask(signal.SIG_SETMASK, mask)
|
|
||||||
@@ -20,12 +20,10 @@ import itertools
|
|||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import stat
|
import stat
|
||||||
import datetime
|
|
||||||
import bb
|
import bb
|
||||||
import bb.msg
|
import bb.msg
|
||||||
import bb.process
|
import bb.process
|
||||||
import bb.progress
|
import bb.progress
|
||||||
from io import StringIO
|
|
||||||
from bb import data, event, utils
|
from bb import data, event, utils
|
||||||
|
|
||||||
bblogger = logging.getLogger('BitBake')
|
bblogger = logging.getLogger('BitBake')
|
||||||
@@ -178,9 +176,7 @@ class StdoutNoopContextManager:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
if "name" in dir(sys.stdout):
|
return sys.stdout.name
|
||||||
return sys.stdout.name
|
|
||||||
return "<mem>"
|
|
||||||
|
|
||||||
|
|
||||||
def exec_func(func, d, dirs = None):
|
def exec_func(func, d, dirs = None):
|
||||||
@@ -299,25 +295,9 @@ def exec_func_python(func, d, runfile, cwd=None):
|
|||||||
lineno = int(d.getVarFlag(func, "lineno", False))
|
lineno = int(d.getVarFlag(func, "lineno", False))
|
||||||
bb.methodpool.insert_method(func, text, fn, lineno - 1)
|
bb.methodpool.insert_method(func, text, fn, lineno - 1)
|
||||||
|
|
||||||
if verboseStdoutLogging:
|
comp = utils.better_compile(code, func, "exec_python_func() autogenerated")
|
||||||
sys.stdout.flush()
|
utils.better_exec(comp, {"d": d}, code, "exec_python_func() autogenerated")
|
||||||
sys.stderr.flush()
|
|
||||||
currout = sys.stdout
|
|
||||||
currerr = sys.stderr
|
|
||||||
sys.stderr = sys.stdout = execio = StringIO()
|
|
||||||
comp = utils.better_compile(code, func, "exec_func_python() autogenerated")
|
|
||||||
utils.better_exec(comp, {"d": d}, code, "exec_func_python() autogenerated")
|
|
||||||
finally:
|
finally:
|
||||||
if verboseStdoutLogging:
|
|
||||||
execio.flush()
|
|
||||||
logger.plain("%s" % execio.getvalue())
|
|
||||||
sys.stdout = currout
|
|
||||||
sys.stderr = currerr
|
|
||||||
execio.close()
|
|
||||||
# We want any stdout/stderr to be printed before any other log messages to make debugging
|
|
||||||
# more accurate. In some cases we seem to lose stdout/stderr entirely in logging tests without this.
|
|
||||||
sys.stdout.flush()
|
|
||||||
sys.stderr.flush()
|
|
||||||
bb.debug(2, "Python function %s finished" % func)
|
bb.debug(2, "Python function %s finished" % func)
|
||||||
|
|
||||||
if cwd and olddir:
|
if cwd and olddir:
|
||||||
@@ -456,11 +436,7 @@ exit $ret
|
|||||||
if fakerootcmd:
|
if fakerootcmd:
|
||||||
cmd = [fakerootcmd, runfile]
|
cmd = [fakerootcmd, runfile]
|
||||||
|
|
||||||
# We only want to output to logger via LogTee if stdout is sys.__stdout__ (which will either
|
if verboseStdoutLogging:
|
||||||
# be real stdout or subprocess PIPE or similar). In other cases we are being run "recursively",
|
|
||||||
# ie. inside another function, in which case stdout is already being captured so we don't
|
|
||||||
# want to Tee here as output would be printed twice, and out of order.
|
|
||||||
if verboseStdoutLogging and sys.stdout == sys.__stdout__:
|
|
||||||
logfile = LogTee(logger, StdoutNoopContextManager())
|
logfile = LogTee(logger, StdoutNoopContextManager())
|
||||||
else:
|
else:
|
||||||
logfile = StdoutNoopContextManager()
|
logfile = StdoutNoopContextManager()
|
||||||
@@ -589,8 +565,10 @@ exit $ret
|
|||||||
def _task_data(fn, task, d):
|
def _task_data(fn, task, d):
|
||||||
localdata = bb.data.createCopy(d)
|
localdata = bb.data.createCopy(d)
|
||||||
localdata.setVar('BB_FILENAME', fn)
|
localdata.setVar('BB_FILENAME', fn)
|
||||||
|
localdata.setVar('BB_CURRENTTASK', task[3:])
|
||||||
localdata.setVar('OVERRIDES', 'task-%s:%s' %
|
localdata.setVar('OVERRIDES', 'task-%s:%s' %
|
||||||
(task[3:].replace('_', '-'), d.getVar('OVERRIDES', False)))
|
(task[3:].replace('_', '-'), d.getVar('OVERRIDES', False)))
|
||||||
|
localdata.finalize()
|
||||||
bb.data.expandKeys(localdata)
|
bb.data.expandKeys(localdata)
|
||||||
return localdata
|
return localdata
|
||||||
|
|
||||||
@@ -601,11 +579,11 @@ def _exec_task(fn, task, d, quieterr):
|
|||||||
running it with its own local metadata, and with some useful variables set.
|
running it with its own local metadata, and with some useful variables set.
|
||||||
"""
|
"""
|
||||||
if not d.getVarFlag(task, 'task', False):
|
if not d.getVarFlag(task, 'task', False):
|
||||||
event.fire(TaskInvalid(task, fn, d), d)
|
event.fire(TaskInvalid(task, d), d)
|
||||||
logger.error("No such task: %s" % task)
|
logger.error("No such task: %s" % task)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
logger.debug("Executing task %s", task)
|
logger.debug(1, "Executing task %s", task)
|
||||||
|
|
||||||
localdata = _task_data(fn, task, d)
|
localdata = _task_data(fn, task, d)
|
||||||
tempdir = localdata.getVar('T')
|
tempdir = localdata.getVar('T')
|
||||||
@@ -618,7 +596,7 @@ def _exec_task(fn, task, d, quieterr):
|
|||||||
curnice = os.nice(0)
|
curnice = os.nice(0)
|
||||||
nice = int(nice) - curnice
|
nice = int(nice) - curnice
|
||||||
newnice = os.nice(nice)
|
newnice = os.nice(nice)
|
||||||
logger.debug("Renice to %s " % newnice)
|
logger.debug(1, "Renice to %s " % newnice)
|
||||||
ionice = localdata.getVar("BB_TASK_IONICE_LEVEL")
|
ionice = localdata.getVar("BB_TASK_IONICE_LEVEL")
|
||||||
if ionice:
|
if ionice:
|
||||||
try:
|
try:
|
||||||
@@ -637,8 +615,7 @@ def _exec_task(fn, task, d, quieterr):
|
|||||||
logorder = os.path.join(tempdir, 'log.task_order')
|
logorder = os.path.join(tempdir, 'log.task_order')
|
||||||
try:
|
try:
|
||||||
with open(logorder, 'a') as logorderfile:
|
with open(logorder, 'a') as logorderfile:
|
||||||
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S.%f")
|
logorderfile.write('{0} ({1}): {2}\n'.format(task, os.getpid(), logbase))
|
||||||
logorderfile.write('{0} {1} ({2}): {3}\n'.format(timestamp, task, os.getpid(), logbase))
|
|
||||||
except OSError:
|
except OSError:
|
||||||
logger.exception("Opening log file '%s'", logorder)
|
logger.exception("Opening log file '%s'", logorder)
|
||||||
pass
|
pass
|
||||||
@@ -705,55 +682,47 @@ def _exec_task(fn, task, d, quieterr):
|
|||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
event.fire(TaskStarted(task, fn, logfn, flags, localdata), localdata)
|
event.fire(TaskStarted(task, fn, logfn, flags, localdata), localdata)
|
||||||
|
except (bb.BBHandledException, SystemExit):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
try:
|
||||||
for func in (prefuncs or '').split():
|
for func in (prefuncs or '').split():
|
||||||
exec_func(func, localdata)
|
exec_func(func, localdata)
|
||||||
exec_func(task, localdata)
|
exec_func(task, localdata)
|
||||||
for func in (postfuncs or '').split():
|
for func in (postfuncs or '').split():
|
||||||
exec_func(func, localdata)
|
exec_func(func, localdata)
|
||||||
finally:
|
except bb.BBHandledException:
|
||||||
# Need to flush and close the logs before sending events where the
|
event.fire(TaskFailed(task, fn, logfn, localdata, True), localdata)
|
||||||
# UI may try to look at the logs.
|
return 1
|
||||||
sys.stdout.flush()
|
except Exception as exc:
|
||||||
sys.stderr.flush()
|
if quieterr:
|
||||||
|
event.fire(TaskFailedSilent(task, fn, logfn, localdata), localdata)
|
||||||
|
else:
|
||||||
|
errprinted = errchk.triggered
|
||||||
|
logger.error(str(exc))
|
||||||
|
event.fire(TaskFailed(task, fn, logfn, localdata, errprinted), localdata)
|
||||||
|
return 1
|
||||||
|
finally:
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
bblogger.removeHandler(handler)
|
bblogger.removeHandler(handler)
|
||||||
|
|
||||||
# Restore the backup fds
|
# Restore the backup fds
|
||||||
os.dup2(osi[0], osi[1])
|
os.dup2(osi[0], osi[1])
|
||||||
os.dup2(oso[0], oso[1])
|
os.dup2(oso[0], oso[1])
|
||||||
os.dup2(ose[0], ose[1])
|
os.dup2(ose[0], ose[1])
|
||||||
|
|
||||||
# Close the backup fds
|
# Close the backup fds
|
||||||
os.close(osi[0])
|
os.close(osi[0])
|
||||||
os.close(oso[0])
|
os.close(oso[0])
|
||||||
os.close(ose[0])
|
os.close(ose[0])
|
||||||
|
|
||||||
logfile.close()
|
|
||||||
if os.path.exists(logfn) and os.path.getsize(logfn) == 0:
|
|
||||||
logger.debug2("Zero size logfn %s, removing", logfn)
|
|
||||||
bb.utils.remove(logfn)
|
|
||||||
bb.utils.remove(loglink)
|
|
||||||
except (Exception, SystemExit) as exc:
|
|
||||||
handled = False
|
|
||||||
if isinstance(exc, bb.BBHandledException):
|
|
||||||
handled = True
|
|
||||||
|
|
||||||
if quieterr:
|
|
||||||
if not handled:
|
|
||||||
logger.warning(repr(exc))
|
|
||||||
event.fire(TaskFailedSilent(task, fn, logfn, localdata), localdata)
|
|
||||||
else:
|
|
||||||
errprinted = errchk.triggered
|
|
||||||
# If the output is already on stdout, we've printed the information in the
|
|
||||||
# logs once already so don't duplicate
|
|
||||||
if verboseStdoutLogging or handled:
|
|
||||||
errprinted = True
|
|
||||||
if not handled:
|
|
||||||
logger.error(repr(exc))
|
|
||||||
event.fire(TaskFailed(task, fn, logfn, localdata, errprinted), localdata)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
logfile.close()
|
||||||
|
if os.path.exists(logfn) and os.path.getsize(logfn) == 0:
|
||||||
|
logger.debug(2, "Zero size logfn %s, removing", logfn)
|
||||||
|
bb.utils.remove(logfn)
|
||||||
|
bb.utils.remove(loglink)
|
||||||
event.fire(TaskSucceeded(task, fn, logfn, localdata), localdata)
|
event.fire(TaskSucceeded(task, fn, logfn, localdata), localdata)
|
||||||
|
|
||||||
if not localdata.getVarFlag(task, 'nostamp', False) and not localdata.getVarFlag(task, 'selfstamp', False):
|
if not localdata.getVarFlag(task, 'nostamp', False) and not localdata.getVarFlag(task, 'selfstamp', False):
|
||||||
@@ -791,7 +760,44 @@ def exec_task(fn, task, d, profile = False):
|
|||||||
event.fire(failedevent, d)
|
event.fire(failedevent, d)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def _get_cleanmask(taskname, mcfn):
|
def stamp_internal(taskname, d, file_name, baseonly=False, noextra=False):
|
||||||
|
"""
|
||||||
|
Internal stamp helper function
|
||||||
|
Makes sure the stamp directory exists
|
||||||
|
Returns the stamp path+filename
|
||||||
|
|
||||||
|
In the bitbake core, d can be a CacheData and file_name will be set.
|
||||||
|
When called in task context, d will be a data store, file_name will not be set
|
||||||
|
"""
|
||||||
|
taskflagname = taskname
|
||||||
|
if taskname.endswith("_setscene") and taskname != "do_setscene":
|
||||||
|
taskflagname = taskname.replace("_setscene", "")
|
||||||
|
|
||||||
|
if file_name:
|
||||||
|
stamp = d.stamp[file_name]
|
||||||
|
extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
|
||||||
|
else:
|
||||||
|
stamp = d.getVar('STAMP')
|
||||||
|
file_name = d.getVar('BB_FILENAME')
|
||||||
|
extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info') or ""
|
||||||
|
|
||||||
|
if baseonly:
|
||||||
|
return stamp
|
||||||
|
if noextra:
|
||||||
|
extrainfo = ""
|
||||||
|
|
||||||
|
if not stamp:
|
||||||
|
return
|
||||||
|
|
||||||
|
stamp = bb.parse.siggen.stampfile(stamp, file_name, taskname, extrainfo)
|
||||||
|
|
||||||
|
stampdir = os.path.dirname(stamp)
|
||||||
|
if cached_mtime_noerror(stampdir) == 0:
|
||||||
|
bb.utils.mkdirhier(stampdir)
|
||||||
|
|
||||||
|
return stamp
|
||||||
|
|
||||||
|
def stamp_cleanmask_internal(taskname, d, file_name):
|
||||||
"""
|
"""
|
||||||
Internal stamp helper function to generate stamp cleaning mask
|
Internal stamp helper function to generate stamp cleaning mask
|
||||||
Returns the stamp path+filename
|
Returns the stamp path+filename
|
||||||
@@ -799,14 +805,31 @@ def _get_cleanmask(taskname, mcfn):
|
|||||||
In the bitbake core, d can be a CacheData and file_name will be set.
|
In the bitbake core, d can be a CacheData and file_name will be set.
|
||||||
When called in task context, d will be a data store, file_name will not be set
|
When called in task context, d will be a data store, file_name will not be set
|
||||||
"""
|
"""
|
||||||
cleanmask = bb.parse.siggen.stampcleanmask_mcfn(taskname, mcfn)
|
taskflagname = taskname
|
||||||
taskflagname = taskname.replace("_setscene", "")
|
if taskname.endswith("_setscene") and taskname != "do_setscene":
|
||||||
if cleanmask:
|
taskflagname = taskname.replace("_setscene", "")
|
||||||
return [cleanmask, cleanmask.replace(taskflagname, taskflagname + "_setscene")]
|
|
||||||
return []
|
|
||||||
|
|
||||||
def clean_stamp_mcfn(task, mcfn):
|
if file_name:
|
||||||
cleanmask = _get_cleanmask(task, mcfn)
|
stamp = d.stampclean[file_name]
|
||||||
|
extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
|
||||||
|
else:
|
||||||
|
stamp = d.getVar('STAMPCLEAN')
|
||||||
|
file_name = d.getVar('BB_FILENAME')
|
||||||
|
extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info') or ""
|
||||||
|
|
||||||
|
if not stamp:
|
||||||
|
return []
|
||||||
|
|
||||||
|
cleanmask = bb.parse.siggen.stampcleanmask(stamp, file_name, taskname, extrainfo)
|
||||||
|
|
||||||
|
return [cleanmask, cleanmask.replace(taskflagname, taskflagname + "_setscene")]
|
||||||
|
|
||||||
|
def make_stamp(task, d, file_name = None):
|
||||||
|
"""
|
||||||
|
Creates/updates a stamp for a given task
|
||||||
|
(d can be a data dict or dataCache)
|
||||||
|
"""
|
||||||
|
cleanmask = stamp_cleanmask_internal(task, d, file_name)
|
||||||
for mask in cleanmask:
|
for mask in cleanmask:
|
||||||
for name in glob.glob(mask):
|
for name in glob.glob(mask):
|
||||||
# Preserve sigdata files in the stamps directory
|
# Preserve sigdata files in the stamps directory
|
||||||
@@ -817,66 +840,52 @@ def clean_stamp_mcfn(task, mcfn):
|
|||||||
continue
|
continue
|
||||||
os.unlink(name)
|
os.unlink(name)
|
||||||
|
|
||||||
def clean_stamp(task, d):
|
stamp = stamp_internal(task, d, file_name)
|
||||||
mcfn = d.getVar('BB_FILENAME')
|
|
||||||
clean_stamp_mcfn(task, mcfn)
|
|
||||||
|
|
||||||
def make_stamp_mcfn(task, mcfn):
|
|
||||||
|
|
||||||
basestamp = bb.parse.siggen.stampfile_mcfn(task, mcfn)
|
|
||||||
|
|
||||||
stampdir = os.path.dirname(basestamp)
|
|
||||||
if cached_mtime_noerror(stampdir) == 0:
|
|
||||||
bb.utils.mkdirhier(stampdir)
|
|
||||||
|
|
||||||
clean_stamp_mcfn(task, mcfn)
|
|
||||||
|
|
||||||
# Remove the file and recreate to force timestamp
|
# Remove the file and recreate to force timestamp
|
||||||
# change on broken NFS filesystems
|
# change on broken NFS filesystems
|
||||||
if basestamp:
|
if stamp:
|
||||||
bb.utils.remove(basestamp)
|
bb.utils.remove(stamp)
|
||||||
open(basestamp, "w").close()
|
open(stamp, "w").close()
|
||||||
|
|
||||||
def make_stamp(task, d):
|
|
||||||
"""
|
|
||||||
Creates/updates a stamp for a given task
|
|
||||||
"""
|
|
||||||
mcfn = d.getVar('BB_FILENAME')
|
|
||||||
|
|
||||||
make_stamp_mcfn(task, mcfn)
|
|
||||||
|
|
||||||
# If we're in task context, write out a signature file for each task
|
# If we're in task context, write out a signature file for each task
|
||||||
# as it completes
|
# as it completes
|
||||||
if not task.endswith("_setscene"):
|
if not task.endswith("_setscene") and task != "do_setscene" and not file_name:
|
||||||
stampbase = bb.parse.siggen.stampfile_base(mcfn)
|
stampbase = stamp_internal(task, d, None, True)
|
||||||
bb.parse.siggen.dump_sigtask(mcfn, task, stampbase, True)
|
file_name = d.getVar('BB_FILENAME')
|
||||||
|
bb.parse.siggen.dump_sigtask(file_name, task, stampbase, True)
|
||||||
|
|
||||||
|
def del_stamp(task, d, file_name = None):
|
||||||
|
"""
|
||||||
|
Removes a stamp for a given task
|
||||||
|
(d can be a data dict or dataCache)
|
||||||
|
"""
|
||||||
|
stamp = stamp_internal(task, d, file_name)
|
||||||
|
bb.utils.remove(stamp)
|
||||||
|
|
||||||
def find_stale_stamps(task, mcfn):
|
def write_taint(task, d, file_name = None):
|
||||||
current = bb.parse.siggen.stampfile_mcfn(task, mcfn)
|
|
||||||
current2 = bb.parse.siggen.stampfile_mcfn(task + "_setscene", mcfn)
|
|
||||||
cleanmask = _get_cleanmask(task, mcfn)
|
|
||||||
found = []
|
|
||||||
for mask in cleanmask:
|
|
||||||
for name in glob.glob(mask):
|
|
||||||
if "sigdata" in name or "sigbasedata" in name:
|
|
||||||
continue
|
|
||||||
if name.endswith('.taint'):
|
|
||||||
continue
|
|
||||||
if name == current or name == current2:
|
|
||||||
continue
|
|
||||||
logger.debug2("Stampfile %s does not match %s or %s" % (name, current, current2))
|
|
||||||
found.append(name)
|
|
||||||
return found
|
|
||||||
|
|
||||||
def write_taint(task, d):
|
|
||||||
"""
|
"""
|
||||||
Creates a "taint" file which will force the specified task and its
|
Creates a "taint" file which will force the specified task and its
|
||||||
dependents to be re-run the next time by influencing the value of its
|
dependents to be re-run the next time by influencing the value of its
|
||||||
taskhash.
|
taskhash.
|
||||||
|
(d can be a data dict or dataCache)
|
||||||
"""
|
"""
|
||||||
mcfn = d.getVar('BB_FILENAME')
|
import uuid
|
||||||
bb.parse.siggen.invalidate_task(task, mcfn)
|
if file_name:
|
||||||
|
taintfn = d.stamp[file_name] + '.' + task + '.taint'
|
||||||
|
else:
|
||||||
|
taintfn = d.getVar('STAMP') + '.' + task + '.taint'
|
||||||
|
bb.utils.mkdirhier(os.path.dirname(taintfn))
|
||||||
|
# The specific content of the taint file is not really important,
|
||||||
|
# we just need it to be random, so a random UUID is used
|
||||||
|
with open(taintfn, 'w') as taintf:
|
||||||
|
taintf.write(str(uuid.uuid4()))
|
||||||
|
|
||||||
|
def stampfile(taskname, d, file_name = None, noextra=False):
|
||||||
|
"""
|
||||||
|
Return the stamp for a given task
|
||||||
|
(d can be a data dict or dataCache)
|
||||||
|
"""
|
||||||
|
return stamp_internal(taskname, d, file_name, noextra=noextra)
|
||||||
|
|
||||||
def add_tasks(tasklist, d):
|
def add_tasks(tasklist, d):
|
||||||
task_deps = d.getVar('_task_deps', False)
|
task_deps = d.getVar('_task_deps', False)
|
||||||
@@ -901,11 +910,6 @@ def add_tasks(tasklist, d):
|
|||||||
task_deps[name] = {}
|
task_deps[name] = {}
|
||||||
if name in flags:
|
if name in flags:
|
||||||
deptask = d.expand(flags[name])
|
deptask = d.expand(flags[name])
|
||||||
if name in ['noexec', 'fakeroot', 'nostamp']:
|
|
||||||
if deptask != '1':
|
|
||||||
bb.warn("In a future version of BitBake, setting the '{}' flag to something other than '1' "
|
|
||||||
"will result in the flag not being set. See YP bug #13808.".format(name))
|
|
||||||
|
|
||||||
task_deps[name][task] = deptask
|
task_deps[name][task] = deptask
|
||||||
getTask('mcdepends')
|
getTask('mcdepends')
|
||||||
getTask('depends')
|
getTask('depends')
|
||||||
@@ -1004,8 +1008,6 @@ def tasksbetween(task_start, task_end, d):
|
|||||||
def follow_chain(task, endtask, chain=None):
|
def follow_chain(task, endtask, chain=None):
|
||||||
if not chain:
|
if not chain:
|
||||||
chain = []
|
chain = []
|
||||||
if task in chain:
|
|
||||||
bb.fatal("Circular task dependencies as %s depends on itself via the chain %s" % (task, " -> ".join(chain)))
|
|
||||||
chain.append(task)
|
chain.append(task)
|
||||||
for othertask in tasks:
|
for othertask in tasks:
|
||||||
if othertask == task:
|
if othertask == task:
|
||||||
|
|||||||
@@ -19,16 +19,14 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import pickle
|
import pickle
|
||||||
from collections import defaultdict
|
from collections import defaultdict, Mapping
|
||||||
from collections.abc import Mapping
|
|
||||||
import bb.utils
|
import bb.utils
|
||||||
from bb import PrefixLoggerAdapter
|
from bb import PrefixLoggerAdapter
|
||||||
import re
|
import re
|
||||||
import shutil
|
|
||||||
|
|
||||||
logger = logging.getLogger("BitBake.Cache")
|
logger = logging.getLogger("BitBake.Cache")
|
||||||
|
|
||||||
__cache_version__ = "155"
|
__cache_version__ = "153"
|
||||||
|
|
||||||
def getCacheFile(path, filename, mc, data_hash):
|
def getCacheFile(path, filename, mc, data_hash):
|
||||||
mcspec = ''
|
mcspec = ''
|
||||||
@@ -55,12 +53,12 @@ class RecipeInfoCommon(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pkgvar(cls, var, packages, metadata):
|
def pkgvar(cls, var, packages, metadata):
|
||||||
return dict((pkg, cls.depvar("%s:%s" % (var, pkg), metadata))
|
return dict((pkg, cls.depvar("%s_%s" % (var, pkg), metadata))
|
||||||
for pkg in packages)
|
for pkg in packages)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def taskvar(cls, var, tasks, metadata):
|
def taskvar(cls, var, tasks, metadata):
|
||||||
return dict((task, cls.getvar("%s:task-%s" % (var, task), metadata))
|
return dict((task, cls.getvar("%s_task-%s" % (var, task), metadata))
|
||||||
for task in tasks)
|
for task in tasks)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -96,7 +94,6 @@ class CoreRecipeInfo(RecipeInfoCommon):
|
|||||||
if not self.packages:
|
if not self.packages:
|
||||||
self.packages.append(self.pn)
|
self.packages.append(self.pn)
|
||||||
self.packages_dynamic = self.listvar('PACKAGES_DYNAMIC', metadata)
|
self.packages_dynamic = self.listvar('PACKAGES_DYNAMIC', metadata)
|
||||||
self.rprovides_pkg = self.pkgvar('RPROVIDES', self.packages, metadata)
|
|
||||||
|
|
||||||
self.skipreason = self.getvar('__SKIPPED', metadata)
|
self.skipreason = self.getvar('__SKIPPED', metadata)
|
||||||
if self.skipreason:
|
if self.skipreason:
|
||||||
@@ -105,7 +102,7 @@ class CoreRecipeInfo(RecipeInfoCommon):
|
|||||||
|
|
||||||
self.tasks = metadata.getVar('__BBTASKS', False)
|
self.tasks = metadata.getVar('__BBTASKS', False)
|
||||||
|
|
||||||
self.basetaskhashes = metadata.getVar('__siggen_basehashes', False) or {}
|
self.basetaskhashes = self.taskvar('BB_BASEHASH', self.tasks, metadata)
|
||||||
self.hashfilename = self.getvar('BB_HASHFILENAME', metadata)
|
self.hashfilename = self.getvar('BB_HASHFILENAME', metadata)
|
||||||
|
|
||||||
self.task_deps = metadata.getVar('_task_deps', False) or {'tasks': [], 'parents': {}}
|
self.task_deps = metadata.getVar('_task_deps', False) or {'tasks': [], 'parents': {}}
|
||||||
@@ -123,12 +120,12 @@ class CoreRecipeInfo(RecipeInfoCommon):
|
|||||||
self.depends = self.depvar('DEPENDS', metadata)
|
self.depends = self.depvar('DEPENDS', metadata)
|
||||||
self.rdepends = self.depvar('RDEPENDS', metadata)
|
self.rdepends = self.depvar('RDEPENDS', metadata)
|
||||||
self.rrecommends = self.depvar('RRECOMMENDS', 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)
|
self.rdepends_pkg = self.pkgvar('RDEPENDS', self.packages, metadata)
|
||||||
self.rrecommends_pkg = self.pkgvar('RRECOMMENDS', self.packages, metadata)
|
self.rrecommends_pkg = self.pkgvar('RRECOMMENDS', self.packages, metadata)
|
||||||
self.inherits = self.getvar('__inherit_cache', metadata, expand=False)
|
self.inherits = self.getvar('__inherit_cache', metadata, expand=False)
|
||||||
self.fakerootenv = self.getvar('FAKEROOTENV', metadata)
|
self.fakerootenv = self.getvar('FAKEROOTENV', metadata)
|
||||||
self.fakerootdirs = self.getvar('FAKEROOTDIRS', metadata)
|
self.fakerootdirs = self.getvar('FAKEROOTDIRS', metadata)
|
||||||
self.fakerootlogs = self.getvar('FAKEROOTLOGS', metadata)
|
|
||||||
self.fakerootnoenv = self.getvar('FAKEROOTNOENV', metadata)
|
self.fakerootnoenv = self.getvar('FAKEROOTNOENV', metadata)
|
||||||
self.extradepsfunc = self.getvar('calculate_extra_depends', metadata)
|
self.extradepsfunc = self.getvar('calculate_extra_depends', metadata)
|
||||||
|
|
||||||
@@ -166,7 +163,6 @@ class CoreRecipeInfo(RecipeInfoCommon):
|
|||||||
cachedata.fakerootenv = {}
|
cachedata.fakerootenv = {}
|
||||||
cachedata.fakerootnoenv = {}
|
cachedata.fakerootnoenv = {}
|
||||||
cachedata.fakerootdirs = {}
|
cachedata.fakerootdirs = {}
|
||||||
cachedata.fakerootlogs = {}
|
|
||||||
cachedata.extradepsfunc = {}
|
cachedata.extradepsfunc = {}
|
||||||
|
|
||||||
def add_cacheData(self, cachedata, fn):
|
def add_cacheData(self, cachedata, fn):
|
||||||
@@ -216,10 +212,10 @@ class CoreRecipeInfo(RecipeInfoCommon):
|
|||||||
|
|
||||||
# Collect files we may need for possible world-dep
|
# Collect files we may need for possible world-dep
|
||||||
# calculations
|
# calculations
|
||||||
if not bb.utils.to_boolean(self.not_world):
|
if not self.not_world:
|
||||||
cachedata.possible_world.append(fn)
|
cachedata.possible_world.append(fn)
|
||||||
#else:
|
#else:
|
||||||
# logger.debug2("EXCLUDE FROM WORLD: %s", fn)
|
# logger.debug(2, "EXCLUDE FROM WORLD: %s", fn)
|
||||||
|
|
||||||
# create a collection of all targets for sanity checking
|
# create a collection of all targets for sanity checking
|
||||||
# tasks, such as upstream versions, license, and tools for
|
# tasks, such as upstream versions, license, and tools for
|
||||||
@@ -235,116 +231,17 @@ class CoreRecipeInfo(RecipeInfoCommon):
|
|||||||
cachedata.fakerootenv[fn] = self.fakerootenv
|
cachedata.fakerootenv[fn] = self.fakerootenv
|
||||||
cachedata.fakerootnoenv[fn] = self.fakerootnoenv
|
cachedata.fakerootnoenv[fn] = self.fakerootnoenv
|
||||||
cachedata.fakerootdirs[fn] = self.fakerootdirs
|
cachedata.fakerootdirs[fn] = self.fakerootdirs
|
||||||
cachedata.fakerootlogs[fn] = self.fakerootlogs
|
|
||||||
cachedata.extradepsfunc[fn] = self.extradepsfunc
|
cachedata.extradepsfunc[fn] = self.extradepsfunc
|
||||||
|
|
||||||
|
|
||||||
class SiggenRecipeInfo(RecipeInfoCommon):
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
classname = "SiggenRecipeInfo"
|
|
||||||
cachefile = "bb_cache_" + classname +".dat"
|
|
||||||
# we don't want to show this information in graph files so don't set cachefields
|
|
||||||
#cachefields = []
|
|
||||||
|
|
||||||
def __init__(self, filename, metadata):
|
|
||||||
self.siggen_gendeps = metadata.getVar("__siggen_gendeps", False)
|
|
||||||
self.siggen_varvals = metadata.getVar("__siggen_varvals", False)
|
|
||||||
self.siggen_taskdeps = metadata.getVar("__siggen_taskdeps", False)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def init_cacheData(cls, cachedata):
|
|
||||||
cachedata.siggen_taskdeps = {}
|
|
||||||
cachedata.siggen_gendeps = {}
|
|
||||||
cachedata.siggen_varvals = {}
|
|
||||||
|
|
||||||
def add_cacheData(self, cachedata, fn):
|
|
||||||
cachedata.siggen_gendeps[fn] = self.siggen_gendeps
|
|
||||||
cachedata.siggen_varvals[fn] = self.siggen_varvals
|
|
||||||
cachedata.siggen_taskdeps[fn] = self.siggen_taskdeps
|
|
||||||
|
|
||||||
# The siggen variable data is large and impacts:
|
|
||||||
# - bitbake's overall memory usage
|
|
||||||
# - the amount of data sent over IPC between parsing processes and the server
|
|
||||||
# - the size of the cache files on disk
|
|
||||||
# - the size of "sigdata" hash information files on disk
|
|
||||||
# The data consists of strings (some large) or frozenset lists of variables
|
|
||||||
# As such, we a) deplicate the data here and b) pass references to the object at second
|
|
||||||
# access (e.g. over IPC or saving into pickle).
|
|
||||||
|
|
||||||
store = {}
|
|
||||||
save_map = {}
|
|
||||||
save_count = 1
|
|
||||||
restore_map = {}
|
|
||||||
restore_count = {}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def reset(cls):
|
|
||||||
# Needs to be called before starting new streamed data in a given process
|
|
||||||
# (e.g. writing out the cache again)
|
|
||||||
cls.save_map = {}
|
|
||||||
cls.save_count = 1
|
|
||||||
cls.restore_map = {}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _save(cls, deps):
|
|
||||||
ret = []
|
|
||||||
if not deps:
|
|
||||||
return deps
|
|
||||||
for dep in deps:
|
|
||||||
fs = deps[dep]
|
|
||||||
if fs is None:
|
|
||||||
ret.append((dep, None, None))
|
|
||||||
elif fs in cls.save_map:
|
|
||||||
ret.append((dep, None, cls.save_map[fs]))
|
|
||||||
else:
|
|
||||||
cls.save_map[fs] = cls.save_count
|
|
||||||
ret.append((dep, fs, cls.save_count))
|
|
||||||
cls.save_count = cls.save_count + 1
|
|
||||||
return ret
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _restore(cls, deps, pid):
|
|
||||||
ret = {}
|
|
||||||
if not deps:
|
|
||||||
return deps
|
|
||||||
if pid not in cls.restore_map:
|
|
||||||
cls.restore_map[pid] = {}
|
|
||||||
map = cls.restore_map[pid]
|
|
||||||
for dep, fs, mapnum in deps:
|
|
||||||
if fs is None and mapnum is None:
|
|
||||||
ret[dep] = None
|
|
||||||
elif fs is None:
|
|
||||||
ret[dep] = map[mapnum]
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
fs = cls.store[fs]
|
|
||||||
except KeyError:
|
|
||||||
cls.store[fs] = fs
|
|
||||||
map[mapnum] = fs
|
|
||||||
ret[dep] = fs
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def __getstate__(self):
|
|
||||||
ret = {}
|
|
||||||
for key in ["siggen_gendeps", "siggen_taskdeps", "siggen_varvals"]:
|
|
||||||
ret[key] = self._save(self.__dict__[key])
|
|
||||||
ret['pid'] = os.getpid()
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def __setstate__(self, state):
|
|
||||||
pid = state['pid']
|
|
||||||
for key in ["siggen_gendeps", "siggen_taskdeps", "siggen_varvals"]:
|
|
||||||
setattr(self, key, self._restore(state[key], pid))
|
|
||||||
|
|
||||||
|
|
||||||
def virtualfn2realfn(virtualfn):
|
def virtualfn2realfn(virtualfn):
|
||||||
"""
|
"""
|
||||||
Convert a virtual file name to a real one + the associated subclass keyword
|
Convert a virtual file name to a real one + the associated subclass keyword
|
||||||
"""
|
"""
|
||||||
mc = ""
|
mc = ""
|
||||||
if virtualfn.startswith('mc:') and virtualfn.count(':') >= 2:
|
if virtualfn.startswith('mc:'):
|
||||||
(_, mc, virtualfn) = virtualfn.split(':', 2)
|
elems = virtualfn.split(':')
|
||||||
|
mc = elems[1]
|
||||||
|
virtualfn = ":".join(elems[2:])
|
||||||
|
|
||||||
fn = virtualfn
|
fn = virtualfn
|
||||||
cls = ""
|
cls = ""
|
||||||
@@ -367,29 +264,107 @@ def realfn2virtual(realfn, cls, mc):
|
|||||||
|
|
||||||
def variant2virtual(realfn, variant):
|
def variant2virtual(realfn, variant):
|
||||||
"""
|
"""
|
||||||
Convert a real filename + a variant to a virtual filename
|
Convert a real filename + the associated subclass keyword to a virtual filename
|
||||||
"""
|
"""
|
||||||
if variant == "":
|
if variant == "":
|
||||||
return realfn
|
return realfn
|
||||||
if variant.startswith("mc:") and variant.count(':') >= 2:
|
if variant.startswith("mc:"):
|
||||||
elems = variant.split(":")
|
elems = variant.split(":")
|
||||||
if elems[2]:
|
if elems[2]:
|
||||||
return "mc:" + elems[1] + ":virtual:" + ":".join(elems[2:]) + ":" + realfn
|
return "mc:" + elems[1] + ":virtual:" + ":".join(elems[2:]) + ":" + realfn
|
||||||
return "mc:" + elems[1] + ":" + realfn
|
return "mc:" + elems[1] + ":" + realfn
|
||||||
return "virtual:" + variant + ":" + realfn
|
return "virtual:" + variant + ":" + realfn
|
||||||
|
|
||||||
#
|
def parse_recipe(bb_data, bbfile, appends, mc=''):
|
||||||
# Cooker calls cacheValid on its recipe list, then either calls loadCached
|
"""
|
||||||
# from it's main thread or parse from separate processes to generate an up to
|
Parse a recipe
|
||||||
# date cache
|
"""
|
||||||
#
|
|
||||||
class Cache(object):
|
chdir_back = False
|
||||||
|
|
||||||
|
bb_data.setVar("__BBMULTICONFIG", mc)
|
||||||
|
|
||||||
|
# expand tmpdir to include this topdir
|
||||||
|
bb_data.setVar('TMPDIR', bb_data.getVar('TMPDIR') or "")
|
||||||
|
bbfile_loc = os.path.abspath(os.path.dirname(bbfile))
|
||||||
|
oldpath = os.path.abspath(os.getcwd())
|
||||||
|
bb.parse.cached_mtime_noerror(bbfile_loc)
|
||||||
|
|
||||||
|
# The ConfHandler first looks if there is a TOPDIR and if not
|
||||||
|
# then it would call getcwd().
|
||||||
|
# Previously, we chdir()ed to bbfile_loc, called the handler
|
||||||
|
# and finally chdir()ed back, a couple of thousand times. We now
|
||||||
|
# just fill in TOPDIR to point to bbfile_loc if there is no TOPDIR yet.
|
||||||
|
if not bb_data.getVar('TOPDIR', False):
|
||||||
|
chdir_back = True
|
||||||
|
bb_data.setVar('TOPDIR', bbfile_loc)
|
||||||
|
try:
|
||||||
|
if appends:
|
||||||
|
bb_data.setVar('__BBAPPEND', " ".join(appends))
|
||||||
|
bb_data = bb.parse.handle(bbfile, bb_data)
|
||||||
|
if chdir_back:
|
||||||
|
os.chdir(oldpath)
|
||||||
|
return bb_data
|
||||||
|
except:
|
||||||
|
if chdir_back:
|
||||||
|
os.chdir(oldpath)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class NoCache(object):
|
||||||
|
|
||||||
|
def __init__(self, databuilder):
|
||||||
|
self.databuilder = databuilder
|
||||||
|
self.data = databuilder.data
|
||||||
|
|
||||||
|
def loadDataFull(self, virtualfn, appends):
|
||||||
|
"""
|
||||||
|
Return a complete set of data for fn.
|
||||||
|
To do this, we need to parse the file.
|
||||||
|
"""
|
||||||
|
logger.debug(1, "Parsing %s (full)" % virtualfn)
|
||||||
|
(fn, virtual, mc) = virtualfn2realfn(virtualfn)
|
||||||
|
bb_data = self.load_bbfile(virtualfn, appends, virtonly=True)
|
||||||
|
return bb_data[virtual]
|
||||||
|
|
||||||
|
def load_bbfile(self, bbfile, appends, virtonly = False, mc=None):
|
||||||
|
"""
|
||||||
|
Load and parse one .bb build file
|
||||||
|
Return the data and whether parsing resulted in the file being skipped
|
||||||
|
"""
|
||||||
|
|
||||||
|
if virtonly:
|
||||||
|
(bbfile, virtual, mc) = virtualfn2realfn(bbfile)
|
||||||
|
bb_data = self.databuilder.mcdata[mc].createCopy()
|
||||||
|
bb_data.setVar("__ONLYFINALISE", virtual or "default")
|
||||||
|
datastores = parse_recipe(bb_data, bbfile, appends, mc)
|
||||||
|
return datastores
|
||||||
|
|
||||||
|
if mc is not None:
|
||||||
|
bb_data = self.databuilder.mcdata[mc].createCopy()
|
||||||
|
return parse_recipe(bb_data, bbfile, appends, mc)
|
||||||
|
|
||||||
|
bb_data = self.data.createCopy()
|
||||||
|
datastores = parse_recipe(bb_data, bbfile, appends)
|
||||||
|
|
||||||
|
for mc in self.databuilder.mcdata:
|
||||||
|
if not mc:
|
||||||
|
continue
|
||||||
|
bb_data = self.databuilder.mcdata[mc].createCopy()
|
||||||
|
newstores = parse_recipe(bb_data, bbfile, appends, mc)
|
||||||
|
for ns in newstores:
|
||||||
|
datastores["mc:%s:%s" % (mc, ns)] = newstores[ns]
|
||||||
|
|
||||||
|
return datastores
|
||||||
|
|
||||||
|
class Cache(NoCache):
|
||||||
"""
|
"""
|
||||||
BitBake Cache implementation
|
BitBake Cache implementation
|
||||||
"""
|
"""
|
||||||
def __init__(self, databuilder, mc, data_hash, caches_array):
|
def __init__(self, databuilder, mc, data_hash, caches_array):
|
||||||
self.databuilder = databuilder
|
super().__init__(databuilder)
|
||||||
self.data = databuilder.data
|
data = databuilder.data
|
||||||
|
|
||||||
# Pass caches_array information into Cache Constructor
|
# Pass caches_array information into Cache Constructor
|
||||||
# It will be used later for deciding whether we
|
# It will be used later for deciding whether we
|
||||||
@@ -397,7 +372,7 @@ class Cache(object):
|
|||||||
self.mc = mc
|
self.mc = mc
|
||||||
self.logger = PrefixLoggerAdapter("Cache: %s: " % (mc if mc else "default"), logger)
|
self.logger = PrefixLoggerAdapter("Cache: %s: " % (mc if mc else "default"), logger)
|
||||||
self.caches_array = caches_array
|
self.caches_array = caches_array
|
||||||
self.cachedir = self.data.getVar("CACHE")
|
self.cachedir = data.getVar("CACHE")
|
||||||
self.clean = set()
|
self.clean = set()
|
||||||
self.checked = set()
|
self.checked = set()
|
||||||
self.depends_cache = {}
|
self.depends_cache = {}
|
||||||
@@ -407,17 +382,25 @@ class Cache(object):
|
|||||||
self.filelist_regex = re.compile(r'(?:(?<=:True)|(?<=:False))\s+')
|
self.filelist_regex = re.compile(r'(?:(?<=:True)|(?<=:False))\s+')
|
||||||
|
|
||||||
if self.cachedir in [None, '']:
|
if self.cachedir in [None, '']:
|
||||||
bb.fatal("Please ensure CACHE is set to the cache directory for BitBake to use")
|
self.has_cache = False
|
||||||
|
self.logger.info("Not using a cache. "
|
||||||
|
"Set CACHE = <directory> to enable.")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.has_cache = True
|
||||||
|
|
||||||
def getCacheFile(self, cachefile):
|
def getCacheFile(self, cachefile):
|
||||||
return getCacheFile(self.cachedir, cachefile, self.mc, self.data_hash)
|
return getCacheFile(self.cachedir, cachefile, self.mc, self.data_hash)
|
||||||
|
|
||||||
def prepare_cache(self, progress):
|
def prepare_cache(self, progress):
|
||||||
|
if not self.has_cache:
|
||||||
|
return 0
|
||||||
|
|
||||||
loaded = 0
|
loaded = 0
|
||||||
|
|
||||||
self.cachefile = self.getCacheFile("bb_cache.dat")
|
self.cachefile = self.getCacheFile("bb_cache.dat")
|
||||||
|
|
||||||
self.logger.debug("Cache dir: %s", self.cachedir)
|
self.logger.debug(1, "Cache dir: %s", self.cachedir)
|
||||||
bb.utils.mkdirhier(self.cachedir)
|
bb.utils.mkdirhier(self.cachedir)
|
||||||
|
|
||||||
cache_ok = True
|
cache_ok = True
|
||||||
@@ -425,7 +408,7 @@ class Cache(object):
|
|||||||
for cache_class in self.caches_array:
|
for cache_class in self.caches_array:
|
||||||
cachefile = self.getCacheFile(cache_class.cachefile)
|
cachefile = self.getCacheFile(cache_class.cachefile)
|
||||||
cache_exists = os.path.exists(cachefile)
|
cache_exists = os.path.exists(cachefile)
|
||||||
self.logger.debug2("Checking if %s exists: %r", cachefile, cache_exists)
|
self.logger.debug(2, "Checking if %s exists: %r", cachefile, cache_exists)
|
||||||
cache_ok = cache_ok and cache_exists
|
cache_ok = cache_ok and cache_exists
|
||||||
cache_class.init_cacheData(self)
|
cache_class.init_cacheData(self)
|
||||||
if cache_ok:
|
if cache_ok:
|
||||||
@@ -433,7 +416,7 @@ class Cache(object):
|
|||||||
elif os.path.isfile(self.cachefile):
|
elif os.path.isfile(self.cachefile):
|
||||||
self.logger.info("Out of date cache found, rebuilding...")
|
self.logger.info("Out of date cache found, rebuilding...")
|
||||||
else:
|
else:
|
||||||
self.logger.debug("Cache file %s not found, building..." % self.cachefile)
|
self.logger.debug(1, "Cache file %s not found, building..." % self.cachefile)
|
||||||
|
|
||||||
# We don't use the symlink, its just for debugging convinience
|
# We don't use the symlink, its just for debugging convinience
|
||||||
if self.mc:
|
if self.mc:
|
||||||
@@ -451,6 +434,9 @@ class Cache(object):
|
|||||||
return loaded
|
return loaded
|
||||||
|
|
||||||
def cachesize(self):
|
def cachesize(self):
|
||||||
|
if not self.has_cache:
|
||||||
|
return 0
|
||||||
|
|
||||||
cachesize = 0
|
cachesize = 0
|
||||||
for cache_class in self.caches_array:
|
for cache_class in self.caches_array:
|
||||||
cachefile = self.getCacheFile(cache_class.cachefile)
|
cachefile = self.getCacheFile(cache_class.cachefile)
|
||||||
@@ -463,11 +449,13 @@ class Cache(object):
|
|||||||
return cachesize
|
return cachesize
|
||||||
|
|
||||||
def load_cachefile(self, progress):
|
def load_cachefile(self, progress):
|
||||||
|
cachesize = self.cachesize()
|
||||||
previous_progress = 0
|
previous_progress = 0
|
||||||
|
previous_percent = 0
|
||||||
|
|
||||||
for cache_class in self.caches_array:
|
for cache_class in self.caches_array:
|
||||||
cachefile = self.getCacheFile(cache_class.cachefile)
|
cachefile = self.getCacheFile(cache_class.cachefile)
|
||||||
self.logger.debug('Loading cache file: %s' % cachefile)
|
self.logger.debug(1, 'Loading cache file: %s' % cachefile)
|
||||||
with open(cachefile, "rb") as cachefile:
|
with open(cachefile, "rb") as cachefile:
|
||||||
pickled = pickle.Unpickler(cachefile)
|
pickled = pickle.Unpickler(cachefile)
|
||||||
# Check cache version information
|
# Check cache version information
|
||||||
@@ -512,11 +500,11 @@ class Cache(object):
|
|||||||
|
|
||||||
return len(self.depends_cache)
|
return len(self.depends_cache)
|
||||||
|
|
||||||
def parse(self, filename, appends, layername):
|
def parse(self, filename, appends):
|
||||||
"""Parse the specified filename, returning the recipe information"""
|
"""Parse the specified filename, returning the recipe information"""
|
||||||
self.logger.debug("Parsing %s", filename)
|
self.logger.debug(1, "Parsing %s", filename)
|
||||||
infos = []
|
infos = []
|
||||||
datastores = self.databuilder.parseRecipeVariants(filename, appends, mc=self.mc, layername=layername)
|
datastores = self.load_bbfile(filename, appends, mc=self.mc)
|
||||||
depends = []
|
depends = []
|
||||||
variants = []
|
variants = []
|
||||||
# Process the "real" fn last so we can store variants list
|
# Process the "real" fn last so we can store variants list
|
||||||
@@ -538,19 +526,43 @@ class Cache(object):
|
|||||||
|
|
||||||
return infos
|
return infos
|
||||||
|
|
||||||
def loadCached(self, filename, appends):
|
def load(self, filename, appends):
|
||||||
"""Obtain the recipe information for the specified filename,
|
"""Obtain the recipe information for the specified filename,
|
||||||
using cached values.
|
using cached values if available, otherwise parsing.
|
||||||
"""
|
|
||||||
|
|
||||||
infos = []
|
Note that if it does parse to obtain the info, it will not
|
||||||
# info_array item is a list of [CoreRecipeInfo, XXXRecipeInfo]
|
automatically add the information to the cache or to your
|
||||||
info_array = self.depends_cache[filename]
|
CacheData. Use the add or add_info method to do so after
|
||||||
for variant in info_array[0].variants:
|
running this, or use loadData instead."""
|
||||||
virtualfn = variant2virtual(filename, variant)
|
cached = self.cacheValid(filename, appends)
|
||||||
infos.append((virtualfn, self.depends_cache[virtualfn]))
|
if cached:
|
||||||
|
infos = []
|
||||||
|
# info_array item is a list of [CoreRecipeInfo, XXXRecipeInfo]
|
||||||
|
info_array = self.depends_cache[filename]
|
||||||
|
for variant in info_array[0].variants:
|
||||||
|
virtualfn = variant2virtual(filename, variant)
|
||||||
|
infos.append((virtualfn, self.depends_cache[virtualfn]))
|
||||||
|
else:
|
||||||
|
return self.parse(filename, appends, configdata, self.caches_array)
|
||||||
|
|
||||||
return infos
|
return cached, infos
|
||||||
|
|
||||||
|
def loadData(self, fn, appends, cacheData):
|
||||||
|
"""Load the recipe info for the specified filename,
|
||||||
|
parsing and adding to the cache if necessary, and adding
|
||||||
|
the recipe information to the supplied CacheData instance."""
|
||||||
|
skipped, virtuals = 0, 0
|
||||||
|
|
||||||
|
cached, infos = self.load(fn, appends)
|
||||||
|
for virtualfn, info_array in infos:
|
||||||
|
if info_array[0].skipped:
|
||||||
|
self.logger.debug(1, "Skipping %s: %s", virtualfn, info_array[0].skipreason)
|
||||||
|
skipped += 1
|
||||||
|
else:
|
||||||
|
self.add_info(virtualfn, info_array, cacheData, not cached)
|
||||||
|
virtuals += 1
|
||||||
|
|
||||||
|
return cached, skipped, virtuals
|
||||||
|
|
||||||
def cacheValid(self, fn, appends):
|
def cacheValid(self, fn, appends):
|
||||||
"""
|
"""
|
||||||
@@ -559,6 +571,10 @@ class Cache(object):
|
|||||||
"""
|
"""
|
||||||
if fn not in self.checked:
|
if fn not in self.checked:
|
||||||
self.cacheValidUpdate(fn, appends)
|
self.cacheValidUpdate(fn, appends)
|
||||||
|
|
||||||
|
# Is cache enabled?
|
||||||
|
if not self.has_cache:
|
||||||
|
return False
|
||||||
if fn in self.clean:
|
if fn in self.clean:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -568,25 +584,29 @@ class Cache(object):
|
|||||||
Is the cache valid for fn?
|
Is the cache valid for fn?
|
||||||
Make thorough (slower) checks including timestamps.
|
Make thorough (slower) checks including timestamps.
|
||||||
"""
|
"""
|
||||||
|
# Is cache enabled?
|
||||||
|
if not self.has_cache:
|
||||||
|
return False
|
||||||
|
|
||||||
self.checked.add(fn)
|
self.checked.add(fn)
|
||||||
|
|
||||||
# File isn't in depends_cache
|
# File isn't in depends_cache
|
||||||
if not fn in self.depends_cache:
|
if not fn in self.depends_cache:
|
||||||
self.logger.debug2("%s is not cached", fn)
|
self.logger.debug(2, "%s is not cached", fn)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
mtime = bb.parse.cached_mtime_noerror(fn)
|
mtime = bb.parse.cached_mtime_noerror(fn)
|
||||||
|
|
||||||
# Check file still exists
|
# Check file still exists
|
||||||
if mtime == 0:
|
if mtime == 0:
|
||||||
self.logger.debug2("%s no longer exists", fn)
|
self.logger.debug(2, "%s no longer exists", fn)
|
||||||
self.remove(fn)
|
self.remove(fn)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
info_array = self.depends_cache[fn]
|
info_array = self.depends_cache[fn]
|
||||||
# Check the file's timestamp
|
# Check the file's timestamp
|
||||||
if mtime != info_array[0].timestamp:
|
if mtime != info_array[0].timestamp:
|
||||||
self.logger.debug2("%s changed", fn)
|
self.logger.debug(2, "%s changed", fn)
|
||||||
self.remove(fn)
|
self.remove(fn)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -597,13 +617,13 @@ class Cache(object):
|
|||||||
fmtime = bb.parse.cached_mtime_noerror(f)
|
fmtime = bb.parse.cached_mtime_noerror(f)
|
||||||
# Check if file still exists
|
# Check if file still exists
|
||||||
if old_mtime != 0 and fmtime == 0:
|
if old_mtime != 0 and fmtime == 0:
|
||||||
self.logger.debug2("%s's dependency %s was removed",
|
self.logger.debug(2, "%s's dependency %s was removed",
|
||||||
fn, f)
|
fn, f)
|
||||||
self.remove(fn)
|
self.remove(fn)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if (fmtime != old_mtime):
|
if (fmtime != old_mtime):
|
||||||
self.logger.debug2("%s's dependency %s changed",
|
self.logger.debug(2, "%s's dependency %s changed",
|
||||||
fn, f)
|
fn, f)
|
||||||
self.remove(fn)
|
self.remove(fn)
|
||||||
return False
|
return False
|
||||||
@@ -618,16 +638,16 @@ class Cache(object):
|
|||||||
for f in flist:
|
for f in flist:
|
||||||
if not f:
|
if not f:
|
||||||
continue
|
continue
|
||||||
f, exist = f.rsplit(":", 1)
|
f, exist = f.split(":")
|
||||||
if (exist == "True" and not os.path.exists(f)) or (exist == "False" and os.path.exists(f)):
|
if (exist == "True" and not os.path.exists(f)) or (exist == "False" and os.path.exists(f)):
|
||||||
self.logger.debug2("%s's file checksum list file %s changed",
|
self.logger.debug(2, "%s's file checksum list file %s changed",
|
||||||
fn, f)
|
fn, f)
|
||||||
self.remove(fn)
|
self.remove(fn)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if tuple(appends) != tuple(info_array[0].appends):
|
if tuple(appends) != tuple(info_array[0].appends):
|
||||||
self.logger.debug2("appends for %s changed", fn)
|
self.logger.debug(2, "appends for %s changed", fn)
|
||||||
self.logger.debug2("%s to %s" % (str(appends), str(info_array[0].appends)))
|
self.logger.debug(2, "%s to %s" % (str(appends), str(info_array[0].appends)))
|
||||||
self.remove(fn)
|
self.remove(fn)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -636,10 +656,10 @@ class Cache(object):
|
|||||||
virtualfn = variant2virtual(fn, cls)
|
virtualfn = variant2virtual(fn, cls)
|
||||||
self.clean.add(virtualfn)
|
self.clean.add(virtualfn)
|
||||||
if virtualfn not in self.depends_cache:
|
if virtualfn not in self.depends_cache:
|
||||||
self.logger.debug2("%s is not cached", virtualfn)
|
self.logger.debug(2, "%s is not cached", virtualfn)
|
||||||
invalid = True
|
invalid = True
|
||||||
elif len(self.depends_cache[virtualfn]) != len(self.caches_array):
|
elif len(self.depends_cache[virtualfn]) != len(self.caches_array):
|
||||||
self.logger.debug2("Extra caches missing for %s?" % virtualfn)
|
self.logger.debug(2, "Extra caches missing for %s?" % virtualfn)
|
||||||
invalid = True
|
invalid = True
|
||||||
|
|
||||||
# If any one of the variants is not present, mark as invalid for all
|
# If any one of the variants is not present, mark as invalid for all
|
||||||
@@ -647,10 +667,10 @@ class Cache(object):
|
|||||||
for cls in info_array[0].variants:
|
for cls in info_array[0].variants:
|
||||||
virtualfn = variant2virtual(fn, cls)
|
virtualfn = variant2virtual(fn, cls)
|
||||||
if virtualfn in self.clean:
|
if virtualfn in self.clean:
|
||||||
self.logger.debug2("Removing %s from cache", virtualfn)
|
self.logger.debug(2, "Removing %s from cache", virtualfn)
|
||||||
self.clean.remove(virtualfn)
|
self.clean.remove(virtualfn)
|
||||||
if fn in self.clean:
|
if fn in self.clean:
|
||||||
self.logger.debug2("Marking %s as not clean", fn)
|
self.logger.debug(2, "Marking %s as not clean", fn)
|
||||||
self.clean.remove(fn)
|
self.clean.remove(fn)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -663,10 +683,10 @@ class Cache(object):
|
|||||||
Called from the parser in error cases
|
Called from the parser in error cases
|
||||||
"""
|
"""
|
||||||
if fn in self.depends_cache:
|
if fn in self.depends_cache:
|
||||||
self.logger.debug("Removing %s from cache", fn)
|
self.logger.debug(1, "Removing %s from cache", fn)
|
||||||
del self.depends_cache[fn]
|
del self.depends_cache[fn]
|
||||||
if fn in self.clean:
|
if fn in self.clean:
|
||||||
self.logger.debug("Marking %s as unclean", fn)
|
self.logger.debug(1, "Marking %s as unclean", fn)
|
||||||
self.clean.remove(fn)
|
self.clean.remove(fn)
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
@@ -674,14 +694,18 @@ class Cache(object):
|
|||||||
Save the cache
|
Save the cache
|
||||||
Called from the parser when complete (or exiting)
|
Called from the parser when complete (or exiting)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not self.has_cache:
|
||||||
|
return
|
||||||
|
|
||||||
if self.cacheclean:
|
if self.cacheclean:
|
||||||
self.logger.debug2("Cache is clean, not saving.")
|
self.logger.debug(2, "Cache is clean, not saving.")
|
||||||
return
|
return
|
||||||
|
|
||||||
for cache_class in self.caches_array:
|
for cache_class in self.caches_array:
|
||||||
cache_class_name = cache_class.__name__
|
cache_class_name = cache_class.__name__
|
||||||
cachefile = self.getCacheFile(cache_class.cachefile)
|
cachefile = self.getCacheFile(cache_class.cachefile)
|
||||||
self.logger.debug2("Writing %s", cachefile)
|
self.logger.debug(2, "Writing %s", cachefile)
|
||||||
with open(cachefile, "wb") as f:
|
with open(cachefile, "wb") as f:
|
||||||
p = pickle.Pickler(f, pickle.HIGHEST_PROTOCOL)
|
p = pickle.Pickler(f, pickle.HIGHEST_PROTOCOL)
|
||||||
p.dump(__cache_version__)
|
p.dump(__cache_version__)
|
||||||
@@ -694,7 +718,6 @@ class Cache(object):
|
|||||||
p.dump(info)
|
p.dump(info)
|
||||||
|
|
||||||
del self.depends_cache
|
del self.depends_cache
|
||||||
SiggenRecipeInfo.reset()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mtime(cachefile):
|
def mtime(cachefile):
|
||||||
@@ -717,11 +740,26 @@ class Cache(object):
|
|||||||
if watcher:
|
if watcher:
|
||||||
watcher(info_array[0].file_depends)
|
watcher(info_array[0].file_depends)
|
||||||
|
|
||||||
|
if not self.has_cache:
|
||||||
|
return
|
||||||
|
|
||||||
if (info_array[0].skipped or 'SRCREVINACTION' not in info_array[0].pv) and not info_array[0].nocache:
|
if (info_array[0].skipped or 'SRCREVINACTION' not in info_array[0].pv) and not info_array[0].nocache:
|
||||||
if parsed:
|
if parsed:
|
||||||
self.cacheclean = False
|
self.cacheclean = False
|
||||||
self.depends_cache[filename] = info_array
|
self.depends_cache[filename] = info_array
|
||||||
|
|
||||||
|
def add(self, file_name, data, cacheData, parsed=None):
|
||||||
|
"""
|
||||||
|
Save data we need into the cache
|
||||||
|
"""
|
||||||
|
|
||||||
|
realfn = virtualfn2realfn(file_name)[0]
|
||||||
|
|
||||||
|
info_array = []
|
||||||
|
for cache_class in self.caches_array:
|
||||||
|
info_array.append(cache_class(realfn, data))
|
||||||
|
self.add_info(file_name, info_array, cacheData, parsed)
|
||||||
|
|
||||||
class MulticonfigCache(Mapping):
|
class MulticonfigCache(Mapping):
|
||||||
def __init__(self, databuilder, data_hash, caches_array):
|
def __init__(self, databuilder, data_hash, caches_array):
|
||||||
def progress(p):
|
def progress(p):
|
||||||
@@ -758,7 +796,6 @@ class MulticonfigCache(Mapping):
|
|||||||
loaded = 0
|
loaded = 0
|
||||||
|
|
||||||
for c in self.__caches.values():
|
for c in self.__caches.values():
|
||||||
SiggenRecipeInfo.reset()
|
|
||||||
loaded += c.prepare_cache(progress)
|
loaded += c.prepare_cache(progress)
|
||||||
previous_progress = current_progress
|
previous_progress = current_progress
|
||||||
|
|
||||||
@@ -779,6 +816,10 @@ class MulticonfigCache(Mapping):
|
|||||||
for k in self.__caches:
|
for k in self.__caches:
|
||||||
yield k
|
yield k
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self.__caches[key]
|
||||||
|
|
||||||
|
|
||||||
def init(cooker):
|
def init(cooker):
|
||||||
"""
|
"""
|
||||||
The Objective: Cache the minimum amount of data possible yet get to the
|
The Objective: Cache the minimum amount of data possible yet get to the
|
||||||
@@ -836,14 +877,15 @@ class MultiProcessCache(object):
|
|||||||
self.cachedata = self.create_cachedata()
|
self.cachedata = self.create_cachedata()
|
||||||
self.cachedata_extras = self.create_cachedata()
|
self.cachedata_extras = self.create_cachedata()
|
||||||
|
|
||||||
def init_cache(self, cachedir, cache_file_name=None):
|
def init_cache(self, d, cache_file_name=None):
|
||||||
if not cachedir:
|
cachedir = (d.getVar("PERSISTENT_DIR") or
|
||||||
|
d.getVar("CACHE"))
|
||||||
|
if cachedir in [None, '']:
|
||||||
return
|
return
|
||||||
|
|
||||||
bb.utils.mkdirhier(cachedir)
|
bb.utils.mkdirhier(cachedir)
|
||||||
self.cachefile = os.path.join(cachedir,
|
self.cachefile = os.path.join(cachedir,
|
||||||
cache_file_name or self.__class__.cache_file_name)
|
cache_file_name or self.__class__.cache_file_name)
|
||||||
logger.debug("Using cache in '%s'", self.cachefile)
|
logger.debug(1, "Using cache in '%s'", self.cachefile)
|
||||||
|
|
||||||
glf = bb.utils.lockfile(self.cachefile + ".lock")
|
glf = bb.utils.lockfile(self.cachefile + ".lock")
|
||||||
|
|
||||||
@@ -870,10 +912,6 @@ class MultiProcessCache(object):
|
|||||||
if not self.cachefile:
|
if not self.cachefile:
|
||||||
return
|
return
|
||||||
|
|
||||||
have_data = any(self.cachedata_extras)
|
|
||||||
if not have_data:
|
|
||||||
return
|
|
||||||
|
|
||||||
glf = bb.utils.lockfile(self.cachefile + ".lock", shared=True)
|
glf = bb.utils.lockfile(self.cachefile + ".lock", shared=True)
|
||||||
|
|
||||||
i = os.getpid()
|
i = os.getpid()
|
||||||
@@ -908,8 +946,6 @@ class MultiProcessCache(object):
|
|||||||
|
|
||||||
data = self.cachedata
|
data = self.cachedata
|
||||||
|
|
||||||
have_data = False
|
|
||||||
|
|
||||||
for f in [y for y in os.listdir(os.path.dirname(self.cachefile)) if y.startswith(os.path.basename(self.cachefile) + '-')]:
|
for f in [y for y in os.listdir(os.path.dirname(self.cachefile)) if y.startswith(os.path.basename(self.cachefile) + '-')]:
|
||||||
f = os.path.join(os.path.dirname(self.cachefile), f)
|
f = os.path.join(os.path.dirname(self.cachefile), f)
|
||||||
try:
|
try:
|
||||||
@@ -924,14 +960,12 @@ class MultiProcessCache(object):
|
|||||||
os.unlink(f)
|
os.unlink(f)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
have_data = True
|
|
||||||
self.merge_data(extradata, data)
|
self.merge_data(extradata, data)
|
||||||
os.unlink(f)
|
os.unlink(f)
|
||||||
|
|
||||||
if have_data:
|
with open(self.cachefile, "wb") as f:
|
||||||
with open(self.cachefile, "wb") as f:
|
p = pickle.Pickler(f, -1)
|
||||||
p = pickle.Pickler(f, -1)
|
p.dump([data, self.__class__.CACHE_VERSION])
|
||||||
p.dump([data, self.__class__.CACHE_VERSION])
|
|
||||||
|
|
||||||
bb.utils.unlockfile(glf)
|
bb.utils.unlockfile(glf)
|
||||||
|
|
||||||
@@ -957,7 +991,7 @@ class SimpleCache(object):
|
|||||||
bb.utils.mkdirhier(cachedir)
|
bb.utils.mkdirhier(cachedir)
|
||||||
self.cachefile = os.path.join(cachedir,
|
self.cachefile = os.path.join(cachedir,
|
||||||
cache_file_name or self.__class__.cache_file_name)
|
cache_file_name or self.__class__.cache_file_name)
|
||||||
logger.debug("Using cache in '%s'", self.cachefile)
|
logger.debug(1, "Using cache in '%s'", self.cachefile)
|
||||||
|
|
||||||
glf = bb.utils.lockfile(self.cachefile + ".lock")
|
glf = bb.utils.lockfile(self.cachefile + ".lock")
|
||||||
|
|
||||||
@@ -987,11 +1021,3 @@ class SimpleCache(object):
|
|||||||
p.dump([data, self.cacheversion])
|
p.dump([data, self.cacheversion])
|
||||||
|
|
||||||
bb.utils.unlockfile(glf)
|
bb.utils.unlockfile(glf)
|
||||||
|
|
||||||
def copyfile(self, target):
|
|
||||||
if not self.cachefile:
|
|
||||||
return
|
|
||||||
|
|
||||||
glf = bb.utils.lockfile(self.cachefile + ".lock")
|
|
||||||
shutil.copy(self.cachefile, target)
|
|
||||||
bb.utils.unlockfile(glf)
|
|
||||||
|
|||||||
@@ -11,13 +11,10 @@ import os
|
|||||||
import stat
|
import stat
|
||||||
import bb.utils
|
import bb.utils
|
||||||
import logging
|
import logging
|
||||||
import re
|
|
||||||
from bb.cache import MultiProcessCache
|
from bb.cache import MultiProcessCache
|
||||||
|
|
||||||
logger = logging.getLogger("BitBake.Cache")
|
logger = logging.getLogger("BitBake.Cache")
|
||||||
|
|
||||||
filelist_regex = re.compile(r'(?:(?<=:True)|(?<=:False))\s+')
|
|
||||||
|
|
||||||
# mtime cache (non-persistent)
|
# mtime cache (non-persistent)
|
||||||
# based upon the assumption that files do not change during bitbake run
|
# based upon the assumption that files do not change during bitbake run
|
||||||
class FileMtimeCache(object):
|
class FileMtimeCache(object):
|
||||||
@@ -53,7 +50,6 @@ class FileChecksumCache(MultiProcessCache):
|
|||||||
MultiProcessCache.__init__(self)
|
MultiProcessCache.__init__(self)
|
||||||
|
|
||||||
def get_checksum(self, f):
|
def get_checksum(self, f):
|
||||||
f = os.path.normpath(f)
|
|
||||||
entry = self.cachedata[0].get(f)
|
entry = self.cachedata[0].get(f)
|
||||||
cmtime = self.mtime_cache.cached_mtime(f)
|
cmtime = self.mtime_cache.cached_mtime(f)
|
||||||
if entry:
|
if entry:
|
||||||
@@ -88,36 +84,22 @@ class FileChecksumCache(MultiProcessCache):
|
|||||||
return None
|
return None
|
||||||
return checksum
|
return checksum
|
||||||
|
|
||||||
#
|
|
||||||
# Changing the format of file-checksums is problematic as both OE and Bitbake have
|
|
||||||
# knowledge of them. We need to encode a new piece of data, the portion of the path
|
|
||||||
# we care about from a checksum perspective. This means that files that change subdirectory
|
|
||||||
# are tracked by the task hashes. To do this, we do something horrible and put a "/./" into
|
|
||||||
# the path. The filesystem handles it but it gives us a marker to know which subsection
|
|
||||||
# of the path to cache.
|
|
||||||
#
|
|
||||||
def checksum_dir(pth):
|
def checksum_dir(pth):
|
||||||
# Handle directories recursively
|
# Handle directories recursively
|
||||||
if pth == "/":
|
if pth == "/":
|
||||||
bb.fatal("Refusing to checksum /")
|
bb.fatal("Refusing to checksum /")
|
||||||
pth = pth.rstrip("/")
|
|
||||||
dirchecksums = []
|
dirchecksums = []
|
||||||
for root, dirs, files in os.walk(pth, topdown=True):
|
for root, dirs, files in os.walk(pth, topdown=True):
|
||||||
[dirs.remove(d) for d in list(dirs) if d in localdirsexclude]
|
[dirs.remove(d) for d in list(dirs) if d in localdirsexclude]
|
||||||
for name in files:
|
for name in files:
|
||||||
fullpth = os.path.join(root, name).replace(pth, os.path.join(pth, "."))
|
fullpth = os.path.join(root, name)
|
||||||
checksum = checksum_file(fullpth)
|
checksum = checksum_file(fullpth)
|
||||||
if checksum:
|
if checksum:
|
||||||
dirchecksums.append((fullpth, checksum))
|
dirchecksums.append((fullpth, checksum))
|
||||||
return dirchecksums
|
return dirchecksums
|
||||||
|
|
||||||
checksums = []
|
checksums = []
|
||||||
for pth in filelist_regex.split(filelist):
|
for pth in filelist.split():
|
||||||
if not pth:
|
|
||||||
continue
|
|
||||||
pth = pth.strip()
|
|
||||||
if not pth:
|
|
||||||
continue
|
|
||||||
exist = pth.split(":")[1]
|
exist = pth.split(":")[1]
|
||||||
if exist == "False":
|
if exist == "False":
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#
|
#
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
#
|
#
|
||||||
|
|
||||||
@@ -27,7 +25,6 @@ import ast
|
|||||||
import sys
|
import sys
|
||||||
import codegen
|
import codegen
|
||||||
import logging
|
import logging
|
||||||
import inspect
|
|
||||||
import bb.pysh as pysh
|
import bb.pysh as pysh
|
||||||
import bb.utils, bb.data
|
import bb.utils, bb.data
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -59,45 +56,10 @@ def check_indent(codestr):
|
|||||||
|
|
||||||
return codestr
|
return codestr
|
||||||
|
|
||||||
modulecode_deps = {}
|
|
||||||
|
|
||||||
def add_module_functions(fn, functions, namespace):
|
|
||||||
import os
|
|
||||||
fstat = os.stat(fn)
|
|
||||||
fixedhash = fn + ":" + str(fstat.st_size) + ":" + str(fstat.st_mtime)
|
|
||||||
for f in functions:
|
|
||||||
name = "%s.%s" % (namespace, f)
|
|
||||||
parser = PythonParser(name, logger)
|
|
||||||
try:
|
|
||||||
parser.parse_python(None, filename=fn, lineno=1, fixedhash=fixedhash+f)
|
|
||||||
#bb.warn("Cached %s" % f)
|
|
||||||
except KeyError:
|
|
||||||
targetfn = inspect.getsourcefile(functions[f])
|
|
||||||
if fn != targetfn:
|
|
||||||
# Skip references to other modules outside this file
|
|
||||||
#bb.warn("Skipping %s" % name)
|
|
||||||
continue
|
|
||||||
lines, lineno = inspect.getsourcelines(functions[f])
|
|
||||||
src = "".join(lines)
|
|
||||||
parser.parse_python(src, filename=fn, lineno=lineno, fixedhash=fixedhash+f)
|
|
||||||
#bb.warn("Not cached %s" % f)
|
|
||||||
execs = parser.execs.copy()
|
|
||||||
# Expand internal module exec references
|
|
||||||
for e in parser.execs:
|
|
||||||
if e in functions:
|
|
||||||
execs.remove(e)
|
|
||||||
execs.add(namespace + "." + e)
|
|
||||||
modulecode_deps[name] = [parser.references.copy(), execs, parser.var_execs.copy(), parser.contains.copy(), parser.extra]
|
|
||||||
#bb.warn("%s: %s\nRefs:%s Execs: %s %s %s" % (name, fn, parser.references, parser.execs, parser.var_execs, parser.contains))
|
|
||||||
|
|
||||||
def update_module_dependencies(d):
|
|
||||||
for mod in modulecode_deps:
|
|
||||||
excludes = set((d.getVarFlag(mod, "vardepsexclude") or "").split())
|
|
||||||
if excludes:
|
|
||||||
modulecode_deps[mod] = [modulecode_deps[mod][0] - excludes, modulecode_deps[mod][1] - excludes, modulecode_deps[mod][2] - excludes, modulecode_deps[mod][3], modulecode_deps[mod][4]]
|
|
||||||
|
|
||||||
# A custom getstate/setstate using tuples is actually worth 15% cachesize by
|
# A custom getstate/setstate using tuples is actually worth 15% cachesize by
|
||||||
# avoiding duplication of the attribute names!
|
# avoiding duplication of the attribute names!
|
||||||
|
|
||||||
|
|
||||||
class SetCache(object):
|
class SetCache(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.setcache = {}
|
self.setcache = {}
|
||||||
@@ -117,22 +79,21 @@ class SetCache(object):
|
|||||||
codecache = SetCache()
|
codecache = SetCache()
|
||||||
|
|
||||||
class pythonCacheLine(object):
|
class pythonCacheLine(object):
|
||||||
def __init__(self, refs, execs, contains, extra):
|
def __init__(self, refs, execs, contains):
|
||||||
self.refs = codecache.internSet(refs)
|
self.refs = codecache.internSet(refs)
|
||||||
self.execs = codecache.internSet(execs)
|
self.execs = codecache.internSet(execs)
|
||||||
self.contains = {}
|
self.contains = {}
|
||||||
for c in contains:
|
for c in contains:
|
||||||
self.contains[c] = codecache.internSet(contains[c])
|
self.contains[c] = codecache.internSet(contains[c])
|
||||||
self.extra = extra
|
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
return (self.refs, self.execs, self.contains, self.extra)
|
return (self.refs, self.execs, self.contains)
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
(refs, execs, contains, extra) = state
|
(refs, execs, contains) = state
|
||||||
self.__init__(refs, execs, contains, extra)
|
self.__init__(refs, execs, contains)
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
l = (hash(self.refs), hash(self.execs), hash(self.extra))
|
l = (hash(self.refs), hash(self.execs))
|
||||||
for c in sorted(self.contains.keys()):
|
for c in sorted(self.contains.keys()):
|
||||||
l = l + (c, hash(self.contains[c]))
|
l = l + (c, hash(self.contains[c]))
|
||||||
return hash(l)
|
return hash(l)
|
||||||
@@ -161,7 +122,7 @@ class CodeParserCache(MultiProcessCache):
|
|||||||
# so that an existing cache gets invalidated. Additionally you'll need
|
# so that an existing cache gets invalidated. Additionally you'll need
|
||||||
# to increment __cache_version__ in cache.py in order to ensure that old
|
# to increment __cache_version__ in cache.py in order to ensure that old
|
||||||
# recipe caches don't trigger "Taskhash mismatch" errors.
|
# recipe caches don't trigger "Taskhash mismatch" errors.
|
||||||
CACHE_VERSION = 12
|
CACHE_VERSION = 11
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
MultiProcessCache.__init__(self)
|
MultiProcessCache.__init__(self)
|
||||||
@@ -175,8 +136,8 @@ class CodeParserCache(MultiProcessCache):
|
|||||||
self.pythoncachelines = {}
|
self.pythoncachelines = {}
|
||||||
self.shellcachelines = {}
|
self.shellcachelines = {}
|
||||||
|
|
||||||
def newPythonCacheLine(self, refs, execs, contains, extra):
|
def newPythonCacheLine(self, refs, execs, contains):
|
||||||
cacheline = pythonCacheLine(refs, execs, contains, extra)
|
cacheline = pythonCacheLine(refs, execs, contains)
|
||||||
h = hash(cacheline)
|
h = hash(cacheline)
|
||||||
if h in self.pythoncachelines:
|
if h in self.pythoncachelines:
|
||||||
return self.pythoncachelines[h]
|
return self.pythoncachelines[h]
|
||||||
@@ -191,12 +152,12 @@ class CodeParserCache(MultiProcessCache):
|
|||||||
self.shellcachelines[h] = cacheline
|
self.shellcachelines[h] = cacheline
|
||||||
return cacheline
|
return cacheline
|
||||||
|
|
||||||
def init_cache(self, cachedir):
|
def init_cache(self, d):
|
||||||
# Check if we already have the caches
|
# Check if we already have the caches
|
||||||
if self.pythoncache:
|
if self.pythoncache:
|
||||||
return
|
return
|
||||||
|
|
||||||
MultiProcessCache.init_cache(self, cachedir)
|
MultiProcessCache.init_cache(self, d)
|
||||||
|
|
||||||
# cachedata gets re-assigned in the parent
|
# cachedata gets re-assigned in the parent
|
||||||
self.pythoncache = self.cachedata[0]
|
self.pythoncache = self.cachedata[0]
|
||||||
@@ -208,8 +169,8 @@ class CodeParserCache(MultiProcessCache):
|
|||||||
|
|
||||||
codeparsercache = CodeParserCache()
|
codeparsercache = CodeParserCache()
|
||||||
|
|
||||||
def parser_cache_init(cachedir):
|
def parser_cache_init(d):
|
||||||
codeparsercache.init_cache(cachedir)
|
codeparsercache.init_cache(d)
|
||||||
|
|
||||||
def parser_cache_save():
|
def parser_cache_save():
|
||||||
codeparsercache.save_extras()
|
codeparsercache.save_extras()
|
||||||
@@ -234,10 +195,6 @@ class BufferedLogger(Logger):
|
|||||||
self.target.handle(record)
|
self.target.handle(record)
|
||||||
self.buffer = []
|
self.buffer = []
|
||||||
|
|
||||||
class DummyLogger():
|
|
||||||
def flush(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
class PythonParser():
|
class PythonParser():
|
||||||
getvars = (".getVar", ".appendVar", ".prependVar", "oe.utils.conditional")
|
getvars = (".getVar", ".appendVar", ".prependVar", "oe.utils.conditional")
|
||||||
getvarflags = (".getVarFlag", ".appendVarFlag", ".prependVarFlag")
|
getvarflags = (".getVarFlag", ".appendVarFlag", ".prependVarFlag")
|
||||||
@@ -255,26 +212,26 @@ class PythonParser():
|
|||||||
funcstr = codegen.to_source(func)
|
funcstr = codegen.to_source(func)
|
||||||
argstr = codegen.to_source(arg)
|
argstr = codegen.to_source(arg)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
self.log.debug2('Failed to convert function and argument to source form')
|
self.log.debug(2, 'Failed to convert function and argument to source form')
|
||||||
else:
|
else:
|
||||||
self.log.debug(self.unhandled_message % (funcstr, argstr))
|
self.log.debug(1, self.unhandled_message % (funcstr, argstr))
|
||||||
|
|
||||||
def visit_Call(self, node):
|
def visit_Call(self, node):
|
||||||
name = self.called_node_name(node.func)
|
name = self.called_node_name(node.func)
|
||||||
if name and (name.endswith(self.getvars) or name.endswith(self.getvarflags) or name in self.containsfuncs or name in self.containsanyfuncs):
|
if name and (name.endswith(self.getvars) or name.endswith(self.getvarflags) or name in self.containsfuncs or name in self.containsanyfuncs):
|
||||||
if isinstance(node.args[0], ast.Constant) and isinstance(node.args[0].value, str):
|
if isinstance(node.args[0], ast.Str):
|
||||||
varname = node.args[0].value
|
varname = node.args[0].s
|
||||||
if name in self.containsfuncs and isinstance(node.args[1], ast.Constant):
|
if name in self.containsfuncs and isinstance(node.args[1], ast.Str):
|
||||||
if varname not in self.contains:
|
if varname not in self.contains:
|
||||||
self.contains[varname] = set()
|
self.contains[varname] = set()
|
||||||
self.contains[varname].add(node.args[1].value)
|
self.contains[varname].add(node.args[1].s)
|
||||||
elif name in self.containsanyfuncs and isinstance(node.args[1], ast.Constant):
|
elif name in self.containsanyfuncs and isinstance(node.args[1], ast.Str):
|
||||||
if varname not in self.contains:
|
if varname not in self.contains:
|
||||||
self.contains[varname] = set()
|
self.contains[varname] = set()
|
||||||
self.contains[varname].update(node.args[1].value.split())
|
self.contains[varname].update(node.args[1].s.split())
|
||||||
elif name.endswith(self.getvarflags):
|
elif name.endswith(self.getvarflags):
|
||||||
if isinstance(node.args[1], ast.Constant):
|
if isinstance(node.args[1], ast.Str):
|
||||||
self.references.add('%s[%s]' % (varname, node.args[1].value))
|
self.references.add('%s[%s]' % (varname, node.args[1].s))
|
||||||
else:
|
else:
|
||||||
self.warn(node.func, node.args[1])
|
self.warn(node.func, node.args[1])
|
||||||
else:
|
else:
|
||||||
@@ -282,8 +239,8 @@ class PythonParser():
|
|||||||
else:
|
else:
|
||||||
self.warn(node.func, node.args[0])
|
self.warn(node.func, node.args[0])
|
||||||
elif name and name.endswith(".expand"):
|
elif name and name.endswith(".expand"):
|
||||||
if isinstance(node.args[0], ast.Constant):
|
if isinstance(node.args[0], ast.Str):
|
||||||
value = node.args[0].value
|
value = node.args[0].s
|
||||||
d = bb.data.init()
|
d = bb.data.init()
|
||||||
parser = d.expandWithRefs(value, self.name)
|
parser = d.expandWithRefs(value, self.name)
|
||||||
self.references |= parser.references
|
self.references |= parser.references
|
||||||
@@ -293,8 +250,8 @@ class PythonParser():
|
|||||||
self.contains[varname] = set()
|
self.contains[varname] = set()
|
||||||
self.contains[varname] |= parser.contains[varname]
|
self.contains[varname] |= parser.contains[varname]
|
||||||
elif name in self.execfuncs:
|
elif name in self.execfuncs:
|
||||||
if isinstance(node.args[0], ast.Constant):
|
if isinstance(node.args[0], ast.Str):
|
||||||
self.var_execs.add(node.args[0].value)
|
self.var_execs.add(node.args[0].s)
|
||||||
else:
|
else:
|
||||||
self.warn(node.func, node.args[0])
|
self.warn(node.func, node.args[0])
|
||||||
elif name and isinstance(node.func, (ast.Name, ast.Attribute)):
|
elif name and isinstance(node.func, (ast.Name, ast.Attribute)):
|
||||||
@@ -319,24 +276,16 @@ class PythonParser():
|
|||||||
self.contains = {}
|
self.contains = {}
|
||||||
self.execs = set()
|
self.execs = set()
|
||||||
self.references = set()
|
self.references = set()
|
||||||
self._log = log
|
self.log = BufferedLogger('BitBake.Data.PythonParser', logging.DEBUG, log)
|
||||||
# Defer init as expensive
|
|
||||||
self.log = DummyLogger()
|
|
||||||
|
|
||||||
self.unhandled_message = "in call of %s, argument '%s' is not a string literal"
|
self.unhandled_message = "in call of %s, argument '%s' is not a string literal"
|
||||||
self.unhandled_message = "while parsing %s, %s" % (name, self.unhandled_message)
|
self.unhandled_message = "while parsing %s, %s" % (name, self.unhandled_message)
|
||||||
|
|
||||||
# For the python module code it is expensive to have the function text so it is
|
def parse_python(self, node, lineno=0, filename="<string>"):
|
||||||
# uses a different fixedhash to cache against. We can take the hit on obtaining the
|
if not node or not node.strip():
|
||||||
# text if it isn't in the cache.
|
|
||||||
def parse_python(self, node, lineno=0, filename="<string>", fixedhash=None):
|
|
||||||
if not fixedhash and (not node or not node.strip()):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if fixedhash:
|
h = bbhash(str(node))
|
||||||
h = fixedhash
|
|
||||||
else:
|
|
||||||
h = bbhash(str(node))
|
|
||||||
|
|
||||||
if h in codeparsercache.pythoncache:
|
if h in codeparsercache.pythoncache:
|
||||||
self.references = set(codeparsercache.pythoncache[h].refs)
|
self.references = set(codeparsercache.pythoncache[h].refs)
|
||||||
@@ -344,7 +293,6 @@ class PythonParser():
|
|||||||
self.contains = {}
|
self.contains = {}
|
||||||
for i in codeparsercache.pythoncache[h].contains:
|
for i in codeparsercache.pythoncache[h].contains:
|
||||||
self.contains[i] = set(codeparsercache.pythoncache[h].contains[i])
|
self.contains[i] = set(codeparsercache.pythoncache[h].contains[i])
|
||||||
self.extra = codeparsercache.pythoncache[h].extra
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if h in codeparsercache.pythoncacheextras:
|
if h in codeparsercache.pythoncacheextras:
|
||||||
@@ -353,15 +301,8 @@ class PythonParser():
|
|||||||
self.contains = {}
|
self.contains = {}
|
||||||
for i in codeparsercache.pythoncacheextras[h].contains:
|
for i in codeparsercache.pythoncacheextras[h].contains:
|
||||||
self.contains[i] = set(codeparsercache.pythoncacheextras[h].contains[i])
|
self.contains[i] = set(codeparsercache.pythoncacheextras[h].contains[i])
|
||||||
self.extra = codeparsercache.pythoncacheextras[h].extra
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if fixedhash and not node:
|
|
||||||
raise KeyError
|
|
||||||
|
|
||||||
# Need to parse so take the hit on the real log buffer
|
|
||||||
self.log = BufferedLogger('BitBake.Data.PythonParser', logging.DEBUG, self._log)
|
|
||||||
|
|
||||||
# We can't add to the linenumbers for compile, we can pad to the correct number of blank lines though
|
# We can't add to the linenumbers for compile, we can pad to the correct number of blank lines though
|
||||||
node = "\n" * int(lineno) + node
|
node = "\n" * int(lineno) + node
|
||||||
code = compile(check_indent(str(node)), filename, "exec",
|
code = compile(check_indent(str(node)), filename, "exec",
|
||||||
@@ -372,22 +313,15 @@ class PythonParser():
|
|||||||
self.visit_Call(n)
|
self.visit_Call(n)
|
||||||
|
|
||||||
self.execs.update(self.var_execs)
|
self.execs.update(self.var_execs)
|
||||||
self.extra = None
|
|
||||||
if fixedhash:
|
|
||||||
self.extra = bbhash(str(node))
|
|
||||||
|
|
||||||
codeparsercache.pythoncacheextras[h] = codeparsercache.newPythonCacheLine(self.references, self.execs, self.contains, self.extra)
|
codeparsercache.pythoncacheextras[h] = codeparsercache.newPythonCacheLine(self.references, self.execs, self.contains)
|
||||||
|
|
||||||
class ShellParser():
|
class ShellParser():
|
||||||
def __init__(self, name, log):
|
def __init__(self, name, log):
|
||||||
self.funcdefs = set()
|
self.funcdefs = set()
|
||||||
self.allexecs = set()
|
self.allexecs = set()
|
||||||
self.execs = set()
|
self.execs = set()
|
||||||
self._name = name
|
self.log = BufferedLogger('BitBake.Data.%s' % name, logging.DEBUG, log)
|
||||||
self._log = log
|
|
||||||
# Defer init as expensive
|
|
||||||
self.log = DummyLogger()
|
|
||||||
|
|
||||||
self.unhandled_template = "unable to handle non-literal command '%s'"
|
self.unhandled_template = "unable to handle non-literal command '%s'"
|
||||||
self.unhandled_template = "while parsing %s, %s" % (name, self.unhandled_template)
|
self.unhandled_template = "while parsing %s, %s" % (name, self.unhandled_template)
|
||||||
|
|
||||||
@@ -406,9 +340,6 @@ class ShellParser():
|
|||||||
self.execs = set(codeparsercache.shellcacheextras[h].execs)
|
self.execs = set(codeparsercache.shellcacheextras[h].execs)
|
||||||
return self.execs
|
return self.execs
|
||||||
|
|
||||||
# Need to parse so take the hit on the real log buffer
|
|
||||||
self.log = BufferedLogger('BitBake.Data.%s' % self._name, logging.DEBUG, self._log)
|
|
||||||
|
|
||||||
self._parse_shell(value)
|
self._parse_shell(value)
|
||||||
self.execs = set(cmd for cmd in self.allexecs if cmd not in self.funcdefs)
|
self.execs = set(cmd for cmd in self.allexecs if cmd not in self.funcdefs)
|
||||||
|
|
||||||
@@ -519,7 +450,7 @@ class ShellParser():
|
|||||||
|
|
||||||
cmd = word[1]
|
cmd = word[1]
|
||||||
if cmd.startswith("$"):
|
if cmd.startswith("$"):
|
||||||
self.log.debug(self.unhandled_template % cmd)
|
self.log.debug(1, self.unhandled_template % cmd)
|
||||||
elif cmd == "eval":
|
elif cmd == "eval":
|
||||||
command = " ".join(word for _, word in words[1:])
|
command = " ".join(word for _, word in words[1:])
|
||||||
self._parse_shell(command)
|
self._parse_shell(command)
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ Commands are queued in a CommandQueue
|
|||||||
|
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
|
|
||||||
import io
|
|
||||||
import bb.event
|
import bb.event
|
||||||
import bb.cooker
|
import bb.cooker
|
||||||
import bb.remotedata
|
import bb.remotedata
|
||||||
@@ -51,32 +50,23 @@ class Command:
|
|||||||
"""
|
"""
|
||||||
A queue of asynchronous commands for bitbake
|
A queue of asynchronous commands for bitbake
|
||||||
"""
|
"""
|
||||||
def __init__(self, cooker, process_server):
|
def __init__(self, cooker):
|
||||||
self.cooker = cooker
|
self.cooker = cooker
|
||||||
self.cmds_sync = CommandsSync()
|
self.cmds_sync = CommandsSync()
|
||||||
self.cmds_async = CommandsAsync()
|
self.cmds_async = CommandsAsync()
|
||||||
self.remotedatastores = None
|
self.remotedatastores = None
|
||||||
|
|
||||||
self.process_server = process_server
|
# FIXME Add lock for this
|
||||||
# Access with locking using process_server.{get/set/clear}_async_cmd()
|
|
||||||
self.currentAsyncCommand = None
|
self.currentAsyncCommand = None
|
||||||
|
|
||||||
def runCommand(self, commandline, process_server, ro_only=False):
|
def runCommand(self, commandline, ro_only = False):
|
||||||
command = commandline.pop(0)
|
command = commandline.pop(0)
|
||||||
|
|
||||||
# Ensure cooker is ready for commands
|
# Ensure cooker is ready for commands
|
||||||
if command not in ["updateConfig", "setFeatures", "ping"]:
|
if command != "updateConfig" and command != "setFeatures":
|
||||||
try:
|
self.cooker.init_configdata()
|
||||||
self.cooker.init_configdata()
|
if not self.remotedatastores:
|
||||||
if not self.remotedatastores:
|
self.remotedatastores = bb.remotedata.RemoteDatastores(self.cooker)
|
||||||
self.remotedatastores = bb.remotedata.RemoteDatastores(self.cooker)
|
|
||||||
except (Exception, SystemExit) as exc:
|
|
||||||
import traceback
|
|
||||||
if isinstance(exc, bb.BBHandledException):
|
|
||||||
# We need to start returning real exceptions here. Until we do, we can't
|
|
||||||
# tell if an exception is an instance of bb.BBHandledException
|
|
||||||
return None, "bb.BBHandledException()\n" + traceback.format_exc()
|
|
||||||
return None, traceback.format_exc()
|
|
||||||
|
|
||||||
if hasattr(CommandsSync, command):
|
if hasattr(CommandsSync, command):
|
||||||
# Can run synchronous commands straight away
|
# Can run synchronous commands straight away
|
||||||
@@ -85,6 +75,7 @@ class Command:
|
|||||||
if not hasattr(command_method, 'readonly') or not getattr(command_method, 'readonly'):
|
if not hasattr(command_method, 'readonly') or not getattr(command_method, 'readonly'):
|
||||||
return None, "Not able to execute not readonly commands in readonly mode"
|
return None, "Not able to execute not readonly commands in readonly mode"
|
||||||
try:
|
try:
|
||||||
|
self.cooker.process_inotify_updates()
|
||||||
if getattr(command_method, 'needconfig', True):
|
if getattr(command_method, 'needconfig', True):
|
||||||
self.cooker.updateCacheSync()
|
self.cooker.updateCacheSync()
|
||||||
result = command_method(self, commandline)
|
result = command_method(self, commandline)
|
||||||
@@ -99,23 +90,24 @@ class Command:
|
|||||||
return None, traceback.format_exc()
|
return None, traceback.format_exc()
|
||||||
else:
|
else:
|
||||||
return result, None
|
return result, None
|
||||||
|
if self.currentAsyncCommand is not None:
|
||||||
|
return None, "Busy (%s in progress)" % self.currentAsyncCommand[0]
|
||||||
if command not in CommandsAsync.__dict__:
|
if command not in CommandsAsync.__dict__:
|
||||||
return None, "No such command"
|
return None, "No such command"
|
||||||
if not process_server.set_async_cmd((command, commandline)):
|
self.currentAsyncCommand = (command, commandline)
|
||||||
return None, "Busy (%s in progress)" % self.process_server.get_async_cmd()[0]
|
self.cooker.idleCallBackRegister(self.cooker.runCommands, self.cooker)
|
||||||
self.cooker.idleCallBackRegister(self.runAsyncCommand, process_server)
|
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
def runAsyncCommand(self, _, process_server, halt):
|
def runAsyncCommand(self):
|
||||||
try:
|
try:
|
||||||
|
self.cooker.process_inotify_updates()
|
||||||
if self.cooker.state in (bb.cooker.state.error, bb.cooker.state.shutdown, bb.cooker.state.forceshutdown):
|
if self.cooker.state in (bb.cooker.state.error, bb.cooker.state.shutdown, bb.cooker.state.forceshutdown):
|
||||||
# updateCache will trigger a shutdown of the parser
|
# updateCache will trigger a shutdown of the parser
|
||||||
# and then raise BBHandledException triggering an exit
|
# and then raise BBHandledException triggering an exit
|
||||||
self.cooker.updateCache()
|
self.cooker.updateCache()
|
||||||
return bb.server.process.idleFinish("Cooker in error state")
|
return False
|
||||||
cmd = process_server.get_async_cmd()
|
if self.currentAsyncCommand is not None:
|
||||||
if cmd is not None:
|
(command, options) = self.currentAsyncCommand
|
||||||
(command, options) = cmd
|
|
||||||
commandmethod = getattr(CommandsAsync, command)
|
commandmethod = getattr(CommandsAsync, command)
|
||||||
needcache = getattr( commandmethod, "needcache" )
|
needcache = getattr( commandmethod, "needcache" )
|
||||||
if needcache and self.cooker.state != bb.cooker.state.running:
|
if needcache and self.cooker.state != bb.cooker.state.running:
|
||||||
@@ -125,21 +117,24 @@ class Command:
|
|||||||
commandmethod(self.cmds_async, self, options)
|
commandmethod(self.cmds_async, self, options)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return bb.server.process.idleFinish("Nothing to do, no async command?")
|
return False
|
||||||
except KeyboardInterrupt as exc:
|
except KeyboardInterrupt as exc:
|
||||||
return bb.server.process.idleFinish("Interrupted")
|
self.finishAsyncCommand("Interrupted")
|
||||||
|
return False
|
||||||
except SystemExit as exc:
|
except SystemExit as exc:
|
||||||
arg = exc.args[0]
|
arg = exc.args[0]
|
||||||
if isinstance(arg, str):
|
if isinstance(arg, str):
|
||||||
return bb.server.process.idleFinish(arg)
|
self.finishAsyncCommand(arg)
|
||||||
else:
|
else:
|
||||||
return bb.server.process.idleFinish("Exited with %s" % arg)
|
self.finishAsyncCommand("Exited with %s" % arg)
|
||||||
|
return False
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
import traceback
|
import traceback
|
||||||
if isinstance(exc, bb.BBHandledException):
|
if isinstance(exc, bb.BBHandledException):
|
||||||
return bb.server.process.idleFinish("")
|
self.finishAsyncCommand("")
|
||||||
else:
|
else:
|
||||||
return bb.server.process.idleFinish(traceback.format_exc())
|
self.finishAsyncCommand(traceback.format_exc())
|
||||||
|
return False
|
||||||
|
|
||||||
def finishAsyncCommand(self, msg=None, code=None):
|
def finishAsyncCommand(self, msg=None, code=None):
|
||||||
if msg or msg == "":
|
if msg or msg == "":
|
||||||
@@ -148,8 +143,8 @@ class Command:
|
|||||||
bb.event.fire(CommandExit(code), self.cooker.data)
|
bb.event.fire(CommandExit(code), self.cooker.data)
|
||||||
else:
|
else:
|
||||||
bb.event.fire(CommandCompleted(), self.cooker.data)
|
bb.event.fire(CommandCompleted(), self.cooker.data)
|
||||||
|
self.currentAsyncCommand = None
|
||||||
self.cooker.finishcommand()
|
self.cooker.finishcommand()
|
||||||
self.process_server.clear_async_cmd()
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
if self.remotedatastores:
|
if self.remotedatastores:
|
||||||
@@ -162,14 +157,6 @@ class CommandsSync:
|
|||||||
These must not influence any running synchronous command.
|
These must not influence any running synchronous command.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def ping(self, command, params):
|
|
||||||
"""
|
|
||||||
Allow a UI to check the server is still alive
|
|
||||||
"""
|
|
||||||
return "Still alive!"
|
|
||||||
ping.needconfig = False
|
|
||||||
ping.readonly = True
|
|
||||||
|
|
||||||
def stateShutdown(self, command, params):
|
def stateShutdown(self, command, params):
|
||||||
"""
|
"""
|
||||||
Trigger cooker 'shutdown' mode
|
Trigger cooker 'shutdown' mode
|
||||||
@@ -307,11 +294,6 @@ class CommandsSync:
|
|||||||
return ret
|
return ret
|
||||||
getLayerPriorities.readonly = True
|
getLayerPriorities.readonly = True
|
||||||
|
|
||||||
def revalidateCaches(self, command, params):
|
|
||||||
"""Called by UI clients when metadata may have changed"""
|
|
||||||
command.cooker.revalidateCaches()
|
|
||||||
parseConfiguration.needconfig = False
|
|
||||||
|
|
||||||
def getRecipes(self, command, params):
|
def getRecipes(self, command, params):
|
||||||
try:
|
try:
|
||||||
mc = params[0]
|
mc = params[0]
|
||||||
@@ -420,30 +402,15 @@ class CommandsSync:
|
|||||||
return command.cooker.recipecaches[mc].pkg_dp
|
return command.cooker.recipecaches[mc].pkg_dp
|
||||||
getDefaultPreference.readonly = True
|
getDefaultPreference.readonly = True
|
||||||
|
|
||||||
|
|
||||||
def getSkippedRecipes(self, command, params):
|
def getSkippedRecipes(self, command, params):
|
||||||
"""
|
|
||||||
Get the map of skipped recipes for the specified multiconfig/mc name (`params[0]`).
|
|
||||||
|
|
||||||
Invoked by `bb.tinfoil.Tinfoil.get_skipped_recipes`
|
|
||||||
|
|
||||||
:param command: Internally used parameter.
|
|
||||||
:param params: Parameter array. params[0] is multiconfig/mc name. If not given, then default mc '' is assumed.
|
|
||||||
:return: Dict whose keys are virtualfns and values are `bb.cooker.SkippedPackage`
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
mc = params[0]
|
|
||||||
except IndexError:
|
|
||||||
mc = ''
|
|
||||||
|
|
||||||
# Return list sorted by reverse priority order
|
# Return list sorted by reverse priority order
|
||||||
import bb.cache
|
import bb.cache
|
||||||
def sortkey(x):
|
def sortkey(x):
|
||||||
vfn, _ = x
|
vfn, _ = x
|
||||||
realfn, _, item_mc = bb.cache.virtualfn2realfn(vfn)
|
realfn, _, mc = bb.cache.virtualfn2realfn(vfn)
|
||||||
return -command.cooker.collections[item_mc].calc_bbfile_priority(realfn)[0], vfn
|
return (-command.cooker.collections[mc].calc_bbfile_priority(realfn)[0], vfn)
|
||||||
|
|
||||||
skipdict = OrderedDict(sorted(command.cooker.skiplist_by_mc[mc].items(), key=sortkey))
|
skipdict = OrderedDict(sorted(command.cooker.skiplist.items(), key=sortkey))
|
||||||
return list(skipdict.items())
|
return list(skipdict.items())
|
||||||
getSkippedRecipes.readonly = True
|
getSkippedRecipes.readonly = True
|
||||||
|
|
||||||
@@ -533,17 +500,6 @@ class CommandsSync:
|
|||||||
d = command.remotedatastores[dsindex].varhistory
|
d = command.remotedatastores[dsindex].varhistory
|
||||||
return getattr(d, method)(*args, **kwargs)
|
return getattr(d, method)(*args, **kwargs)
|
||||||
|
|
||||||
def dataStoreConnectorVarHistCmdEmit(self, command, params):
|
|
||||||
dsindex = params[0]
|
|
||||||
var = params[1]
|
|
||||||
oval = params[2]
|
|
||||||
val = params[3]
|
|
||||||
d = command.remotedatastores[params[4]]
|
|
||||||
|
|
||||||
o = io.StringIO()
|
|
||||||
command.remotedatastores[dsindex].varhistory.emit(var, oval, val, o, d)
|
|
||||||
return o.getvalue()
|
|
||||||
|
|
||||||
def dataStoreConnectorIncHistCmd(self, command, params):
|
def dataStoreConnectorIncHistCmd(self, command, params):
|
||||||
dsindex = params[0]
|
dsindex = params[0]
|
||||||
method = params[1]
|
method = params[1]
|
||||||
@@ -565,8 +521,8 @@ class CommandsSync:
|
|||||||
and return a datastore object representing the environment
|
and return a datastore object representing the environment
|
||||||
for the recipe.
|
for the recipe.
|
||||||
"""
|
"""
|
||||||
virtualfn = params[0]
|
fn = params[0]
|
||||||
(fn, cls, mc) = bb.cache.virtualfn2realfn(virtualfn)
|
mc = bb.runqueue.mc_from_tid(fn)
|
||||||
appends = params[1]
|
appends = params[1]
|
||||||
appendlist = params[2]
|
appendlist = params[2]
|
||||||
if len(params) > 3:
|
if len(params) > 3:
|
||||||
@@ -581,7 +537,6 @@ class CommandsSync:
|
|||||||
appendfiles = command.cooker.collections[mc].get_file_appends(fn)
|
appendfiles = command.cooker.collections[mc].get_file_appends(fn)
|
||||||
else:
|
else:
|
||||||
appendfiles = []
|
appendfiles = []
|
||||||
layername = command.cooker.collections[mc].calc_bbfile_priority(fn)[2]
|
|
||||||
# We are calling bb.cache locally here rather than on the server,
|
# We are calling bb.cache locally here rather than on the server,
|
||||||
# but that's OK because it doesn't actually need anything from
|
# but that's OK because it doesn't actually need anything from
|
||||||
# the server barring the global datastore (which we have a remote
|
# the server barring the global datastore (which we have a remote
|
||||||
@@ -589,10 +544,11 @@ class CommandsSync:
|
|||||||
if config_data:
|
if config_data:
|
||||||
# We have to use a different function here if we're passing in a datastore
|
# We have to use a different function here if we're passing in a datastore
|
||||||
# NOTE: we took a copy above, so we don't do it here again
|
# NOTE: we took a copy above, so we don't do it here again
|
||||||
envdata = command.cooker.databuilder._parse_recipe(config_data, fn, appendfiles, mc, layername)[cls]
|
envdata = bb.cache.parse_recipe(config_data, fn, appendfiles, mc)['']
|
||||||
else:
|
else:
|
||||||
# Use the standard path
|
# Use the standard path
|
||||||
envdata = command.cooker.databuilder.parseRecipe(virtualfn, appendfiles, layername)
|
parser = bb.cache.NoCache(command.cooker.databuilder)
|
||||||
|
envdata = parser.loadDataFull(fn, appendfiles)
|
||||||
idx = command.remotedatastores.store(envdata)
|
idx = command.remotedatastores.store(envdata)
|
||||||
return DataStoreConnectionHandle(idx)
|
return DataStoreConnectionHandle(idx)
|
||||||
parseRecipeFile.readonly = True
|
parseRecipeFile.readonly = True
|
||||||
@@ -691,16 +647,6 @@ class CommandsAsync:
|
|||||||
command.finishAsyncCommand()
|
command.finishAsyncCommand()
|
||||||
findFilesMatchingInDir.needcache = False
|
findFilesMatchingInDir.needcache = False
|
||||||
|
|
||||||
def testCookerCommandEvent(self, command, params):
|
|
||||||
"""
|
|
||||||
Dummy command used by OEQA selftest to test tinfoil without IO
|
|
||||||
"""
|
|
||||||
pattern = params[0]
|
|
||||||
|
|
||||||
command.cooker.testCookerCommandEvent(pattern)
|
|
||||||
command.finishAsyncCommand()
|
|
||||||
testCookerCommandEvent.needcache = False
|
|
||||||
|
|
||||||
def findConfigFilePath(self, command, params):
|
def findConfigFilePath(self, command, params):
|
||||||
"""
|
"""
|
||||||
Find the path of the requested configuration file
|
Find the path of the requested configuration file
|
||||||
@@ -765,7 +711,7 @@ class CommandsAsync:
|
|||||||
"""
|
"""
|
||||||
event = params[0]
|
event = params[0]
|
||||||
bb.event.fire(eval(event), command.cooker.data)
|
bb.event.fire(eval(event), command.cooker.data)
|
||||||
process_server.clear_async_cmd()
|
command.currentAsyncCommand = None
|
||||||
triggerEvent.needcache = False
|
triggerEvent.needcache = False
|
||||||
|
|
||||||
def resetCooker(self, command, params):
|
def resetCooker(self, command, params):
|
||||||
@@ -792,14 +738,7 @@ class CommandsAsync:
|
|||||||
(mc, pn) = bb.runqueue.split_mc(params[0])
|
(mc, pn) = bb.runqueue.split_mc(params[0])
|
||||||
taskname = params[1]
|
taskname = params[1]
|
||||||
sigs = params[2]
|
sigs = params[2]
|
||||||
bb.siggen.check_siggen_version(bb.siggen)
|
|
||||||
res = bb.siggen.find_siginfo(pn, taskname, sigs, command.cooker.databuilder.mcdata[mc])
|
res = bb.siggen.find_siginfo(pn, taskname, sigs, command.cooker.databuilder.mcdata[mc])
|
||||||
bb.event.fire(bb.event.FindSigInfoResult(res), command.cooker.databuilder.mcdata[mc])
|
bb.event.fire(bb.event.FindSigInfoResult(res), command.cooker.databuilder.mcdata[mc])
|
||||||
command.finishAsyncCommand()
|
command.finishAsyncCommand()
|
||||||
findSigInfo.needcache = False
|
findSigInfo.needcache = False
|
||||||
|
|
||||||
def getTaskSignatures(self, command, params):
|
|
||||||
res = command.cooker.getTaskSignatures(params[0], params[1])
|
|
||||||
bb.event.fire(bb.event.GetTaskSignatureResult(res), command.cooker.data)
|
|
||||||
command.finishAsyncCommand()
|
|
||||||
getTaskSignatures.needcache = True
|
|
||||||
|
|||||||
@@ -1,196 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
# Helper library to implement streaming compression and decompression using an
|
|
||||||
# external process
|
|
||||||
#
|
|
||||||
# This library should be used directly by end users; a wrapper library for the
|
|
||||||
# specific compression tool should be created
|
|
||||||
|
|
||||||
import builtins
|
|
||||||
import io
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
def open_wrap(
|
|
||||||
cls, filename, mode="rb", *, encoding=None, errors=None, newline=None, **kwargs
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Open a compressed file in binary or text mode.
|
|
||||||
|
|
||||||
Users should not call this directly. A specific compression library can use
|
|
||||||
this helper to provide it's own "open" command
|
|
||||||
|
|
||||||
The filename argument can be an actual filename (a str or bytes object), or
|
|
||||||
an existing file object to read from or write to.
|
|
||||||
|
|
||||||
The mode argument can be "r", "rb", "w", "wb", "x", "xb", "a" or "ab" for
|
|
||||||
binary mode, or "rt", "wt", "xt" or "at" for text mode. The default mode is
|
|
||||||
"rb".
|
|
||||||
|
|
||||||
For binary mode, this function is equivalent to the cls constructor:
|
|
||||||
cls(filename, mode). In this case, the encoding, errors and newline
|
|
||||||
arguments must not be provided.
|
|
||||||
|
|
||||||
For text mode, a cls object is created, and wrapped in an
|
|
||||||
io.TextIOWrapper instance with the specified encoding, error handling
|
|
||||||
behavior, and line ending(s).
|
|
||||||
"""
|
|
||||||
if "t" in mode:
|
|
||||||
if "b" in mode:
|
|
||||||
raise ValueError("Invalid mode: %r" % (mode,))
|
|
||||||
else:
|
|
||||||
if encoding is not None:
|
|
||||||
raise ValueError("Argument 'encoding' not supported in binary mode")
|
|
||||||
if errors is not None:
|
|
||||||
raise ValueError("Argument 'errors' not supported in binary mode")
|
|
||||||
if newline is not None:
|
|
||||||
raise ValueError("Argument 'newline' not supported in binary mode")
|
|
||||||
|
|
||||||
file_mode = mode.replace("t", "")
|
|
||||||
if isinstance(filename, (str, bytes, os.PathLike, int)):
|
|
||||||
binary_file = cls(filename, file_mode, **kwargs)
|
|
||||||
elif hasattr(filename, "read") or hasattr(filename, "write"):
|
|
||||||
binary_file = cls(None, file_mode, fileobj=filename, **kwargs)
|
|
||||||
else:
|
|
||||||
raise TypeError("filename must be a str or bytes object, or a file")
|
|
||||||
|
|
||||||
if "t" in mode:
|
|
||||||
return io.TextIOWrapper(
|
|
||||||
binary_file, encoding, errors, newline, write_through=True
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return binary_file
|
|
||||||
|
|
||||||
|
|
||||||
class CompressionError(OSError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PipeFile(io.RawIOBase):
|
|
||||||
"""
|
|
||||||
Class that implements generically piping to/from a compression program
|
|
||||||
|
|
||||||
Derived classes should add the function get_compress() and get_decompress()
|
|
||||||
that return the required commands. Input will be piped into stdin and the
|
|
||||||
(de)compressed output should be written to stdout, e.g.:
|
|
||||||
|
|
||||||
class FooFile(PipeCompressionFile):
|
|
||||||
def get_decompress(self):
|
|
||||||
return ["fooc", "--decompress", "--stdout"]
|
|
||||||
|
|
||||||
def get_compress(self):
|
|
||||||
return ["fooc", "--compress", "--stdout"]
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
READ = 0
|
|
||||||
WRITE = 1
|
|
||||||
|
|
||||||
def __init__(self, filename=None, mode="rb", *, stderr=None, fileobj=None):
|
|
||||||
if "t" in mode or "U" in mode:
|
|
||||||
raise ValueError("Invalid mode: {!r}".format(mode))
|
|
||||||
|
|
||||||
if not "b" in mode:
|
|
||||||
mode += "b"
|
|
||||||
|
|
||||||
if mode.startswith("r"):
|
|
||||||
self.mode = self.READ
|
|
||||||
elif mode.startswith("w"):
|
|
||||||
self.mode = self.WRITE
|
|
||||||
else:
|
|
||||||
raise ValueError("Invalid mode %r" % mode)
|
|
||||||
|
|
||||||
if fileobj is not None:
|
|
||||||
self.fileobj = fileobj
|
|
||||||
else:
|
|
||||||
self.fileobj = builtins.open(filename, mode or "rb")
|
|
||||||
|
|
||||||
if self.mode == self.READ:
|
|
||||||
self.p = subprocess.Popen(
|
|
||||||
self.get_decompress(),
|
|
||||||
stdin=self.fileobj,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=stderr,
|
|
||||||
close_fds=True,
|
|
||||||
)
|
|
||||||
self.pipe = self.p.stdout
|
|
||||||
else:
|
|
||||||
self.p = subprocess.Popen(
|
|
||||||
self.get_compress(),
|
|
||||||
stdin=subprocess.PIPE,
|
|
||||||
stdout=self.fileobj,
|
|
||||||
stderr=stderr,
|
|
||||||
close_fds=True,
|
|
||||||
)
|
|
||||||
self.pipe = self.p.stdin
|
|
||||||
|
|
||||||
self.__closed = False
|
|
||||||
|
|
||||||
def _check_process(self):
|
|
||||||
if self.p is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
returncode = self.p.wait()
|
|
||||||
if returncode:
|
|
||||||
raise CompressionError("Process died with %d" % returncode)
|
|
||||||
self.p = None
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self.closed:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.pipe.close()
|
|
||||||
if self.p is not None:
|
|
||||||
self._check_process()
|
|
||||||
self.fileobj.close()
|
|
||||||
|
|
||||||
self.__closed = True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def closed(self):
|
|
||||||
return self.__closed
|
|
||||||
|
|
||||||
def fileno(self):
|
|
||||||
return self.pipe.fileno()
|
|
||||||
|
|
||||||
def flush(self):
|
|
||||||
self.pipe.flush()
|
|
||||||
|
|
||||||
def isatty(self):
|
|
||||||
return self.pipe.isatty()
|
|
||||||
|
|
||||||
def readable(self):
|
|
||||||
return self.mode == self.READ
|
|
||||||
|
|
||||||
def writable(self):
|
|
||||||
return self.mode == self.WRITE
|
|
||||||
|
|
||||||
def readinto(self, b):
|
|
||||||
if self.mode != self.READ:
|
|
||||||
import errno
|
|
||||||
|
|
||||||
raise OSError(
|
|
||||||
errno.EBADF, "read() on write-only %s object" % self.__class__.__name__
|
|
||||||
)
|
|
||||||
size = self.pipe.readinto(b)
|
|
||||||
if size == 0:
|
|
||||||
self._check_process()
|
|
||||||
return size
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
if self.mode != self.WRITE:
|
|
||||||
import errno
|
|
||||||
|
|
||||||
raise OSError(
|
|
||||||
errno.EBADF, "write() on read-only %s object" % self.__class__.__name__
|
|
||||||
)
|
|
||||||
data = self.pipe.write(data)
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
self._check_process()
|
|
||||||
|
|
||||||
return data
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
|
|
||||||
import bb.compress._pipecompress
|
|
||||||
|
|
||||||
|
|
||||||
def open(*args, **kwargs):
|
|
||||||
return bb.compress._pipecompress.open_wrap(LZ4File, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class LZ4File(bb.compress._pipecompress.PipeFile):
|
|
||||||
def get_compress(self):
|
|
||||||
return ["lz4c", "-z", "-c"]
|
|
||||||
|
|
||||||
def get_decompress(self):
|
|
||||||
return ["lz4c", "-d", "-c"]
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
|
|
||||||
import bb.compress._pipecompress
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
|
|
||||||
def open(*args, **kwargs):
|
|
||||||
return bb.compress._pipecompress.open_wrap(ZstdFile, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class ZstdFile(bb.compress._pipecompress.PipeFile):
|
|
||||||
def __init__(self, *args, num_threads=1, compresslevel=3, **kwargs):
|
|
||||||
self.num_threads = num_threads
|
|
||||||
self.compresslevel = compresslevel
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def _get_zstd(self):
|
|
||||||
if self.num_threads == 1 or not shutil.which("pzstd"):
|
|
||||||
return ["zstd"]
|
|
||||||
return ["pzstd", "-p", "%d" % self.num_threads]
|
|
||||||
|
|
||||||
def get_compress(self):
|
|
||||||
return self._get_zstd() + ["-c", "-%d" % self.compresslevel]
|
|
||||||
|
|
||||||
def get_decompress(self):
|
|
||||||
return self._get_zstd() + ["-d", "-c"]
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -23,8 +23,8 @@ logger = logging.getLogger("BitBake")
|
|||||||
parselog = logging.getLogger("BitBake.Parsing")
|
parselog = logging.getLogger("BitBake.Parsing")
|
||||||
|
|
||||||
class ConfigParameters(object):
|
class ConfigParameters(object):
|
||||||
def __init__(self, argv=None):
|
def __init__(self, argv=sys.argv):
|
||||||
self.options, targets = self.parseCommandLine(argv or sys.argv)
|
self.options, targets = self.parseCommandLine(argv)
|
||||||
self.environment = self.parseEnvironment()
|
self.environment = self.parseEnvironment()
|
||||||
|
|
||||||
self.options.pkgs_to_build = targets or []
|
self.options.pkgs_to_build = targets or []
|
||||||
@@ -57,7 +57,7 @@ class ConfigParameters(object):
|
|||||||
|
|
||||||
def updateToServer(self, server, environment):
|
def updateToServer(self, server, environment):
|
||||||
options = {}
|
options = {}
|
||||||
for o in ["halt", "force", "invalidate_stamp",
|
for o in ["abort", "force", "invalidate_stamp",
|
||||||
"dry_run", "dump_signatures",
|
"dry_run", "dump_signatures",
|
||||||
"extra_assume_provided", "profile",
|
"extra_assume_provided", "profile",
|
||||||
"prefile", "postfile", "server_timeout",
|
"prefile", "postfile", "server_timeout",
|
||||||
@@ -86,7 +86,7 @@ class ConfigParameters(object):
|
|||||||
action['msg'] = "Only one target can be used with the --environment option."
|
action['msg'] = "Only one target can be used with the --environment option."
|
||||||
elif self.options.buildfile and len(self.options.pkgs_to_build) > 0:
|
elif self.options.buildfile and len(self.options.pkgs_to_build) > 0:
|
||||||
action['msg'] = "No target should be used with the --environment and --buildfile options."
|
action['msg'] = "No target should be used with the --environment and --buildfile options."
|
||||||
elif self.options.pkgs_to_build:
|
elif len(self.options.pkgs_to_build) > 0:
|
||||||
action['action'] = ["showEnvironmentTarget", self.options.pkgs_to_build]
|
action['action'] = ["showEnvironmentTarget", self.options.pkgs_to_build]
|
||||||
else:
|
else:
|
||||||
action['action'] = ["showEnvironment", self.options.buildfile]
|
action['action'] = ["showEnvironment", self.options.buildfile]
|
||||||
@@ -124,7 +124,7 @@ class CookerConfiguration(object):
|
|||||||
self.prefile = []
|
self.prefile = []
|
||||||
self.postfile = []
|
self.postfile = []
|
||||||
self.cmd = None
|
self.cmd = None
|
||||||
self.halt = True
|
self.abort = True
|
||||||
self.force = False
|
self.force = False
|
||||||
self.profile = False
|
self.profile = False
|
||||||
self.nosetscene = False
|
self.nosetscene = False
|
||||||
@@ -160,7 +160,12 @@ def catch_parse_error(func):
|
|||||||
def wrapped(fn, *args):
|
def wrapped(fn, *args):
|
||||||
try:
|
try:
|
||||||
return func(fn, *args)
|
return func(fn, *args)
|
||||||
except Exception as exc:
|
except IOError as exc:
|
||||||
|
import traceback
|
||||||
|
parselog.critical(traceback.format_exc())
|
||||||
|
parselog.critical("Unable to parse %s: %s" % (fn, exc))
|
||||||
|
raise bb.BBHandledException()
|
||||||
|
except bb.data_smart.ExpansionError as exc:
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
bbdir = os.path.dirname(__file__) + os.sep
|
bbdir = os.path.dirname(__file__) + os.sep
|
||||||
@@ -172,11 +177,14 @@ def catch_parse_error(func):
|
|||||||
break
|
break
|
||||||
parselog.critical("Unable to parse %s" % fn, exc_info=(exc_class, exc, tb))
|
parselog.critical("Unable to parse %s" % fn, exc_info=(exc_class, exc, tb))
|
||||||
raise bb.BBHandledException()
|
raise bb.BBHandledException()
|
||||||
|
except bb.parse.ParseError as exc:
|
||||||
|
parselog.critical(str(exc))
|
||||||
|
raise bb.BBHandledException()
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
@catch_parse_error
|
@catch_parse_error
|
||||||
def parse_config_file(fn, data, include=True):
|
def parse_config_file(fn, data, include=True):
|
||||||
return bb.parse.handle(fn, data, include, baseconfig=True)
|
return bb.parse.handle(fn, data, include)
|
||||||
|
|
||||||
@catch_parse_error
|
@catch_parse_error
|
||||||
def _inherit(bbclass, data):
|
def _inherit(bbclass, data):
|
||||||
@@ -202,7 +210,7 @@ def findConfigFile(configfile, data):
|
|||||||
|
|
||||||
#
|
#
|
||||||
# We search for a conf/bblayers.conf under an entry in BBPATH or in cwd working
|
# We search for a conf/bblayers.conf under an entry in BBPATH or in cwd working
|
||||||
# up to /. If that fails, bitbake would fall back to cwd.
|
# up to /. If that fails, we search for a conf/bitbake.conf in BBPATH.
|
||||||
#
|
#
|
||||||
|
|
||||||
def findTopdir():
|
def findTopdir():
|
||||||
@@ -215,8 +223,11 @@ def findTopdir():
|
|||||||
layerconf = findConfigFile("bblayers.conf", d)
|
layerconf = findConfigFile("bblayers.conf", d)
|
||||||
if layerconf:
|
if layerconf:
|
||||||
return os.path.dirname(os.path.dirname(layerconf))
|
return os.path.dirname(os.path.dirname(layerconf))
|
||||||
|
if bbpath:
|
||||||
return os.path.abspath(os.getcwd())
|
bitbakeconf = bb.utils.which(bbpath, "conf/bitbake.conf")
|
||||||
|
if bitbakeconf:
|
||||||
|
return os.path.dirname(os.path.dirname(bitbakeconf))
|
||||||
|
return None
|
||||||
|
|
||||||
class CookerDataBuilder(object):
|
class CookerDataBuilder(object):
|
||||||
|
|
||||||
@@ -239,14 +250,10 @@ class CookerDataBuilder(object):
|
|||||||
self.savedenv = bb.data.init()
|
self.savedenv = bb.data.init()
|
||||||
for k in cookercfg.env:
|
for k in cookercfg.env:
|
||||||
self.savedenv.setVar(k, cookercfg.env[k])
|
self.savedenv.setVar(k, cookercfg.env[k])
|
||||||
if k in bb.data_smart.bitbake_renamed_vars:
|
|
||||||
bb.error('Shell environment variable %s has been renamed to %s' % (k, bb.data_smart.bitbake_renamed_vars[k]))
|
|
||||||
bb.fatal("Exiting to allow enviroment variables to be corrected")
|
|
||||||
|
|
||||||
filtered_keys = bb.utils.approved_variables()
|
filtered_keys = bb.utils.approved_variables()
|
||||||
bb.data.inheritFromOS(self.basedata, self.savedenv, filtered_keys)
|
bb.data.inheritFromOS(self.basedata, self.savedenv, filtered_keys)
|
||||||
self.basedata.setVar("BB_ORIGENV", self.savedenv)
|
self.basedata.setVar("BB_ORIGENV", self.savedenv)
|
||||||
self.basedata.setVar("__bbclasstype", "global")
|
|
||||||
|
|
||||||
if worker:
|
if worker:
|
||||||
self.basedata.setVar("BB_WORKERCONTEXT", "1")
|
self.basedata.setVar("BB_WORKERCONTEXT", "1")
|
||||||
@@ -254,15 +261,15 @@ class CookerDataBuilder(object):
|
|||||||
self.data = self.basedata
|
self.data = self.basedata
|
||||||
self.mcdata = {}
|
self.mcdata = {}
|
||||||
|
|
||||||
def parseBaseConfiguration(self, worker=False):
|
def parseBaseConfiguration(self):
|
||||||
mcdata = {}
|
|
||||||
data_hash = hashlib.sha256()
|
data_hash = hashlib.sha256()
|
||||||
try:
|
try:
|
||||||
self.data = self.parseConfigurationFiles(self.prefiles, self.postfiles)
|
self.data = self.parseConfigurationFiles(self.prefiles, self.postfiles)
|
||||||
|
|
||||||
if self.data.getVar("BB_WORKERCONTEXT", False) is None and not worker:
|
if self.data.getVar("BB_WORKERCONTEXT", False) is None:
|
||||||
bb.fetch.fetcher_init(self.data)
|
bb.fetch.fetcher_init(self.data)
|
||||||
bb.parse.init_parser(self.data)
|
bb.parse.init_parser(self.data)
|
||||||
|
bb.codeparser.parser_cache_init(self.data)
|
||||||
|
|
||||||
bb.event.fire(bb.event.ConfigParsed(), self.data)
|
bb.event.fire(bb.event.ConfigParsed(), self.data)
|
||||||
|
|
||||||
@@ -280,62 +287,38 @@ class CookerDataBuilder(object):
|
|||||||
|
|
||||||
bb.parse.init_parser(self.data)
|
bb.parse.init_parser(self.data)
|
||||||
data_hash.update(self.data.get_hash().encode('utf-8'))
|
data_hash.update(self.data.get_hash().encode('utf-8'))
|
||||||
mcdata[''] = self.data
|
self.mcdata[''] = self.data
|
||||||
|
|
||||||
multiconfig = (self.data.getVar("BBMULTICONFIG") or "").split()
|
multiconfig = (self.data.getVar("BBMULTICONFIG") or "").split()
|
||||||
for config in multiconfig:
|
for config in multiconfig:
|
||||||
if config[0].isdigit():
|
mcdata = self.parseConfigurationFiles(self.prefiles, self.postfiles, config)
|
||||||
bb.fatal("Multiconfig name '%s' is invalid as multiconfigs cannot start with a digit" % config)
|
bb.event.fire(bb.event.ConfigParsed(), mcdata)
|
||||||
parsed_mcdata = self.parseConfigurationFiles(self.prefiles, self.postfiles, config)
|
self.mcdata[config] = mcdata
|
||||||
bb.event.fire(bb.event.ConfigParsed(), parsed_mcdata)
|
data_hash.update(mcdata.get_hash().encode('utf-8'))
|
||||||
mcdata[config] = parsed_mcdata
|
|
||||||
data_hash.update(parsed_mcdata.get_hash().encode('utf-8'))
|
|
||||||
if multiconfig:
|
if multiconfig:
|
||||||
bb.event.fire(bb.event.MultiConfigParsed(mcdata), self.data)
|
bb.event.fire(bb.event.MultiConfigParsed(self.mcdata), self.data)
|
||||||
|
|
||||||
self.data_hash = data_hash.hexdigest()
|
self.data_hash = data_hash.hexdigest()
|
||||||
|
except (SyntaxError, bb.BBHandledException):
|
||||||
|
raise bb.BBHandledException()
|
||||||
except bb.data_smart.ExpansionError as e:
|
except bb.data_smart.ExpansionError as e:
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
raise bb.BBHandledException()
|
raise bb.BBHandledException()
|
||||||
|
except Exception:
|
||||||
bb.codeparser.update_module_dependencies(self.data)
|
logger.exception("Error parsing configuration files")
|
||||||
|
|
||||||
# Handle obsolete variable names
|
|
||||||
d = self.data
|
|
||||||
renamedvars = d.getVarFlags('BB_RENAMED_VARIABLES') or {}
|
|
||||||
renamedvars.update(bb.data_smart.bitbake_renamed_vars)
|
|
||||||
issues = False
|
|
||||||
for v in renamedvars:
|
|
||||||
if d.getVar(v) != None or d.hasOverrides(v):
|
|
||||||
issues = True
|
|
||||||
loginfo = {}
|
|
||||||
history = d.varhistory.get_variable_refs(v)
|
|
||||||
for h in history:
|
|
||||||
for line in history[h]:
|
|
||||||
loginfo = {'file' : h, 'line' : line}
|
|
||||||
bb.data.data_smart._print_rename_error(v, loginfo, renamedvars)
|
|
||||||
if not history:
|
|
||||||
bb.data.data_smart._print_rename_error(v, loginfo, renamedvars)
|
|
||||||
if issues:
|
|
||||||
raise bb.BBHandledException()
|
raise bb.BBHandledException()
|
||||||
|
|
||||||
for mc in mcdata:
|
|
||||||
mcdata[mc].renameVar("__depends", "__base_depends")
|
|
||||||
mcdata[mc].setVar("__bbclasstype", "recipe")
|
|
||||||
|
|
||||||
# Create a copy so we can reset at a later date when UIs disconnect
|
# Create a copy so we can reset at a later date when UIs disconnect
|
||||||
self.mcorigdata = mcdata
|
self.origdata = self.data
|
||||||
for mc in mcdata:
|
self.data = bb.data.createCopy(self.origdata)
|
||||||
self.mcdata[mc] = bb.data.createCopy(mcdata[mc])
|
self.mcdata[''] = self.data
|
||||||
self.data = self.mcdata['']
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
# We may not have run parseBaseConfiguration() yet
|
# We may not have run parseBaseConfiguration() yet
|
||||||
if not hasattr(self, 'mcorigdata'):
|
if not hasattr(self, 'origdata'):
|
||||||
return
|
return
|
||||||
for mc in self.mcorigdata:
|
self.data = bb.data.createCopy(self.origdata)
|
||||||
self.mcdata[mc] = bb.data.createCopy(self.mcorigdata[mc])
|
self.mcdata[''] = self.data
|
||||||
self.data = self.mcdata['']
|
|
||||||
|
|
||||||
def _findLayerConf(self, data):
|
def _findLayerConf(self, data):
|
||||||
return findConfigFile("bblayers.conf", data)
|
return findConfigFile("bblayers.conf", data)
|
||||||
@@ -350,23 +333,15 @@ class CookerDataBuilder(object):
|
|||||||
|
|
||||||
layerconf = self._findLayerConf(data)
|
layerconf = self._findLayerConf(data)
|
||||||
if layerconf:
|
if layerconf:
|
||||||
parselog.debug2("Found bblayers.conf (%s)", layerconf)
|
parselog.debug(2, "Found bblayers.conf (%s)", layerconf)
|
||||||
# By definition bblayers.conf is in conf/ of TOPDIR.
|
# By definition bblayers.conf is in conf/ of TOPDIR.
|
||||||
# We may have been called with cwd somewhere else so reset TOPDIR
|
# We may have been called with cwd somewhere else so reset TOPDIR
|
||||||
data.setVar("TOPDIR", os.path.dirname(os.path.dirname(layerconf)))
|
data.setVar("TOPDIR", os.path.dirname(os.path.dirname(layerconf)))
|
||||||
data = parse_config_file(layerconf, data)
|
data = parse_config_file(layerconf, data)
|
||||||
|
|
||||||
if not data.getVar("BB_CACHEDIR"):
|
|
||||||
data.setVar("BB_CACHEDIR", "${TOPDIR}/cache")
|
|
||||||
|
|
||||||
bb.codeparser.parser_cache_init(data.getVar("BB_CACHEDIR"))
|
|
||||||
|
|
||||||
layers = (data.getVar('BBLAYERS') or "").split()
|
layers = (data.getVar('BBLAYERS') or "").split()
|
||||||
broken_layers = []
|
broken_layers = []
|
||||||
|
|
||||||
if not layers:
|
|
||||||
bb.fatal("The bblayers.conf file doesn't contain any BBLAYERS definition")
|
|
||||||
|
|
||||||
data = bb.data.createCopy(data)
|
data = bb.data.createCopy(data)
|
||||||
approved = bb.utils.approved_variables()
|
approved = bb.utils.approved_variables()
|
||||||
|
|
||||||
@@ -382,10 +357,8 @@ class CookerDataBuilder(object):
|
|||||||
parselog.critical("Please check BBLAYERS in %s" % (layerconf))
|
parselog.critical("Please check BBLAYERS in %s" % (layerconf))
|
||||||
raise bb.BBHandledException()
|
raise bb.BBHandledException()
|
||||||
|
|
||||||
layerseries = None
|
|
||||||
compat_entries = {}
|
|
||||||
for layer in layers:
|
for layer in layers:
|
||||||
parselog.debug2("Adding layer %s", layer)
|
parselog.debug(2, "Adding layer %s", layer)
|
||||||
if 'HOME' in approved and '~' in layer:
|
if 'HOME' in approved and '~' in layer:
|
||||||
layer = os.path.expanduser(layer)
|
layer = os.path.expanduser(layer)
|
||||||
if layer.endswith('/'):
|
if layer.endswith('/'):
|
||||||
@@ -396,27 +369,8 @@ class CookerDataBuilder(object):
|
|||||||
data.expandVarref('LAYERDIR')
|
data.expandVarref('LAYERDIR')
|
||||||
data.expandVarref('LAYERDIR_RE')
|
data.expandVarref('LAYERDIR_RE')
|
||||||
|
|
||||||
# Sadly we can't have nice things.
|
|
||||||
# Some layers think they're going to be 'clever' and copy the values from
|
|
||||||
# another layer, e.g. using ${LAYERSERIES_COMPAT_core}. The whole point of
|
|
||||||
# this mechanism is to make it clear which releases a layer supports and
|
|
||||||
# show when a layer master branch is bitrotting and is unmaintained.
|
|
||||||
# We therefore avoid people doing this here.
|
|
||||||
collections = (data.getVar('BBFILE_COLLECTIONS') or "").split()
|
|
||||||
for c in collections:
|
|
||||||
compat_entry = data.getVar("LAYERSERIES_COMPAT_%s" % c)
|
|
||||||
if compat_entry:
|
|
||||||
compat_entries[c] = set(compat_entry.split())
|
|
||||||
data.delVar("LAYERSERIES_COMPAT_%s" % c)
|
|
||||||
if not layerseries:
|
|
||||||
layerseries = set((data.getVar("LAYERSERIES_CORENAMES") or "").split())
|
|
||||||
if layerseries:
|
|
||||||
data.delVar("LAYERSERIES_CORENAMES")
|
|
||||||
|
|
||||||
data.delVar('LAYERDIR_RE')
|
data.delVar('LAYERDIR_RE')
|
||||||
data.delVar('LAYERDIR')
|
data.delVar('LAYERDIR')
|
||||||
for c in compat_entries:
|
|
||||||
data.setVar("LAYERSERIES_COMPAT_%s" % c, " ".join(sorted(compat_entries[c])))
|
|
||||||
|
|
||||||
bbfiles_dynamic = (data.getVar('BBFILES_DYNAMIC') or "").split()
|
bbfiles_dynamic = (data.getVar('BBFILES_DYNAMIC') or "").split()
|
||||||
collections = (data.getVar('BBFILE_COLLECTIONS') or "").split()
|
collections = (data.getVar('BBFILE_COLLECTIONS') or "").split()
|
||||||
@@ -435,38 +389,26 @@ class CookerDataBuilder(object):
|
|||||||
if invalid:
|
if invalid:
|
||||||
bb.fatal("BBFILES_DYNAMIC entries must be of the form {!}<collection name>:<filename pattern>, not:\n %s" % "\n ".join(invalid))
|
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[:]
|
collections_tmp = collections[:]
|
||||||
for c in collections:
|
for c in collections:
|
||||||
collections_tmp.remove(c)
|
collections_tmp.remove(c)
|
||||||
if c in collections_tmp:
|
if c in collections_tmp:
|
||||||
bb.fatal("Found duplicated BBFILE_COLLECTIONS '%s', check bblayers.conf or layer.conf to fix it." % c)
|
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())
|
||||||
compat = set()
|
|
||||||
if c in compat_entries:
|
|
||||||
compat = compat_entries[c]
|
|
||||||
if compat and not layerseries:
|
|
||||||
bb.fatal("No core layer found to work with layer '%s'. Missing entry in bblayers.conf?" % c)
|
|
||||||
if compat and not (compat & layerseries):
|
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)"
|
bb.fatal("Layer %s is not compatible with the core layer which only supports these series: %s (layer is compatible with %s)"
|
||||||
% (c, " ".join(layerseries), " ".join(compat)))
|
% (c, " ".join(layerseries), " ".join(compat)))
|
||||||
elif not compat and not data.getVar("BB_WORKERCONTEXT"):
|
elif not compat and not data.getVar("BB_WORKERCONTEXT"):
|
||||||
bb.warn("Layer %s should set LAYERSERIES_COMPAT_%s in its conf/layer.conf file to list the core layer names it is compatible with." % (c, c))
|
bb.warn("Layer %s should set LAYERSERIES_COMPAT_%s in its conf/layer.conf file to list the core layer names it is compatible with." % (c, c))
|
||||||
|
|
||||||
data.setVar("LAYERSERIES_CORENAMES", " ".join(sorted(layerseries)))
|
|
||||||
|
|
||||||
if not data.getVar("BBPATH"):
|
if not data.getVar("BBPATH"):
|
||||||
msg = "The BBPATH variable is not set"
|
msg = "The BBPATH variable is not set"
|
||||||
if not layerconf:
|
if not layerconf:
|
||||||
msg += (" and bitbake did not find a conf/bblayers.conf file in"
|
msg += (" and bitbake did not find a conf/bblayers.conf file in"
|
||||||
" the expected location.\nMaybe you accidentally"
|
" the expected location.\nMaybe you accidentally"
|
||||||
" invoked bitbake from the wrong directory?")
|
" invoked bitbake from the wrong directory?")
|
||||||
bb.fatal(msg)
|
raise SystemExit(msg)
|
||||||
|
|
||||||
if not data.getVar("TOPDIR"):
|
|
||||||
data.setVar("TOPDIR", os.path.abspath(os.getcwd()))
|
|
||||||
if not data.getVar("BB_CACHEDIR"):
|
|
||||||
data.setVar("BB_CACHEDIR", "${TOPDIR}/cache")
|
|
||||||
bb.codeparser.parser_cache_init(data.getVar("BB_CACHEDIR"))
|
|
||||||
|
|
||||||
data = parse_config_file(os.path.join("conf", "bitbake.conf"), data)
|
data = parse_config_file(os.path.join("conf", "bitbake.conf"), data)
|
||||||
|
|
||||||
@@ -479,7 +421,7 @@ class CookerDataBuilder(object):
|
|||||||
for bbclass in bbclasses:
|
for bbclass in bbclasses:
|
||||||
data = _inherit(bbclass, data)
|
data = _inherit(bbclass, data)
|
||||||
|
|
||||||
# Normally we only register event handlers at the end of parsing .bb files
|
# Nomally we only register event handlers at the end of parsing .bb files
|
||||||
# We register any handlers we've found so far here...
|
# We register any handlers we've found so far here...
|
||||||
for var in data.getVar('__BBHANDLERS', False) or []:
|
for var in data.getVar('__BBHANDLERS', False) or []:
|
||||||
handlerfn = data.getVarFlag(var, "filename", False)
|
handlerfn = data.getVarFlag(var, "filename", False)
|
||||||
@@ -487,60 +429,9 @@ class CookerDataBuilder(object):
|
|||||||
parselog.critical("Undefined event handler function '%s'" % var)
|
parselog.critical("Undefined event handler function '%s'" % var)
|
||||||
raise bb.BBHandledException()
|
raise bb.BBHandledException()
|
||||||
handlerln = int(data.getVarFlag(var, "lineno", False))
|
handlerln = int(data.getVarFlag(var, "lineno", False))
|
||||||
bb.event.register(var, data.getVar(var, False), (data.getVarFlag(var, "eventmask") or "").split(), handlerfn, handlerln, data)
|
bb.event.register(var, data.getVar(var, False), (data.getVarFlag(var, "eventmask") or "").split(), handlerfn, handlerln)
|
||||||
|
|
||||||
data.setVar('BBINCLUDED',bb.parse.get_file_depends(data))
|
data.setVar('BBINCLUDED',bb.parse.get_file_depends(data))
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _parse_recipe(bb_data, bbfile, appends, mc, layername):
|
|
||||||
bb_data.setVar("__BBMULTICONFIG", mc)
|
|
||||||
bb_data.setVar("FILE_LAYERNAME", layername)
|
|
||||||
|
|
||||||
bbfile_loc = os.path.abspath(os.path.dirname(bbfile))
|
|
||||||
bb.parse.cached_mtime_noerror(bbfile_loc)
|
|
||||||
|
|
||||||
if appends:
|
|
||||||
bb_data.setVar('__BBAPPEND', " ".join(appends))
|
|
||||||
|
|
||||||
return bb.parse.handle(bbfile, bb_data)
|
|
||||||
|
|
||||||
def parseRecipeVariants(self, bbfile, appends, virtonly=False, mc=None, layername=None):
|
|
||||||
"""
|
|
||||||
Load and parse one .bb build file
|
|
||||||
Return the data and whether parsing resulted in the file being skipped
|
|
||||||
"""
|
|
||||||
|
|
||||||
if virtonly:
|
|
||||||
(bbfile, virtual, mc) = bb.cache.virtualfn2realfn(bbfile)
|
|
||||||
bb_data = self.mcdata[mc].createCopy()
|
|
||||||
bb_data.setVar("__ONLYFINALISE", virtual or "default")
|
|
||||||
return self._parse_recipe(bb_data, bbfile, appends, mc, layername)
|
|
||||||
|
|
||||||
if mc is not None:
|
|
||||||
bb_data = self.mcdata[mc].createCopy()
|
|
||||||
return self._parse_recipe(bb_data, bbfile, appends, mc, layername)
|
|
||||||
|
|
||||||
bb_data = self.data.createCopy()
|
|
||||||
datastores = self._parse_recipe(bb_data, bbfile, appends, '', layername)
|
|
||||||
|
|
||||||
for mc in self.mcdata:
|
|
||||||
if not mc:
|
|
||||||
continue
|
|
||||||
bb_data = self.mcdata[mc].createCopy()
|
|
||||||
newstores = self._parse_recipe(bb_data, bbfile, appends, mc, layername)
|
|
||||||
for ns in newstores:
|
|
||||||
datastores["mc:%s:%s" % (mc, ns)] = newstores[ns]
|
|
||||||
|
|
||||||
return datastores
|
|
||||||
|
|
||||||
def parseRecipe(self, virtualfn, appends, layername):
|
|
||||||
"""
|
|
||||||
Return a complete set of data for fn.
|
|
||||||
To do this, we need to parse the file.
|
|
||||||
"""
|
|
||||||
logger.debug("Parsing %s (full)" % virtualfn)
|
|
||||||
(fn, virtual, mc) = bb.cache.virtualfn2realfn(virtualfn)
|
|
||||||
datastores = self.parseRecipeVariants(virtualfn, appends, virtonly=True, layername=layername)
|
|
||||||
return datastores[virtual]
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#
|
#
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
#
|
#
|
||||||
|
|
||||||
@@ -76,26 +74,26 @@ def createDaemon(function, logfile):
|
|||||||
with open('/dev/null', 'r') as si:
|
with open('/dev/null', 'r') as si:
|
||||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||||
|
|
||||||
with open(logfile, 'a+') as so:
|
try:
|
||||||
try:
|
so = open(logfile, 'a+')
|
||||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||||
os.dup2(so.fileno(), sys.stderr.fileno())
|
os.dup2(so.fileno(), sys.stderr.fileno())
|
||||||
except io.UnsupportedOperation:
|
except io.UnsupportedOperation:
|
||||||
sys.stdout = so
|
sys.stdout = open(logfile, 'a+')
|
||||||
|
|
||||||
# Have stdout and stderr be the same so log output matches chronologically
|
# Have stdout and stderr be the same so log output matches chronologically
|
||||||
# and there aren't two separate buffers
|
# and there aren't two seperate buffers
|
||||||
sys.stderr = sys.stdout
|
sys.stderr = sys.stdout
|
||||||
|
|
||||||
try:
|
try:
|
||||||
function()
|
function()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
finally:
|
finally:
|
||||||
bb.event.print_ui_queue()
|
bb.event.print_ui_queue()
|
||||||
# os._exit() doesn't flush open files like os.exit() does. Manually flush
|
# os._exit() doesn't flush open files like os.exit() does. Manually flush
|
||||||
# stdout and stderr so that any logging output will be seen, particularly
|
# stdout and stderr so that any logging output will be seen, particularly
|
||||||
# exception tracebacks.
|
# exception tracebacks.
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
|
|||||||
@@ -4,16 +4,14 @@ BitBake 'Data' implementations
|
|||||||
Functions for interacting with the data structure used by the
|
Functions for interacting with the data structure used by the
|
||||||
BitBake build tools.
|
BitBake build tools.
|
||||||
|
|
||||||
expandKeys and datastore iteration are the most expensive
|
The expandKeys and update_data are the most expensive
|
||||||
operations. Updating overrides is now "on the fly" but still based
|
operations. At night the cookie monster came by and
|
||||||
on the idea of the cookie monster introduced by zecke:
|
|
||||||
"At night the cookie monster came by and
|
|
||||||
suggested 'give me cookies on setting the variables and
|
suggested 'give me cookies on setting the variables and
|
||||||
things will work out'. Taking this suggestion into account
|
things will work out'. Taking this suggestion into account
|
||||||
applying the skills from the not yet passed 'Entwurf und
|
applying the skills from the not yet passed 'Entwurf und
|
||||||
Analyse von Algorithmen' lecture and the cookie
|
Analyse von Algorithmen' lecture and the cookie
|
||||||
monster seems to be right. We will track setVar more carefully
|
monster seems to be right. We will track setVar more carefully
|
||||||
to have faster datastore operations."
|
to have faster update_data and expandKeys operations.
|
||||||
|
|
||||||
This is a trade-off between speed and memory again but
|
This is a trade-off between speed and memory again but
|
||||||
the speed is more critical here.
|
the speed is more critical here.
|
||||||
@@ -28,6 +26,11 @@ the speed is more critical here.
|
|||||||
|
|
||||||
import sys, os, re
|
import sys, os, re
|
||||||
import hashlib
|
import hashlib
|
||||||
|
if sys.argv[0][-5:] == "pydoc":
|
||||||
|
path = os.path.dirname(os.path.dirname(sys.argv[1]))
|
||||||
|
else:
|
||||||
|
path = os.path.dirname(os.path.dirname(sys.argv[0]))
|
||||||
|
sys.path.insert(0, path)
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
|
||||||
from bb import data_smart
|
from bb import data_smart
|
||||||
@@ -67,6 +70,10 @@ def keys(d):
|
|||||||
"""Return a list of keys in d"""
|
"""Return a list of keys in d"""
|
||||||
return d.keys()
|
return d.keys()
|
||||||
|
|
||||||
|
|
||||||
|
__expand_var_regexp__ = re.compile(r"\${[^{}]+}")
|
||||||
|
__expand_python_regexp__ = re.compile(r"\${@.+?}")
|
||||||
|
|
||||||
def expand(s, d, varname = None):
|
def expand(s, d, varname = None):
|
||||||
"""Variable expansion using the data store"""
|
"""Variable expansion using the data store"""
|
||||||
return d.expand(s, varname)
|
return d.expand(s, varname)
|
||||||
@@ -114,8 +121,8 @@ def emit_var(var, o=sys.__stdout__, d = init(), all=False):
|
|||||||
if d.getVarFlag(var, 'python', False) and func:
|
if d.getVarFlag(var, 'python', False) and func:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
export = bb.utils.to_boolean(d.getVarFlag(var, "export"))
|
export = d.getVarFlag(var, "export", False)
|
||||||
unexport = bb.utils.to_boolean(d.getVarFlag(var, "unexport"))
|
unexport = d.getVarFlag(var, "unexport", False)
|
||||||
if not all and not export and not unexport and not func:
|
if not all and not export and not unexport and not func:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -188,8 +195,8 @@ def emit_env(o=sys.__stdout__, d = init(), all=False):
|
|||||||
|
|
||||||
def exported_keys(d):
|
def exported_keys(d):
|
||||||
return (key for key in d.keys() if not key.startswith('__') and
|
return (key for key in d.keys() if not key.startswith('__') and
|
||||||
bb.utils.to_boolean(d.getVarFlag(key, 'export')) and
|
d.getVarFlag(key, 'export', False) and
|
||||||
not bb.utils.to_boolean(d.getVarFlag(key, 'unexport')))
|
not d.getVarFlag(key, 'unexport', False))
|
||||||
|
|
||||||
def exported_vars(d):
|
def exported_vars(d):
|
||||||
k = list(exported_keys(d))
|
k = list(exported_keys(d))
|
||||||
@@ -219,7 +226,7 @@ def emit_func(func, o=sys.__stdout__, d = init()):
|
|||||||
deps = newdeps
|
deps = newdeps
|
||||||
seen |= deps
|
seen |= deps
|
||||||
newdeps = set()
|
newdeps = set()
|
||||||
for dep in sorted(deps):
|
for dep in deps:
|
||||||
if d.getVarFlag(dep, "func", False) and not d.getVarFlag(dep, "python", False):
|
if d.getVarFlag(dep, "func", False) and not d.getVarFlag(dep, "python", False):
|
||||||
emit_var(dep, o, d, False) and o.write('\n')
|
emit_var(dep, o, d, False) and o.write('\n')
|
||||||
newdeps |= bb.codeparser.ShellParser(dep, logger).parse_shell(d.getVar(dep))
|
newdeps |= bb.codeparser.ShellParser(dep, logger).parse_shell(d.getVar(dep))
|
||||||
@@ -261,72 +268,65 @@ def emit_func_python(func, o=sys.__stdout__, d = init()):
|
|||||||
newdeps |= set((d.getVarFlag(dep, "vardeps") or "").split())
|
newdeps |= set((d.getVarFlag(dep, "vardeps") or "").split())
|
||||||
newdeps -= seen
|
newdeps -= seen
|
||||||
|
|
||||||
def build_dependencies(key, keys, mod_funcs, shelldeps, varflagsexcl, ignored_vars, d, codeparsedata):
|
def update_data(d):
|
||||||
def handle_contains(value, contains, exclusions, d):
|
"""Performs final steps upon the datastore, including application of overrides"""
|
||||||
newvalue = []
|
d.finalize(parent = True)
|
||||||
if value:
|
|
||||||
newvalue.append(str(value))
|
|
||||||
for k in sorted(contains):
|
|
||||||
if k in exclusions or k in ignored_vars:
|
|
||||||
continue
|
|
||||||
l = (d.getVar(k) or "").split()
|
|
||||||
for item in sorted(contains[k]):
|
|
||||||
for word in item.split():
|
|
||||||
if not word in l:
|
|
||||||
newvalue.append("\n%s{%s} = Unset" % (k, item))
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
newvalue.append("\n%s{%s} = Set" % (k, item))
|
|
||||||
return "".join(newvalue)
|
|
||||||
|
|
||||||
def handle_remove(value, deps, removes, d):
|
|
||||||
for r in sorted(removes):
|
|
||||||
r2 = d.expandWithRefs(r, None)
|
|
||||||
value += "\n_remove of %s" % r
|
|
||||||
deps |= r2.references
|
|
||||||
deps = deps | (keys & r2.execs)
|
|
||||||
value = handle_contains(value, r2.contains, exclusions, d)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
def build_dependencies(key, keys, shelldeps, varflagsexcl, d):
|
||||||
deps = set()
|
deps = set()
|
||||||
try:
|
try:
|
||||||
if key in mod_funcs:
|
|
||||||
exclusions = set()
|
|
||||||
moddep = bb.codeparser.modulecode_deps[key]
|
|
||||||
value = handle_contains(moddep[4], moddep[3], exclusions, d)
|
|
||||||
return frozenset((moddep[0] | keys & moddep[1]) - ignored_vars), value
|
|
||||||
|
|
||||||
if key[-1] == ']':
|
if key[-1] == ']':
|
||||||
vf = key[:-1].split('[')
|
vf = key[:-1].split('[')
|
||||||
if vf[1] == "vardepvalueexclude":
|
|
||||||
return deps, ""
|
|
||||||
value, parser = d.getVarFlag(vf[0], vf[1], False, retparser=True)
|
value, parser = d.getVarFlag(vf[0], vf[1], False, retparser=True)
|
||||||
deps |= parser.references
|
deps |= parser.references
|
||||||
deps = deps | (keys & parser.execs)
|
deps = deps | (keys & parser.execs)
|
||||||
deps -= ignored_vars
|
return deps, value
|
||||||
return frozenset(deps), value
|
|
||||||
varflags = d.getVarFlags(key, ["vardeps", "vardepvalue", "vardepsexclude", "exports", "postfuncs", "prefuncs", "lineno", "filename"]) or {}
|
varflags = d.getVarFlags(key, ["vardeps", "vardepvalue", "vardepsexclude", "exports", "postfuncs", "prefuncs", "lineno", "filename"]) or {}
|
||||||
vardeps = varflags.get("vardeps")
|
vardeps = varflags.get("vardeps")
|
||||||
exclusions = varflags.get("vardepsexclude", "").split()
|
|
||||||
|
def handle_contains(value, contains, d):
|
||||||
|
newvalue = ""
|
||||||
|
for k in sorted(contains):
|
||||||
|
l = (d.getVar(k) or "").split()
|
||||||
|
for item in sorted(contains[k]):
|
||||||
|
for word in item.split():
|
||||||
|
if not word in l:
|
||||||
|
newvalue += "\n%s{%s} = Unset" % (k, item)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
newvalue += "\n%s{%s} = Set" % (k, item)
|
||||||
|
if not newvalue:
|
||||||
|
return value
|
||||||
|
if not value:
|
||||||
|
return newvalue
|
||||||
|
return value + newvalue
|
||||||
|
|
||||||
|
def handle_remove(value, deps, removes, d):
|
||||||
|
for r in sorted(removes):
|
||||||
|
r2 = d.expandWithRefs(r, None)
|
||||||
|
value += "\n_remove of %s" % r
|
||||||
|
deps |= r2.references
|
||||||
|
deps = deps | (keys & r2.execs)
|
||||||
|
return value
|
||||||
|
|
||||||
if "vardepvalue" in varflags:
|
if "vardepvalue" in varflags:
|
||||||
value = varflags.get("vardepvalue")
|
value = varflags.get("vardepvalue")
|
||||||
elif varflags.get("func"):
|
elif varflags.get("func"):
|
||||||
if varflags.get("python"):
|
if varflags.get("python"):
|
||||||
value = codeparsedata.getVarFlag(key, "_content", False)
|
value = d.getVarFlag(key, "_content", False)
|
||||||
parser = bb.codeparser.PythonParser(key, logger)
|
parser = bb.codeparser.PythonParser(key, logger)
|
||||||
parser.parse_python(value, filename=varflags.get("filename"), lineno=varflags.get("lineno"))
|
parser.parse_python(value, filename=varflags.get("filename"), lineno=varflags.get("lineno"))
|
||||||
deps = deps | parser.references
|
deps = deps | parser.references
|
||||||
deps = deps | (keys & parser.execs)
|
deps = deps | (keys & parser.execs)
|
||||||
value = handle_contains(value, parser.contains, exclusions, d)
|
value = handle_contains(value, parser.contains, d)
|
||||||
else:
|
else:
|
||||||
value, parsedvar = codeparsedata.getVarFlag(key, "_content", False, retparser=True)
|
value, parsedvar = d.getVarFlag(key, "_content", False, retparser=True)
|
||||||
parser = bb.codeparser.ShellParser(key, logger)
|
parser = bb.codeparser.ShellParser(key, logger)
|
||||||
parser.parse_shell(parsedvar.value)
|
parser.parse_shell(parsedvar.value)
|
||||||
deps = deps | shelldeps
|
deps = deps | shelldeps
|
||||||
deps = deps | parsedvar.references
|
deps = deps | parsedvar.references
|
||||||
deps = deps | (keys & parser.execs) | (keys & parsedvar.execs)
|
deps = deps | (keys & parser.execs) | (keys & parsedvar.execs)
|
||||||
value = handle_contains(value, parsedvar.contains, exclusions, d)
|
value = handle_contains(value, parsedvar.contains, d)
|
||||||
if hasattr(parsedvar, "removes"):
|
if hasattr(parsedvar, "removes"):
|
||||||
value = handle_remove(value, deps, parsedvar.removes, d)
|
value = handle_remove(value, deps, parsedvar.removes, d)
|
||||||
if vardeps is None:
|
if vardeps is None:
|
||||||
@@ -341,7 +341,7 @@ def build_dependencies(key, keys, mod_funcs, shelldeps, varflagsexcl, ignored_va
|
|||||||
value, parser = d.getVarFlag(key, "_content", False, retparser=True)
|
value, parser = d.getVarFlag(key, "_content", False, retparser=True)
|
||||||
deps |= parser.references
|
deps |= parser.references
|
||||||
deps = deps | (keys & parser.execs)
|
deps = deps | (keys & parser.execs)
|
||||||
value = handle_contains(value, parser.contains, exclusions, d)
|
value = handle_contains(value, parser.contains, d)
|
||||||
if hasattr(parser, "removes"):
|
if hasattr(parser, "removes"):
|
||||||
value = handle_remove(value, deps, parser.removes, d)
|
value = handle_remove(value, deps, parser.removes, d)
|
||||||
|
|
||||||
@@ -361,50 +361,43 @@ def build_dependencies(key, keys, mod_funcs, shelldeps, varflagsexcl, ignored_va
|
|||||||
deps |= set(varfdeps)
|
deps |= set(varfdeps)
|
||||||
|
|
||||||
deps |= set((vardeps or "").split())
|
deps |= set((vardeps or "").split())
|
||||||
deps -= set(exclusions)
|
deps -= set(varflags.get("vardepsexclude", "").split())
|
||||||
deps -= ignored_vars
|
|
||||||
except bb.parse.SkipRecipe:
|
except bb.parse.SkipRecipe:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
bb.warn("Exception during build_dependencies for %s" % key)
|
bb.warn("Exception during build_dependencies for %s" % key)
|
||||||
raise
|
raise
|
||||||
return frozenset(deps), value
|
return deps, value
|
||||||
#bb.note("Variable %s references %s and calls %s" % (key, str(deps), str(execs)))
|
#bb.note("Variable %s references %s and calls %s" % (key, str(deps), str(execs)))
|
||||||
#d.setVarFlag(key, "vardeps", deps)
|
#d.setVarFlag(key, "vardeps", deps)
|
||||||
|
|
||||||
def generate_dependencies(d, ignored_vars):
|
def generate_dependencies(d, whitelist):
|
||||||
|
|
||||||
mod_funcs = set(bb.codeparser.modulecode_deps.keys())
|
keys = set(key for key in d if not key.startswith("__"))
|
||||||
keys = set(key for key in d if not key.startswith("__")) | mod_funcs
|
shelldeps = set(key for key in d.getVar("__exportlist", False) if d.getVarFlag(key, "export", False) and not d.getVarFlag(key, "unexport", False))
|
||||||
shelldeps = set(key for key in d.getVar("__exportlist", False) if bb.utils.to_boolean(d.getVarFlag(key, "export")) and not bb.utils.to_boolean(d.getVarFlag(key, "unexport")))
|
|
||||||
varflagsexcl = d.getVar('BB_SIGNATURE_EXCLUDE_FLAGS')
|
varflagsexcl = d.getVar('BB_SIGNATURE_EXCLUDE_FLAGS')
|
||||||
|
|
||||||
codeparserd = d.createCopy()
|
|
||||||
for forced in (d.getVar('BB_HASH_CODEPARSER_VALS') or "").split():
|
|
||||||
key, value = forced.split("=", 1)
|
|
||||||
codeparserd.setVar(key, value)
|
|
||||||
|
|
||||||
deps = {}
|
deps = {}
|
||||||
values = {}
|
values = {}
|
||||||
|
|
||||||
tasklist = d.getVar('__BBTASKS', False) or []
|
tasklist = d.getVar('__BBTASKS', False) or []
|
||||||
for task in tasklist:
|
for task in tasklist:
|
||||||
deps[task], values[task] = build_dependencies(task, keys, mod_funcs, shelldeps, varflagsexcl, ignored_vars, d, codeparserd)
|
deps[task], values[task] = build_dependencies(task, keys, shelldeps, varflagsexcl, d)
|
||||||
newdeps = deps[task]
|
newdeps = deps[task]
|
||||||
seen = set()
|
seen = set()
|
||||||
while newdeps:
|
while newdeps:
|
||||||
nextdeps = newdeps
|
nextdeps = newdeps - whitelist
|
||||||
seen |= nextdeps
|
seen |= nextdeps
|
||||||
newdeps = set()
|
newdeps = set()
|
||||||
for dep in nextdeps:
|
for dep in nextdeps:
|
||||||
if dep not in deps:
|
if dep not in deps:
|
||||||
deps[dep], values[dep] = build_dependencies(dep, keys, mod_funcs, shelldeps, varflagsexcl, ignored_vars, d, codeparserd)
|
deps[dep], values[dep] = build_dependencies(dep, keys, shelldeps, varflagsexcl, d)
|
||||||
newdeps |= deps[dep]
|
newdeps |= deps[dep]
|
||||||
newdeps -= seen
|
newdeps -= seen
|
||||||
#print "For %s: %s" % (task, str(deps[task]))
|
#print "For %s: %s" % (task, str(deps[task]))
|
||||||
return tasklist, deps, values
|
return tasklist, deps, values
|
||||||
|
|
||||||
def generate_dependency_hash(tasklist, gendeps, lookupcache, ignored_vars, fn):
|
def generate_dependency_hash(tasklist, gendeps, lookupcache, whitelist, fn):
|
||||||
taskdeps = {}
|
taskdeps = {}
|
||||||
basehash = {}
|
basehash = {}
|
||||||
|
|
||||||
@@ -413,10 +406,9 @@ def generate_dependency_hash(tasklist, gendeps, lookupcache, ignored_vars, fn):
|
|||||||
|
|
||||||
if data is None:
|
if data is None:
|
||||||
bb.error("Task %s from %s seems to be empty?!" % (task, fn))
|
bb.error("Task %s from %s seems to be empty?!" % (task, fn))
|
||||||
data = []
|
data = ''
|
||||||
else:
|
|
||||||
data = [data]
|
|
||||||
|
|
||||||
|
gendeps[task] -= whitelist
|
||||||
newdeps = gendeps[task]
|
newdeps = gendeps[task]
|
||||||
seen = set()
|
seen = set()
|
||||||
while newdeps:
|
while newdeps:
|
||||||
@@ -424,24 +416,27 @@ def generate_dependency_hash(tasklist, gendeps, lookupcache, ignored_vars, fn):
|
|||||||
seen |= nextdeps
|
seen |= nextdeps
|
||||||
newdeps = set()
|
newdeps = set()
|
||||||
for dep in nextdeps:
|
for dep in nextdeps:
|
||||||
|
if dep in whitelist:
|
||||||
|
continue
|
||||||
|
gendeps[dep] -= whitelist
|
||||||
newdeps |= gendeps[dep]
|
newdeps |= gendeps[dep]
|
||||||
newdeps -= seen
|
newdeps -= seen
|
||||||
|
|
||||||
alldeps = sorted(seen)
|
alldeps = sorted(seen)
|
||||||
for dep in alldeps:
|
for dep in alldeps:
|
||||||
data.append(dep)
|
data = data + dep
|
||||||
var = lookupcache[dep]
|
var = lookupcache[dep]
|
||||||
if var is not None:
|
if var is not None:
|
||||||
data.append(str(var))
|
data = data + str(var)
|
||||||
k = fn + ":" + task
|
k = fn + ":" + task
|
||||||
basehash[k] = hashlib.sha256("".join(data).encode("utf-8")).hexdigest()
|
basehash[k] = hashlib.sha256(data.encode("utf-8")).hexdigest()
|
||||||
taskdeps[task] = frozenset(seen)
|
taskdeps[task] = alldeps
|
||||||
|
|
||||||
return taskdeps, basehash
|
return taskdeps, basehash
|
||||||
|
|
||||||
def inherits_class(klass, d):
|
def inherits_class(klass, d):
|
||||||
val = d.getVar('__inherit_cache', False) or []
|
val = d.getVar('__inherit_cache', False) or []
|
||||||
needle = '/%s.bbclass' % klass
|
needle = os.path.join('classes', '%s.bbclass' % klass)
|
||||||
for v in val:
|
for v in val:
|
||||||
if v.endswith(needle):
|
if v.endswith(needle):
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -16,11 +16,8 @@ BitBake build tools.
|
|||||||
#
|
#
|
||||||
# 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 builtins
|
import copy, re, sys, traceback
|
||||||
import copy
|
from collections import MutableMapping
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from collections.abc import MutableMapping
|
|
||||||
import logging
|
import logging
|
||||||
import hashlib
|
import hashlib
|
||||||
import bb, bb.codeparser
|
import bb, bb.codeparser
|
||||||
@@ -29,25 +26,13 @@ from bb.COW import COWDictBase
|
|||||||
|
|
||||||
logger = logging.getLogger("BitBake.Data")
|
logger = logging.getLogger("BitBake.Data")
|
||||||
|
|
||||||
__setvar_keyword__ = [":append", ":prepend", ":remove"]
|
__setvar_keyword__ = ["_append", "_prepend", "_remove"]
|
||||||
__setvar_regexp__ = re.compile(r'(?P<base>.*?)(?P<keyword>:append|:prepend|:remove)(:(?P<add>[^A-Z]*))?$')
|
__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_var_regexp__ = re.compile(r"\${[a-zA-Z0-9\-_+./~]+?}")
|
||||||
__expand_python_regexp__ = re.compile(r"\${@(?:{.*?}|.)+?}")
|
__expand_python_regexp__ = re.compile(r"\${@.+?}")
|
||||||
__whitespace_split__ = re.compile(r'(\s)')
|
__whitespace_split__ = re.compile(r'(\s)')
|
||||||
__override_regexp__ = re.compile(r'[a-z0-9]+')
|
__override_regexp__ = re.compile(r'[a-z0-9]+')
|
||||||
|
|
||||||
bitbake_renamed_vars = {
|
|
||||||
"BB_ENV_WHITELIST": "BB_ENV_PASSTHROUGH",
|
|
||||||
"BB_ENV_EXTRAWHITE": "BB_ENV_PASSTHROUGH_ADDITIONS",
|
|
||||||
"BB_HASHBASE_WHITELIST": "BB_BASEHASH_IGNORE_VARS",
|
|
||||||
"BB_HASHCONFIG_WHITELIST": "BB_HASHCONFIG_IGNORE_VARS",
|
|
||||||
"BB_HASHTASK_WHITELIST": "BB_TASKHASH_IGNORE_TASKS",
|
|
||||||
"BB_SETSCENE_ENFORCE_WHITELIST": "BB_SETSCENE_ENFORCE_IGNORE_TASKS",
|
|
||||||
"MULTI_PROVIDER_WHITELIST": "BB_MULTI_PROVIDER_ALLOWED",
|
|
||||||
"BB_STAMP_WHITELIST": "is a deprecated variable and support has been removed",
|
|
||||||
"BB_STAMP_POLICY": "is a deprecated variable and support has been removed",
|
|
||||||
}
|
|
||||||
|
|
||||||
def infer_caller_details(loginfo, parent = False, varval = True):
|
def infer_caller_details(loginfo, parent = False, varval = True):
|
||||||
"""Save the caller the trouble of specifying everything."""
|
"""Save the caller the trouble of specifying everything."""
|
||||||
# Save effort.
|
# Save effort.
|
||||||
@@ -95,11 +80,10 @@ def infer_caller_details(loginfo, parent = False, varval = True):
|
|||||||
loginfo['func'] = func
|
loginfo['func'] = func
|
||||||
|
|
||||||
class VariableParse:
|
class VariableParse:
|
||||||
def __init__(self, varname, d, unexpanded_value = None, val = None):
|
def __init__(self, varname, d, val = None):
|
||||||
self.varname = varname
|
self.varname = varname
|
||||||
self.d = d
|
self.d = d
|
||||||
self.value = val
|
self.value = val
|
||||||
self.unexpanded_value = unexpanded_value
|
|
||||||
|
|
||||||
self.references = set()
|
self.references = set()
|
||||||
self.execs = set()
|
self.execs = set()
|
||||||
@@ -123,11 +107,6 @@ class VariableParse:
|
|||||||
else:
|
else:
|
||||||
code = match.group()[3:-1]
|
code = match.group()[3:-1]
|
||||||
|
|
||||||
# Do not run code that contains one or more unexpanded variables
|
|
||||||
# instead return the code with the characters we removed put back
|
|
||||||
if __expand_var_regexp__.findall(code):
|
|
||||||
return "${@" + code + "}"
|
|
||||||
|
|
||||||
if self.varname:
|
if self.varname:
|
||||||
varname = 'Var <%s>' % self.varname
|
varname = 'Var <%s>' % self.varname
|
||||||
else:
|
else:
|
||||||
@@ -153,21 +132,16 @@ class VariableParse:
|
|||||||
value = utils.better_eval(codeobj, DataContext(self.d), {'d' : self.d})
|
value = utils.better_eval(codeobj, DataContext(self.d), {'d' : self.d})
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
class DataContext(dict):
|
|
||||||
excluded = set([i for i in dir(builtins) if not i.startswith('_')] + ['oe'])
|
|
||||||
|
|
||||||
|
class DataContext(dict):
|
||||||
def __init__(self, metadata, **kwargs):
|
def __init__(self, metadata, **kwargs):
|
||||||
self.metadata = metadata
|
self.metadata = metadata
|
||||||
dict.__init__(self, **kwargs)
|
dict.__init__(self, **kwargs)
|
||||||
self['d'] = metadata
|
self['d'] = metadata
|
||||||
self.context = set(bb.utils.get_context())
|
|
||||||
|
|
||||||
def __missing__(self, key):
|
def __missing__(self, key):
|
||||||
if key in self.excluded or key in self.context:
|
|
||||||
raise KeyError(key)
|
|
||||||
|
|
||||||
value = self.metadata.getVar(key)
|
value = self.metadata.getVar(key)
|
||||||
if value is None:
|
if value is None or self.metadata.getVarFlag(key, 'func', False):
|
||||||
raise KeyError(key)
|
raise KeyError(key)
|
||||||
else:
|
else:
|
||||||
return value
|
return value
|
||||||
@@ -177,7 +151,6 @@ class ExpansionError(Exception):
|
|||||||
self.expression = expression
|
self.expression = expression
|
||||||
self.variablename = varname
|
self.variablename = varname
|
||||||
self.exception = exception
|
self.exception = exception
|
||||||
self.varlist = [varname or expression or ""]
|
|
||||||
if varname:
|
if varname:
|
||||||
if expression:
|
if expression:
|
||||||
self.msg = "Failure expanding variable %s, expression was %s which triggered exception %s: %s" % (varname, expression, type(exception).__name__, exception)
|
self.msg = "Failure expanding variable %s, expression was %s which triggered exception %s: %s" % (varname, expression, type(exception).__name__, exception)
|
||||||
@@ -187,14 +160,8 @@ class ExpansionError(Exception):
|
|||||||
self.msg = "Failure expanding expression %s which triggered exception %s: %s" % (expression, type(exception).__name__, exception)
|
self.msg = "Failure expanding expression %s which triggered exception %s: %s" % (expression, type(exception).__name__, exception)
|
||||||
Exception.__init__(self, self.msg)
|
Exception.__init__(self, self.msg)
|
||||||
self.args = (varname, expression, exception)
|
self.args = (varname, expression, exception)
|
||||||
|
|
||||||
def addVar(self, varname):
|
|
||||||
if varname:
|
|
||||||
self.varlist.append(varname)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
chain = "\nThe variable dependency chain for the failure is: " + " -> ".join(self.varlist)
|
return self.msg
|
||||||
return self.msg + chain
|
|
||||||
|
|
||||||
class IncludeHistory(object):
|
class IncludeHistory(object):
|
||||||
def __init__(self, parent = None, filename = '[TOP LEVEL]'):
|
def __init__(self, parent = None, filename = '[TOP LEVEL]'):
|
||||||
@@ -272,9 +239,12 @@ class VariableHistory(object):
|
|||||||
return
|
return
|
||||||
if 'op' not in loginfo or not loginfo['op']:
|
if 'op' not in loginfo or not loginfo['op']:
|
||||||
loginfo['op'] = 'set'
|
loginfo['op'] = 'set'
|
||||||
|
if 'detail' in loginfo:
|
||||||
|
loginfo['detail'] = str(loginfo['detail'])
|
||||||
if 'variable' not in loginfo or 'file' not in loginfo:
|
if 'variable' not in loginfo or 'file' not in loginfo:
|
||||||
raise ValueError("record() missing variable or file.")
|
raise ValueError("record() missing variable or file.")
|
||||||
var = loginfo['variable']
|
var = loginfo['variable']
|
||||||
|
|
||||||
if var not in self.variables:
|
if var not in self.variables:
|
||||||
self.variables[var] = []
|
self.variables[var] = []
|
||||||
if not isinstance(self.variables[var], list):
|
if not isinstance(self.variables[var], list):
|
||||||
@@ -307,7 +277,7 @@ class VariableHistory(object):
|
|||||||
for (r, override) in d.overridedata[var]:
|
for (r, override) in d.overridedata[var]:
|
||||||
for event in self.variable(r):
|
for event in self.variable(r):
|
||||||
loginfo = event.copy()
|
loginfo = event.copy()
|
||||||
if 'flag' in loginfo and not loginfo['flag'].startswith(("_", ":")):
|
if 'flag' in loginfo and not loginfo['flag'].startswith("_"):
|
||||||
continue
|
continue
|
||||||
loginfo['variable'] = var
|
loginfo['variable'] = var
|
||||||
loginfo['op'] = 'override[%s]:%s' % (override, loginfo['op'])
|
loginfo['op'] = 'override[%s]:%s' % (override, loginfo['op'])
|
||||||
@@ -333,8 +303,7 @@ class VariableHistory(object):
|
|||||||
flag = '[%s] ' % (event['flag'])
|
flag = '[%s] ' % (event['flag'])
|
||||||
else:
|
else:
|
||||||
flag = ''
|
flag = ''
|
||||||
o.write("# %s %s:%s%s\n# %s\"%s\"\n" % \
|
o.write("# %s %s:%s%s\n# %s\"%s\"\n" % (event['op'], event['file'], event['line'], display_func, flag, re.sub('\n', '\n# ', event['detail'])))
|
||||||
(event['op'], event['file'], event['line'], display_func, flag, re.sub('\n', '\n# ', str(event['detail']))))
|
|
||||||
if len(history) > 1:
|
if len(history) > 1:
|
||||||
o.write("# pre-expansion value:\n")
|
o.write("# pre-expansion value:\n")
|
||||||
o.write('# "%s"\n' % (commentVal))
|
o.write('# "%s"\n' % (commentVal))
|
||||||
@@ -360,16 +329,6 @@ class VariableHistory(object):
|
|||||||
lines.append(line)
|
lines.append(line)
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
def get_variable_refs(self, var):
|
|
||||||
"""Return a dict of file/line references"""
|
|
||||||
var_history = self.variable(var)
|
|
||||||
refs = {}
|
|
||||||
for event in var_history:
|
|
||||||
if event['file'] not in refs:
|
|
||||||
refs[event['file']] = []
|
|
||||||
refs[event['file']].append(event['line'])
|
|
||||||
return refs
|
|
||||||
|
|
||||||
def get_variable_items_files(self, var):
|
def get_variable_items_files(self, var):
|
||||||
"""
|
"""
|
||||||
Use variable history to map items added to a list variable and
|
Use variable history to map items added to a list variable and
|
||||||
@@ -383,12 +342,12 @@ class VariableHistory(object):
|
|||||||
for event in history:
|
for event in history:
|
||||||
if 'flag' in event:
|
if 'flag' in event:
|
||||||
continue
|
continue
|
||||||
if event['op'] == ':remove':
|
if event['op'] == '_remove':
|
||||||
continue
|
continue
|
||||||
if isset and event['op'] == 'set?':
|
if isset and event['op'] == 'set?':
|
||||||
continue
|
continue
|
||||||
isset = True
|
isset = True
|
||||||
items = d.expand(str(event['detail'])).split()
|
items = d.expand(event['detail']).split()
|
||||||
for item in items:
|
for item in items:
|
||||||
# This is a little crude but is belt-and-braces to avoid us
|
# This is a little crude but is belt-and-braces to avoid us
|
||||||
# having to handle every possible operation type specifically
|
# having to handle every possible operation type specifically
|
||||||
@@ -404,23 +363,6 @@ class VariableHistory(object):
|
|||||||
else:
|
else:
|
||||||
self.variables[var] = []
|
self.variables[var] = []
|
||||||
|
|
||||||
def _print_rename_error(var, loginfo, renamedvars, fullvar=None):
|
|
||||||
info = ""
|
|
||||||
if "file" in loginfo:
|
|
||||||
info = " file: %s" % loginfo["file"]
|
|
||||||
if "line" in loginfo:
|
|
||||||
info += " line: %s" % loginfo["line"]
|
|
||||||
if fullvar and fullvar != var:
|
|
||||||
info += " referenced as: %s" % fullvar
|
|
||||||
if info:
|
|
||||||
info = " (%s)" % info.strip()
|
|
||||||
renameinfo = renamedvars[var]
|
|
||||||
if " " in renameinfo:
|
|
||||||
# A space signals a string to display instead of a rename
|
|
||||||
bb.erroronce('Variable %s %s%s' % (var, renameinfo, info))
|
|
||||||
else:
|
|
||||||
bb.erroronce('Variable %s has been renamed to %s%s' % (var, renameinfo, info))
|
|
||||||
|
|
||||||
class DataSmart(MutableMapping):
|
class DataSmart(MutableMapping):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.dict = {}
|
self.dict = {}
|
||||||
@@ -428,8 +370,6 @@ class DataSmart(MutableMapping):
|
|||||||
self.inchistory = IncludeHistory()
|
self.inchistory = IncludeHistory()
|
||||||
self.varhistory = VariableHistory(self)
|
self.varhistory = VariableHistory(self)
|
||||||
self._tracking = False
|
self._tracking = False
|
||||||
self._var_renames = {}
|
|
||||||
self._var_renames.update(bitbake_renamed_vars)
|
|
||||||
|
|
||||||
self.expand_cache = {}
|
self.expand_cache = {}
|
||||||
|
|
||||||
@@ -451,9 +391,9 @@ class DataSmart(MutableMapping):
|
|||||||
def expandWithRefs(self, s, varname):
|
def expandWithRefs(self, s, varname):
|
||||||
|
|
||||||
if not isinstance(s, str): # sanity check
|
if not isinstance(s, str): # sanity check
|
||||||
return VariableParse(varname, self, s, s)
|
return VariableParse(varname, self, s)
|
||||||
|
|
||||||
varparse = VariableParse(varname, self, s)
|
varparse = VariableParse(varname, self)
|
||||||
|
|
||||||
while s.find('${') != -1:
|
while s.find('${') != -1:
|
||||||
olds = s
|
olds = s
|
||||||
@@ -463,17 +403,14 @@ class DataSmart(MutableMapping):
|
|||||||
s = __expand_python_regexp__.sub(varparse.python_sub, s)
|
s = __expand_python_regexp__.sub(varparse.python_sub, s)
|
||||||
except SyntaxError as e:
|
except SyntaxError as e:
|
||||||
# Likely unmatched brackets, just don't expand the expression
|
# Likely unmatched brackets, just don't expand the expression
|
||||||
if e.msg != "EOL while scanning string literal" and not e.msg.startswith("unterminated string literal"):
|
if e.msg != "EOL while scanning string literal":
|
||||||
raise
|
raise
|
||||||
if s == olds:
|
if s == olds:
|
||||||
break
|
break
|
||||||
except ExpansionError as e:
|
except ExpansionError:
|
||||||
e.addVar(varname)
|
|
||||||
raise
|
raise
|
||||||
except bb.parse.SkipRecipe:
|
except bb.parse.SkipRecipe:
|
||||||
raise
|
raise
|
||||||
except bb.BBHandledException:
|
|
||||||
raise
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
tb = sys.exc_info()[2]
|
tb = sys.exc_info()[2]
|
||||||
raise ExpansionError(varname, s, exc).with_traceback(tb) from exc
|
raise ExpansionError(varname, s, exc).with_traceback(tb) from exc
|
||||||
@@ -485,19 +422,24 @@ class DataSmart(MutableMapping):
|
|||||||
def expand(self, s, varname = None):
|
def expand(self, s, varname = None):
|
||||||
return self.expandWithRefs(s, varname).value
|
return self.expandWithRefs(s, varname).value
|
||||||
|
|
||||||
|
def finalize(self, parent = False):
|
||||||
|
return
|
||||||
|
|
||||||
|
def internal_finalize(self, parent = False):
|
||||||
|
"""Performs final steps upon the datastore, including application of overrides"""
|
||||||
|
self.overrides = None
|
||||||
|
|
||||||
def need_overrides(self):
|
def need_overrides(self):
|
||||||
if self.overrides is not None:
|
if self.overrides is not None:
|
||||||
return
|
return
|
||||||
if self.inoverride:
|
if self.inoverride:
|
||||||
return
|
return
|
||||||
overrride_stack = []
|
|
||||||
for count in range(5):
|
for count in range(5):
|
||||||
self.inoverride = True
|
self.inoverride = True
|
||||||
# Can end up here recursively so setup dummy values
|
# Can end up here recursively so setup dummy values
|
||||||
self.overrides = []
|
self.overrides = []
|
||||||
self.overridesset = set()
|
self.overridesset = set()
|
||||||
self.overrides = (self.getVar("OVERRIDES") or "").split(":") or []
|
self.overrides = (self.getVar("OVERRIDES") or "").split(":") or []
|
||||||
overrride_stack.append(self.overrides)
|
|
||||||
self.overridesset = set(self.overrides)
|
self.overridesset = set(self.overrides)
|
||||||
self.inoverride = False
|
self.inoverride = False
|
||||||
self.expand_cache = {}
|
self.expand_cache = {}
|
||||||
@@ -507,7 +449,7 @@ class DataSmart(MutableMapping):
|
|||||||
self.overrides = newoverrides
|
self.overrides = newoverrides
|
||||||
self.overridesset = set(self.overrides)
|
self.overridesset = set(self.overrides)
|
||||||
else:
|
else:
|
||||||
bb.fatal("Overrides could not be expanded into a stable state after 5 iterations, overrides must be being referenced by other overridden variables in some recursive fashion. Please provide your configuration to bitbake-devel so we can laugh, er, I mean try and understand how to make it work. The list of failing override expansions: %s" % "\n".join(str(s) for s in overrride_stack))
|
bb.fatal("Overrides could not be expanded into a stable state after 5 iterations, overrides must be being referenced by other overridden variables in some recursive fashion. Please provide your configuration to bitbake-devel so we can laugh, er, I mean try and understand how to make it work.")
|
||||||
|
|
||||||
def initVar(self, var):
|
def initVar(self, var):
|
||||||
self.expand_cache = {}
|
self.expand_cache = {}
|
||||||
@@ -518,44 +460,27 @@ class DataSmart(MutableMapping):
|
|||||||
dest = self.dict
|
dest = self.dict
|
||||||
while dest:
|
while dest:
|
||||||
if var in dest:
|
if var in dest:
|
||||||
return dest[var]
|
return dest[var], self.overridedata.get(var, None)
|
||||||
|
|
||||||
if "_data" not in dest:
|
if "_data" not in dest:
|
||||||
break
|
break
|
||||||
dest = dest["_data"]
|
dest = dest["_data"]
|
||||||
return None
|
return None, self.overridedata.get(var, None)
|
||||||
|
|
||||||
def _makeShadowCopy(self, var):
|
def _makeShadowCopy(self, var):
|
||||||
if var in self.dict:
|
if var in self.dict:
|
||||||
return
|
return
|
||||||
|
|
||||||
local_var = self._findVar(var)
|
local_var, _ = self._findVar(var)
|
||||||
|
|
||||||
if local_var:
|
if local_var:
|
||||||
self.dict[var] = copy.copy(local_var)
|
self.dict[var] = copy.copy(local_var)
|
||||||
else:
|
else:
|
||||||
self.initVar(var)
|
self.initVar(var)
|
||||||
|
|
||||||
def hasOverrides(self, var):
|
|
||||||
return var in self.overridedata
|
|
||||||
|
|
||||||
def setVar(self, var, value, **loginfo):
|
def setVar(self, var, value, **loginfo):
|
||||||
#print("var=" + str(var) + " val=" + str(value))
|
#print("var=" + str(var) + " val=" + str(value))
|
||||||
|
|
||||||
if not var.startswith("__anon_") and ("_append" in var or "_prepend" in var or "_remove" in var):
|
|
||||||
info = "%s" % var
|
|
||||||
if "file" in loginfo:
|
|
||||||
info += " file: %s" % loginfo["file"]
|
|
||||||
if "line" in loginfo:
|
|
||||||
info += " line: %s" % loginfo["line"]
|
|
||||||
bb.fatal("Variable %s contains an operation using the old override syntax. Please convert this layer/metadata before attempting to use with a newer bitbake." % info)
|
|
||||||
|
|
||||||
shortvar = var.split(":", 1)[0]
|
|
||||||
if shortvar in self._var_renames:
|
|
||||||
_print_rename_error(shortvar, loginfo, self._var_renames, fullvar=var)
|
|
||||||
# Mark that we have seen a renamed variable
|
|
||||||
self.setVar("_FAILPARSINGERRORHANDLED", True)
|
|
||||||
|
|
||||||
self.expand_cache = {}
|
self.expand_cache = {}
|
||||||
parsing=False
|
parsing=False
|
||||||
if 'parsing' in loginfo:
|
if 'parsing' in loginfo:
|
||||||
@@ -580,9 +505,12 @@ class DataSmart(MutableMapping):
|
|||||||
else:
|
else:
|
||||||
loginfo['op'] = keyword
|
loginfo['op'] = keyword
|
||||||
self.varhistory.record(**loginfo)
|
self.varhistory.record(**loginfo)
|
||||||
|
# todo make sure keyword is not __doc__ or __module__
|
||||||
|
# pay the cookie monster
|
||||||
|
|
||||||
# more cookies for the cookie monster
|
# more cookies for the cookie monster
|
||||||
self._setvar_update_overrides(base, **loginfo)
|
if '_' in var:
|
||||||
|
self._setvar_update_overrides(base, **loginfo)
|
||||||
|
|
||||||
if base in self.overridevars:
|
if base in self.overridevars:
|
||||||
self._setvar_update_overridevars(var, value)
|
self._setvar_update_overridevars(var, value)
|
||||||
@@ -592,27 +520,27 @@ class DataSmart(MutableMapping):
|
|||||||
self._makeShadowCopy(var)
|
self._makeShadowCopy(var)
|
||||||
|
|
||||||
if not parsing:
|
if not parsing:
|
||||||
if ":append" in self.dict[var]:
|
if "_append" in self.dict[var]:
|
||||||
del self.dict[var][":append"]
|
del self.dict[var]["_append"]
|
||||||
if ":prepend" in self.dict[var]:
|
if "_prepend" in self.dict[var]:
|
||||||
del self.dict[var][":prepend"]
|
del self.dict[var]["_prepend"]
|
||||||
if ":remove" in self.dict[var]:
|
if "_remove" in self.dict[var]:
|
||||||
del self.dict[var][":remove"]
|
del self.dict[var]["_remove"]
|
||||||
if var in self.overridedata:
|
if var in self.overridedata:
|
||||||
active = []
|
active = []
|
||||||
self.need_overrides()
|
self.need_overrides()
|
||||||
for (r, o) in self.overridedata[var]:
|
for (r, o) in self.overridedata[var]:
|
||||||
if o in self.overridesset:
|
if o in self.overridesset:
|
||||||
active.append(r)
|
active.append(r)
|
||||||
elif ":" in o:
|
elif "_" in o:
|
||||||
if set(o.split(":")).issubset(self.overridesset):
|
if set(o.split("_")).issubset(self.overridesset):
|
||||||
active.append(r)
|
active.append(r)
|
||||||
for a in active:
|
for a in active:
|
||||||
self.delVar(a)
|
self.delVar(a)
|
||||||
del self.overridedata[var]
|
del self.overridedata[var]
|
||||||
|
|
||||||
# more cookies for the cookie monster
|
# more cookies for the cookie monster
|
||||||
if ':' in var:
|
if '_' in var:
|
||||||
self._setvar_update_overrides(var, **loginfo)
|
self._setvar_update_overrides(var, **loginfo)
|
||||||
|
|
||||||
# setting var
|
# setting var
|
||||||
@@ -634,13 +562,12 @@ class DataSmart(MutableMapping):
|
|||||||
nextnew.update(vardata.references)
|
nextnew.update(vardata.references)
|
||||||
nextnew.update(vardata.contains.keys())
|
nextnew.update(vardata.contains.keys())
|
||||||
new = nextnew
|
new = nextnew
|
||||||
self.overrides = None
|
self.internal_finalize(True)
|
||||||
self.expand_cache = {}
|
|
||||||
|
|
||||||
def _setvar_update_overrides(self, var, **loginfo):
|
def _setvar_update_overrides(self, var, **loginfo):
|
||||||
# aka pay the cookie monster
|
# aka pay the cookie monster
|
||||||
override = var[var.rfind(':')+1:]
|
override = var[var.rfind('_')+1:]
|
||||||
shortvar = var[:var.rfind(':')]
|
shortvar = var[:var.rfind('_')]
|
||||||
while override and __override_regexp__.match(override):
|
while override and __override_regexp__.match(override):
|
||||||
if shortvar not in self.overridedata:
|
if shortvar not in self.overridedata:
|
||||||
self.overridedata[shortvar] = []
|
self.overridedata[shortvar] = []
|
||||||
@@ -649,9 +576,9 @@ class DataSmart(MutableMapping):
|
|||||||
self.overridedata[shortvar] = list(self.overridedata[shortvar])
|
self.overridedata[shortvar] = list(self.overridedata[shortvar])
|
||||||
self.overridedata[shortvar].append([var, override])
|
self.overridedata[shortvar].append([var, override])
|
||||||
override = None
|
override = None
|
||||||
if ":" in shortvar:
|
if "_" in shortvar:
|
||||||
override = var[shortvar.rfind(':')+1:]
|
override = var[shortvar.rfind('_')+1:]
|
||||||
shortvar = var[:shortvar.rfind(':')]
|
shortvar = var[:shortvar.rfind('_')]
|
||||||
if len(shortvar) == 0:
|
if len(shortvar) == 0:
|
||||||
override = None
|
override = None
|
||||||
|
|
||||||
@@ -675,11 +602,10 @@ class DataSmart(MutableMapping):
|
|||||||
self.varhistory.record(**loginfo)
|
self.varhistory.record(**loginfo)
|
||||||
self.setVar(newkey, val, ignore=True, parsing=True)
|
self.setVar(newkey, val, ignore=True, parsing=True)
|
||||||
|
|
||||||
srcflags = self.getVarFlags(key, False, True) or {}
|
for i in (__setvar_keyword__):
|
||||||
for i in srcflags:
|
src = self.getVarFlag(key, i, False)
|
||||||
if i not in (__setvar_keyword__):
|
if src is None:
|
||||||
continue
|
continue
|
||||||
src = srcflags[i]
|
|
||||||
|
|
||||||
dest = self.getVarFlag(newkey, i, False) or []
|
dest = self.getVarFlag(newkey, i, False) or []
|
||||||
dest.extend(src)
|
dest.extend(src)
|
||||||
@@ -691,7 +617,7 @@ class DataSmart(MutableMapping):
|
|||||||
self.overridedata[newkey].append([v.replace(key, newkey), o])
|
self.overridedata[newkey].append([v.replace(key, newkey), o])
|
||||||
self.renameVar(v, v.replace(key, newkey))
|
self.renameVar(v, v.replace(key, newkey))
|
||||||
|
|
||||||
if ':' in newkey and val is None:
|
if '_' in newkey and val is None:
|
||||||
self._setvar_update_overrides(newkey, **loginfo)
|
self._setvar_update_overrides(newkey, **loginfo)
|
||||||
|
|
||||||
loginfo['variable'] = key
|
loginfo['variable'] = key
|
||||||
@@ -703,12 +629,12 @@ class DataSmart(MutableMapping):
|
|||||||
def appendVar(self, var, value, **loginfo):
|
def appendVar(self, var, value, **loginfo):
|
||||||
loginfo['op'] = 'append'
|
loginfo['op'] = 'append'
|
||||||
self.varhistory.record(**loginfo)
|
self.varhistory.record(**loginfo)
|
||||||
self.setVar(var + ":append", value, ignore=True, parsing=True)
|
self.setVar(var + "_append", value, ignore=True, parsing=True)
|
||||||
|
|
||||||
def prependVar(self, var, value, **loginfo):
|
def prependVar(self, var, value, **loginfo):
|
||||||
loginfo['op'] = 'prepend'
|
loginfo['op'] = 'prepend'
|
||||||
self.varhistory.record(**loginfo)
|
self.varhistory.record(**loginfo)
|
||||||
self.setVar(var + ":prepend", value, ignore=True, parsing=True)
|
self.setVar(var + "_prepend", value, ignore=True, parsing=True)
|
||||||
|
|
||||||
def delVar(self, var, **loginfo):
|
def delVar(self, var, **loginfo):
|
||||||
self.expand_cache = {}
|
self.expand_cache = {}
|
||||||
@@ -719,10 +645,10 @@ class DataSmart(MutableMapping):
|
|||||||
self.dict[var] = {}
|
self.dict[var] = {}
|
||||||
if var in self.overridedata:
|
if var in self.overridedata:
|
||||||
del self.overridedata[var]
|
del self.overridedata[var]
|
||||||
if ':' in var:
|
if '_' in var:
|
||||||
override = var[var.rfind(':')+1:]
|
override = var[var.rfind('_')+1:]
|
||||||
shortvar = var[:var.rfind(':')]
|
shortvar = var[:var.rfind('_')]
|
||||||
while override and __override_regexp__.match(override):
|
while override and override.islower():
|
||||||
try:
|
try:
|
||||||
if shortvar in self.overridedata:
|
if shortvar in self.overridedata:
|
||||||
# Force CoW by recreating the list first
|
# Force CoW by recreating the list first
|
||||||
@@ -731,23 +657,15 @@ class DataSmart(MutableMapping):
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
pass
|
pass
|
||||||
override = None
|
override = None
|
||||||
if ":" in shortvar:
|
if "_" in shortvar:
|
||||||
override = var[shortvar.rfind(':')+1:]
|
override = var[shortvar.rfind('_')+1:]
|
||||||
shortvar = var[:shortvar.rfind(':')]
|
shortvar = var[:shortvar.rfind('_')]
|
||||||
if len(shortvar) == 0:
|
if len(shortvar) == 0:
|
||||||
override = None
|
override = None
|
||||||
|
|
||||||
def setVarFlag(self, var, flag, value, **loginfo):
|
def setVarFlag(self, var, flag, value, **loginfo):
|
||||||
self.expand_cache = {}
|
self.expand_cache = {}
|
||||||
|
|
||||||
if var == "BB_RENAMED_VARIABLES":
|
|
||||||
self._var_renames[flag] = value
|
|
||||||
|
|
||||||
if var in self._var_renames:
|
|
||||||
_print_rename_error(var, loginfo, self._var_renames)
|
|
||||||
# Mark that we have seen a renamed variable
|
|
||||||
self.setVar("_FAILPARSINGERRORHANDLED", True)
|
|
||||||
|
|
||||||
if 'op' not in loginfo:
|
if 'op' not in loginfo:
|
||||||
loginfo['op'] = "set"
|
loginfo['op'] = "set"
|
||||||
loginfo['flag'] = flag
|
loginfo['flag'] = flag
|
||||||
@@ -756,7 +674,7 @@ class DataSmart(MutableMapping):
|
|||||||
self._makeShadowCopy(var)
|
self._makeShadowCopy(var)
|
||||||
self.dict[var][flag] = value
|
self.dict[var][flag] = value
|
||||||
|
|
||||||
if flag == "_defaultval" and ':' in var:
|
if flag == "_defaultval" and '_' in var:
|
||||||
self._setvar_update_overrides(var, **loginfo)
|
self._setvar_update_overrides(var, **loginfo)
|
||||||
if flag == "_defaultval" and var in self.overridevars:
|
if flag == "_defaultval" and var in self.overridevars:
|
||||||
self._setvar_update_overridevars(var, value)
|
self._setvar_update_overridevars(var, value)
|
||||||
@@ -777,27 +695,22 @@ class DataSmart(MutableMapping):
|
|||||||
return None
|
return None
|
||||||
cachename = var + "[" + flag + "]"
|
cachename = var + "[" + flag + "]"
|
||||||
|
|
||||||
if not expand and retparser and cachename in self.expand_cache:
|
|
||||||
return self.expand_cache[cachename].unexpanded_value, self.expand_cache[cachename]
|
|
||||||
|
|
||||||
if expand and cachename in self.expand_cache:
|
if expand and cachename in self.expand_cache:
|
||||||
return self.expand_cache[cachename].value
|
return self.expand_cache[cachename].value
|
||||||
|
|
||||||
local_var = self._findVar(var)
|
local_var, overridedata = self._findVar(var)
|
||||||
value = None
|
value = None
|
||||||
removes = set()
|
removes = set()
|
||||||
if flag == "_content" and not parsing:
|
if flag == "_content" and overridedata is not None and not parsing:
|
||||||
overridedata = self.overridedata.get(var, None)
|
|
||||||
if flag == "_content" and not parsing and overridedata is not None:
|
|
||||||
match = False
|
match = False
|
||||||
active = {}
|
active = {}
|
||||||
self.need_overrides()
|
self.need_overrides()
|
||||||
for (r, o) in overridedata:
|
for (r, o) in overridedata:
|
||||||
# FIXME What about double overrides both with "_" in the name?
|
# What about double overrides both with "_" in the name?
|
||||||
if o in self.overridesset:
|
if o in self.overridesset:
|
||||||
active[o] = r
|
active[o] = r
|
||||||
elif ":" in o:
|
elif "_" in o:
|
||||||
if set(o.split(":")).issubset(self.overridesset):
|
if set(o.split("_")).issubset(self.overridesset):
|
||||||
active[o] = r
|
active[o] = r
|
||||||
|
|
||||||
mod = True
|
mod = True
|
||||||
@@ -805,10 +718,10 @@ class DataSmart(MutableMapping):
|
|||||||
mod = False
|
mod = False
|
||||||
for o in self.overrides:
|
for o in self.overrides:
|
||||||
for a in active.copy():
|
for a in active.copy():
|
||||||
if a.endswith(":" + o):
|
if a.endswith("_" + o):
|
||||||
t = active[a]
|
t = active[a]
|
||||||
del active[a]
|
del active[a]
|
||||||
active[a.replace(":" + o, "")] = t
|
active[a.replace("_" + o, "")] = t
|
||||||
mod = True
|
mod = True
|
||||||
elif a == o:
|
elif a == o:
|
||||||
match = active[a]
|
match = active[a]
|
||||||
@@ -827,31 +740,31 @@ class DataSmart(MutableMapping):
|
|||||||
value = copy.copy(local_var["_defaultval"])
|
value = copy.copy(local_var["_defaultval"])
|
||||||
|
|
||||||
|
|
||||||
if flag == "_content" and local_var is not None and ":append" in local_var and not parsing:
|
if flag == "_content" and local_var is not None and "_append" in local_var and not parsing:
|
||||||
|
if not value:
|
||||||
|
value = ""
|
||||||
self.need_overrides()
|
self.need_overrides()
|
||||||
for (r, o) in local_var[":append"]:
|
for (r, o) in local_var["_append"]:
|
||||||
match = True
|
match = True
|
||||||
if o:
|
if o:
|
||||||
for o2 in o.split(":"):
|
for o2 in o.split("_"):
|
||||||
if not o2 in self.overrides:
|
if not o2 in self.overrides:
|
||||||
match = False
|
match = False
|
||||||
if match:
|
if match:
|
||||||
if value is None:
|
|
||||||
value = ""
|
|
||||||
value = value + r
|
value = value + r
|
||||||
|
|
||||||
if flag == "_content" and local_var is not None and ":prepend" in local_var and not parsing:
|
if flag == "_content" and local_var is not None and "_prepend" in local_var and not parsing:
|
||||||
|
if not value:
|
||||||
|
value = ""
|
||||||
self.need_overrides()
|
self.need_overrides()
|
||||||
for (r, o) in local_var[":prepend"]:
|
for (r, o) in local_var["_prepend"]:
|
||||||
|
|
||||||
match = True
|
match = True
|
||||||
if o:
|
if o:
|
||||||
for o2 in o.split(":"):
|
for o2 in o.split("_"):
|
||||||
if not o2 in self.overrides:
|
if not o2 in self.overrides:
|
||||||
match = False
|
match = False
|
||||||
if match:
|
if match:
|
||||||
if value is None:
|
|
||||||
value = ""
|
|
||||||
value = r + value
|
value = r + value
|
||||||
|
|
||||||
parser = None
|
parser = None
|
||||||
@@ -860,12 +773,12 @@ class DataSmart(MutableMapping):
|
|||||||
if expand:
|
if expand:
|
||||||
value = parser.value
|
value = parser.value
|
||||||
|
|
||||||
if value and flag == "_content" and local_var is not None and ":remove" in local_var and not parsing:
|
if value and flag == "_content" and local_var is not None and "_remove" in local_var and not parsing:
|
||||||
self.need_overrides()
|
self.need_overrides()
|
||||||
for (r, o) in local_var[":remove"]:
|
for (r, o) in local_var["_remove"]:
|
||||||
match = True
|
match = True
|
||||||
if o:
|
if o:
|
||||||
for o2 in o.split(":"):
|
for o2 in o.split("_"):
|
||||||
if not o2 in self.overrides:
|
if not o2 in self.overrides:
|
||||||
match = False
|
match = False
|
||||||
if match:
|
if match:
|
||||||
@@ -878,7 +791,7 @@ class DataSmart(MutableMapping):
|
|||||||
expanded_removes[r] = self.expand(r).split()
|
expanded_removes[r] = self.expand(r).split()
|
||||||
|
|
||||||
parser.removes = set()
|
parser.removes = set()
|
||||||
val = []
|
val = ""
|
||||||
for v in __whitespace_split__.split(parser.value):
|
for v in __whitespace_split__.split(parser.value):
|
||||||
skip = False
|
skip = False
|
||||||
for r in removes:
|
for r in removes:
|
||||||
@@ -887,8 +800,8 @@ class DataSmart(MutableMapping):
|
|||||||
skip = True
|
skip = True
|
||||||
if skip:
|
if skip:
|
||||||
continue
|
continue
|
||||||
val.append(v)
|
val = val + v
|
||||||
parser.value = "".join(val)
|
parser.value = val
|
||||||
if expand:
|
if expand:
|
||||||
value = parser.value
|
value = parser.value
|
||||||
|
|
||||||
@@ -903,7 +816,7 @@ class DataSmart(MutableMapping):
|
|||||||
def delVarFlag(self, var, flag, **loginfo):
|
def delVarFlag(self, var, flag, **loginfo):
|
||||||
self.expand_cache = {}
|
self.expand_cache = {}
|
||||||
|
|
||||||
local_var = self._findVar(var)
|
local_var, _ = self._findVar(var)
|
||||||
if not local_var:
|
if not local_var:
|
||||||
return
|
return
|
||||||
if not var in self.dict:
|
if not var in self.dict:
|
||||||
@@ -946,12 +859,12 @@ class DataSmart(MutableMapping):
|
|||||||
self.dict[var][i] = flags[i]
|
self.dict[var][i] = flags[i]
|
||||||
|
|
||||||
def getVarFlags(self, var, expand = False, internalflags=False):
|
def getVarFlags(self, var, expand = False, internalflags=False):
|
||||||
local_var = self._findVar(var)
|
local_var, _ = self._findVar(var)
|
||||||
flags = {}
|
flags = {}
|
||||||
|
|
||||||
if local_var:
|
if local_var:
|
||||||
for i in local_var:
|
for i in local_var:
|
||||||
if i.startswith(("_", ":")) and not internalflags:
|
if i.startswith("_") and not internalflags:
|
||||||
continue
|
continue
|
||||||
flags[i] = local_var[i]
|
flags[i] = local_var[i]
|
||||||
if expand and i in expand:
|
if expand and i in expand:
|
||||||
@@ -992,7 +905,6 @@ class DataSmart(MutableMapping):
|
|||||||
data.inchistory = self.inchistory.copy()
|
data.inchistory = self.inchistory.copy()
|
||||||
|
|
||||||
data._tracking = self._tracking
|
data._tracking = self._tracking
|
||||||
data._var_renames = self._var_renames
|
|
||||||
|
|
||||||
data.overrides = None
|
data.overrides = None
|
||||||
data.overridevars = copy.copy(self.overridevars)
|
data.overridevars = copy.copy(self.overridevars)
|
||||||
@@ -1015,7 +927,7 @@ class DataSmart(MutableMapping):
|
|||||||
value = self.getVar(variable, False)
|
value = self.getVar(variable, False)
|
||||||
for key in keys:
|
for key in keys:
|
||||||
referrervalue = self.getVar(key, False)
|
referrervalue = self.getVar(key, False)
|
||||||
if referrervalue and isinstance(referrervalue, str) and ref in referrervalue:
|
if referrervalue and ref in referrervalue:
|
||||||
self.setVar(key, referrervalue.replace(ref, value))
|
self.setVar(key, referrervalue.replace(ref, value))
|
||||||
|
|
||||||
def localkeys(self):
|
def localkeys(self):
|
||||||
@@ -1050,8 +962,8 @@ class DataSmart(MutableMapping):
|
|||||||
for (r, o) in self.overridedata[var]:
|
for (r, o) in self.overridedata[var]:
|
||||||
if o in self.overridesset:
|
if o in self.overridesset:
|
||||||
overrides.add(var)
|
overrides.add(var)
|
||||||
elif ":" in o:
|
elif "_" in o:
|
||||||
if set(o.split(":")).issubset(self.overridesset):
|
if set(o.split("_")).issubset(self.overridesset):
|
||||||
overrides.add(var)
|
overrides.add(var)
|
||||||
|
|
||||||
for k in keylist(self.dict):
|
for k in keylist(self.dict):
|
||||||
@@ -1081,10 +993,10 @@ class DataSmart(MutableMapping):
|
|||||||
d = self.createCopy()
|
d = self.createCopy()
|
||||||
bb.data.expandKeys(d)
|
bb.data.expandKeys(d)
|
||||||
|
|
||||||
config_ignore_vars = set((d.getVar("BB_HASHCONFIG_IGNORE_VARS") or "").split())
|
config_whitelist = set((d.getVar("BB_HASHCONFIG_WHITELIST") or "").split())
|
||||||
keys = set(key for key in iter(d) if not key.startswith("__"))
|
keys = set(key for key in iter(d) if not key.startswith("__"))
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if key in config_ignore_vars:
|
if key in config_whitelist:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
value = d.getVar(key, False) or ""
|
value = d.getVar(key, False) or ""
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import sys
|
|||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
import bb.exceptions
|
||||||
import bb.utils
|
import bb.utils
|
||||||
|
|
||||||
# This is the pid for which we should generate the event. This is set when
|
# This is the pid for which we should generate the event. This is set when
|
||||||
@@ -39,7 +40,7 @@ class HeartbeatEvent(Event):
|
|||||||
"""Triggered at regular time intervals of 10 seconds. Other events can fire much more often
|
"""Triggered at regular time intervals of 10 seconds. Other events can fire much more often
|
||||||
(runQueueTaskStarted when there are many short tasks) or not at all for long periods
|
(runQueueTaskStarted when there are many short tasks) or not at all for long periods
|
||||||
of time (again runQueueTaskStarted, when there is just one long-running task), so this
|
of time (again runQueueTaskStarted, when there is just one long-running task), so this
|
||||||
event is more suitable for doing some task-independent work occasionally."""
|
event is more suitable for doing some task-independent work occassionally."""
|
||||||
def __init__(self, time):
|
def __init__(self, time):
|
||||||
Event.__init__(self)
|
Event.__init__(self)
|
||||||
self.time = time
|
self.time = time
|
||||||
@@ -67,39 +68,29 @@ _catchall_handlers = {}
|
|||||||
_eventfilter = None
|
_eventfilter = None
|
||||||
_uiready = False
|
_uiready = False
|
||||||
_thread_lock = threading.Lock()
|
_thread_lock = threading.Lock()
|
||||||
_heartbeat_enabled = False
|
_thread_lock_enabled = False
|
||||||
_should_exit = threading.Event()
|
|
||||||
|
if hasattr(__builtins__, '__setitem__'):
|
||||||
|
builtins = __builtins__
|
||||||
|
else:
|
||||||
|
builtins = __builtins__.__dict__
|
||||||
|
|
||||||
def enable_threadlock():
|
def enable_threadlock():
|
||||||
# Always needed now
|
global _thread_lock_enabled
|
||||||
return
|
_thread_lock_enabled = True
|
||||||
|
|
||||||
def disable_threadlock():
|
def disable_threadlock():
|
||||||
# Always needed now
|
global _thread_lock_enabled
|
||||||
return
|
_thread_lock_enabled = False
|
||||||
|
|
||||||
def enable_heartbeat():
|
|
||||||
global _heartbeat_enabled
|
|
||||||
_heartbeat_enabled = True
|
|
||||||
|
|
||||||
def disable_heartbeat():
|
|
||||||
global _heartbeat_enabled
|
|
||||||
_heartbeat_enabled = False
|
|
||||||
|
|
||||||
#
|
|
||||||
# In long running code, this function should be called periodically
|
|
||||||
# to check if we should exit due to an interuption (.e.g Ctrl+C from the UI)
|
|
||||||
#
|
|
||||||
def check_for_interrupts(d):
|
|
||||||
global _should_exit
|
|
||||||
if _should_exit.is_set():
|
|
||||||
bb.warn("Exiting due to interrupt.")
|
|
||||||
raise bb.BBHandledException()
|
|
||||||
|
|
||||||
def execute_handler(name, handler, event, d):
|
def execute_handler(name, handler, event, d):
|
||||||
event.data = d
|
event.data = d
|
||||||
|
addedd = False
|
||||||
|
if 'd' not in builtins:
|
||||||
|
builtins['d'] = d
|
||||||
|
addedd = True
|
||||||
try:
|
try:
|
||||||
ret = handler(event, d)
|
ret = handler(event)
|
||||||
except (bb.parse.SkipRecipe, bb.BBHandledException):
|
except (bb.parse.SkipRecipe, bb.BBHandledException):
|
||||||
raise
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -113,7 +104,8 @@ def execute_handler(name, handler, event, d):
|
|||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
del event.data
|
del event.data
|
||||||
|
if addedd:
|
||||||
|
del builtins['d']
|
||||||
|
|
||||||
def fire_class_handlers(event, d):
|
def fire_class_handlers(event, d):
|
||||||
if isinstance(event, logging.LogRecord):
|
if isinstance(event, logging.LogRecord):
|
||||||
@@ -126,8 +118,6 @@ def fire_class_handlers(event, d):
|
|||||||
if _eventfilter:
|
if _eventfilter:
|
||||||
if not _eventfilter(name, handler, event, d):
|
if not _eventfilter(name, handler, event, d):
|
||||||
continue
|
continue
|
||||||
if d is not None and not name in (d.getVar("__BBHANDLERS_MC") or set()):
|
|
||||||
continue
|
|
||||||
execute_handler(name, handler, event, d)
|
execute_handler(name, handler, event, d)
|
||||||
|
|
||||||
ui_queue = []
|
ui_queue = []
|
||||||
@@ -140,14 +130,8 @@ def print_ui_queue():
|
|||||||
if not _uiready:
|
if not _uiready:
|
||||||
from bb.msg import BBLogFormatter
|
from bb.msg import BBLogFormatter
|
||||||
# Flush any existing buffered content
|
# Flush any existing buffered content
|
||||||
try:
|
sys.stdout.flush()
|
||||||
sys.stdout.flush()
|
sys.stderr.flush()
|
||||||
except:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
sys.stderr.flush()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
stdout = logging.StreamHandler(sys.stdout)
|
stdout = logging.StreamHandler(sys.stdout)
|
||||||
stderr = logging.StreamHandler(sys.stderr)
|
stderr = logging.StreamHandler(sys.stderr)
|
||||||
formatter = BBLogFormatter("%(levelname)s: %(message)s")
|
formatter = BBLogFormatter("%(levelname)s: %(message)s")
|
||||||
@@ -188,38 +172,36 @@ def print_ui_queue():
|
|||||||
|
|
||||||
def fire_ui_handlers(event, d):
|
def fire_ui_handlers(event, d):
|
||||||
global _thread_lock
|
global _thread_lock
|
||||||
|
global _thread_lock_enabled
|
||||||
|
|
||||||
if not _uiready:
|
if not _uiready:
|
||||||
# No UI handlers registered yet, queue up the messages
|
# No UI handlers registered yet, queue up the messages
|
||||||
ui_queue.append(event)
|
ui_queue.append(event)
|
||||||
return
|
return
|
||||||
|
|
||||||
with bb.utils.lock_timeout_nocheck(_thread_lock) as lock:
|
if _thread_lock_enabled:
|
||||||
if not lock:
|
_thread_lock.acquire()
|
||||||
# If we can't get the lock, we may be recursively called, queue and return
|
|
||||||
ui_queue.append(event)
|
|
||||||
return
|
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
for h in _ui_handlers:
|
for h in _ui_handlers:
|
||||||
#print "Sending event %s" % event
|
#print "Sending event %s" % event
|
||||||
try:
|
try:
|
||||||
if not _ui_logfilters[h].filter(event):
|
if not _ui_logfilters[h].filter(event):
|
||||||
continue
|
continue
|
||||||
# We use pickle here since it better handles object instances
|
# We use pickle here since it better handles object instances
|
||||||
# which xmlrpc's marshaller does not. Events *must* be serializable
|
# which xmlrpc's marshaller does not. Events *must* be serializable
|
||||||
# by pickle.
|
# by pickle.
|
||||||
if hasattr(_ui_handlers[h].event, "sendpickle"):
|
if hasattr(_ui_handlers[h].event, "sendpickle"):
|
||||||
_ui_handlers[h].event.sendpickle((pickle.dumps(event)))
|
_ui_handlers[h].event.sendpickle((pickle.dumps(event)))
|
||||||
else:
|
else:
|
||||||
_ui_handlers[h].event.send(event)
|
_ui_handlers[h].event.send(event)
|
||||||
except:
|
except:
|
||||||
errors.append(h)
|
errors.append(h)
|
||||||
for h in errors:
|
for h in errors:
|
||||||
del _ui_handlers[h]
|
del _ui_handlers[h]
|
||||||
|
|
||||||
while ui_queue:
|
if _thread_lock_enabled:
|
||||||
fire_ui_handlers(ui_queue.pop(), d)
|
_thread_lock.release()
|
||||||
|
|
||||||
def fire(event, d):
|
def fire(event, d):
|
||||||
"""Fire off an Event"""
|
"""Fire off an Event"""
|
||||||
@@ -245,34 +227,25 @@ def fire_from_worker(event, d):
|
|||||||
fire_ui_handlers(event, d)
|
fire_ui_handlers(event, d)
|
||||||
|
|
||||||
noop = lambda _: None
|
noop = lambda _: None
|
||||||
def register(name, handler, mask=None, filename=None, lineno=None, data=None):
|
def register(name, handler, mask=None, filename=None, lineno=None):
|
||||||
"""Register an Event handler"""
|
"""Register an Event handler"""
|
||||||
|
|
||||||
if data is not None and data.getVar("BB_CURRENT_MC"):
|
|
||||||
mc = data.getVar("BB_CURRENT_MC")
|
|
||||||
name = '%s%s' % (mc.replace('-', '_'), name)
|
|
||||||
|
|
||||||
# already registered
|
# already registered
|
||||||
if name in _handlers:
|
if name in _handlers:
|
||||||
if data is not None:
|
|
||||||
bbhands_mc = (data.getVar("__BBHANDLERS_MC") or set())
|
|
||||||
bbhands_mc.add(name)
|
|
||||||
data.setVar("__BBHANDLERS_MC", bbhands_mc)
|
|
||||||
return AlreadyRegistered
|
return AlreadyRegistered
|
||||||
|
|
||||||
if handler is not None:
|
if handler is not None:
|
||||||
# handle string containing python code
|
# handle string containing python code
|
||||||
if isinstance(handler, str):
|
if isinstance(handler, str):
|
||||||
tmp = "def %s(e, d):\n%s" % (name, handler)
|
tmp = "def %s(e):\n%s" % (name, handler)
|
||||||
# Inject empty lines to make code match lineno in filename
|
|
||||||
if lineno is not None:
|
|
||||||
tmp = "\n" * (lineno-1) + tmp
|
|
||||||
try:
|
try:
|
||||||
code = bb.methodpool.compile_cache(tmp)
|
code = bb.methodpool.compile_cache(tmp)
|
||||||
if not code:
|
if not code:
|
||||||
if filename is None:
|
if filename is None:
|
||||||
filename = "%s(e, d)" % name
|
filename = "%s(e)" % name
|
||||||
code = compile(tmp, filename, "exec", ast.PyCF_ONLY_AST)
|
code = compile(tmp, filename, "exec", ast.PyCF_ONLY_AST)
|
||||||
|
if lineno is not None:
|
||||||
|
ast.increment_lineno(code, lineno-1)
|
||||||
code = compile(code, filename, "exec")
|
code = compile(code, filename, "exec")
|
||||||
bb.methodpool.compile_cache_add(tmp, code)
|
bb.methodpool.compile_cache_add(tmp, code)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
@@ -295,20 +268,10 @@ def register(name, handler, mask=None, filename=None, lineno=None, data=None):
|
|||||||
_event_handler_map[m] = {}
|
_event_handler_map[m] = {}
|
||||||
_event_handler_map[m][name] = True
|
_event_handler_map[m][name] = True
|
||||||
|
|
||||||
if data is not None:
|
|
||||||
bbhands_mc = (data.getVar("__BBHANDLERS_MC") or set())
|
|
||||||
bbhands_mc.add(name)
|
|
||||||
data.setVar("__BBHANDLERS_MC", bbhands_mc)
|
|
||||||
|
|
||||||
return Registered
|
return Registered
|
||||||
|
|
||||||
def remove(name, handler, data=None):
|
def remove(name, handler):
|
||||||
"""Remove an Event handler"""
|
"""Remove an Event handler"""
|
||||||
if data is not None:
|
|
||||||
if data.getVar("BB_CURRENT_MC"):
|
|
||||||
mc = data.getVar("BB_CURRENT_MC")
|
|
||||||
name = '%s%s' % (mc.replace('-', '_'), name)
|
|
||||||
|
|
||||||
_handlers.pop(name)
|
_handlers.pop(name)
|
||||||
if name in _catchall_handlers:
|
if name in _catchall_handlers:
|
||||||
_catchall_handlers.pop(name)
|
_catchall_handlers.pop(name)
|
||||||
@@ -316,12 +279,6 @@ def remove(name, handler, data=None):
|
|||||||
if name in _event_handler_map[event]:
|
if name in _event_handler_map[event]:
|
||||||
_event_handler_map[event].pop(name)
|
_event_handler_map[event].pop(name)
|
||||||
|
|
||||||
if data is not None:
|
|
||||||
bbhands_mc = (data.getVar("__BBHANDLERS_MC") or set())
|
|
||||||
if name in bbhands_mc:
|
|
||||||
bbhands_mc.remove(name)
|
|
||||||
data.setVar("__BBHANDLERS_MC", bbhands_mc)
|
|
||||||
|
|
||||||
def get_handlers():
|
def get_handlers():
|
||||||
return _handlers
|
return _handlers
|
||||||
|
|
||||||
@@ -334,23 +291,21 @@ def set_eventfilter(func):
|
|||||||
_eventfilter = func
|
_eventfilter = func
|
||||||
|
|
||||||
def register_UIHhandler(handler, mainui=False):
|
def register_UIHhandler(handler, mainui=False):
|
||||||
with bb.utils.lock_timeout(_thread_lock):
|
bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1
|
||||||
bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1
|
_ui_handlers[_ui_handler_seq] = handler
|
||||||
_ui_handlers[_ui_handler_seq] = handler
|
level, debug_domains = bb.msg.constructLogOptions()
|
||||||
level, debug_domains = bb.msg.constructLogOptions()
|
_ui_logfilters[_ui_handler_seq] = UIEventFilter(level, debug_domains)
|
||||||
_ui_logfilters[_ui_handler_seq] = UIEventFilter(level, debug_domains)
|
if mainui:
|
||||||
if mainui:
|
global _uiready
|
||||||
global _uiready
|
_uiready = _ui_handler_seq
|
||||||
_uiready = _ui_handler_seq
|
return _ui_handler_seq
|
||||||
return _ui_handler_seq
|
|
||||||
|
|
||||||
def unregister_UIHhandler(handlerNum, mainui=False):
|
def unregister_UIHhandler(handlerNum, mainui=False):
|
||||||
if mainui:
|
if mainui:
|
||||||
global _uiready
|
global _uiready
|
||||||
_uiready = False
|
_uiready = False
|
||||||
with bb.utils.lock_timeout(_thread_lock):
|
if handlerNum in _ui_handlers:
|
||||||
if handlerNum in _ui_handlers:
|
del _ui_handlers[handlerNum]
|
||||||
del _ui_handlers[handlerNum]
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_uihandler():
|
def get_uihandler():
|
||||||
@@ -505,7 +460,7 @@ class BuildCompleted(BuildBase, OperationCompleted):
|
|||||||
BuildBase.__init__(self, n, p, failures)
|
BuildBase.__init__(self, n, p, failures)
|
||||||
|
|
||||||
class DiskFull(Event):
|
class DiskFull(Event):
|
||||||
"""Disk full case build halted"""
|
"""Disk full case build aborted"""
|
||||||
def __init__(self, dev, type, freespace, mountpoint):
|
def __init__(self, dev, type, freespace, mountpoint):
|
||||||
Event.__init__(self)
|
Event.__init__(self)
|
||||||
self._dev = dev
|
self._dev = dev
|
||||||
@@ -689,17 +644,6 @@ class ReachableStamps(Event):
|
|||||||
Event.__init__(self)
|
Event.__init__(self)
|
||||||
self.stamps = stamps
|
self.stamps = stamps
|
||||||
|
|
||||||
class StaleSetSceneTasks(Event):
|
|
||||||
"""
|
|
||||||
An event listing setscene tasks which are 'stale' and will
|
|
||||||
be rerun. The metadata may use to clean up stale data.
|
|
||||||
tasks is a mapping of tasks and matching stale stamps.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, tasks):
|
|
||||||
Event.__init__(self)
|
|
||||||
self.tasks = tasks
|
|
||||||
|
|
||||||
class FilesMatchingFound(Event):
|
class FilesMatchingFound(Event):
|
||||||
"""
|
"""
|
||||||
Event when a list of files matching the supplied pattern has
|
Event when a list of files matching the supplied pattern has
|
||||||
@@ -766,7 +710,13 @@ class LogHandler(logging.Handler):
|
|||||||
|
|
||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
if record.exc_info:
|
if record.exc_info:
|
||||||
record.bb_exc_formatted = traceback.format_exception(*record.exc_info)
|
etype, value, tb = record.exc_info
|
||||||
|
if hasattr(tb, 'tb_next'):
|
||||||
|
tb = list(bb.exceptions.extract_traceback(tb, context=3))
|
||||||
|
# Need to turn the value into something the logging system can pickle
|
||||||
|
record.bb_exc_info = (etype, value, tb)
|
||||||
|
record.bb_exc_formatted = bb.exceptions.format_exception(etype, value, tb, limit=5)
|
||||||
|
value = str(value)
|
||||||
record.exc_info = None
|
record.exc_info = None
|
||||||
fire(record, None)
|
fire(record, None)
|
||||||
|
|
||||||
@@ -777,7 +727,7 @@ class LogHandler(logging.Handler):
|
|||||||
class MetadataEvent(Event):
|
class MetadataEvent(Event):
|
||||||
"""
|
"""
|
||||||
Generic event that target for OE-Core classes
|
Generic event that target for OE-Core classes
|
||||||
to report information during asynchronous execution
|
to report information during asynchrous execution
|
||||||
"""
|
"""
|
||||||
def __init__(self, eventtype, eventdata):
|
def __init__(self, eventtype, eventdata):
|
||||||
Event.__init__(self)
|
Event.__init__(self)
|
||||||
@@ -858,19 +808,3 @@ class FindSigInfoResult(Event):
|
|||||||
def __init__(self, result):
|
def __init__(self, result):
|
||||||
Event.__init__(self)
|
Event.__init__(self)
|
||||||
self.result = result
|
self.result = result
|
||||||
|
|
||||||
class GetTaskSignatureResult(Event):
|
|
||||||
"""
|
|
||||||
Event to return results from GetTaskSignatures command
|
|
||||||
"""
|
|
||||||
def __init__(self, sig):
|
|
||||||
Event.__init__(self)
|
|
||||||
self.sig = sig
|
|
||||||
|
|
||||||
class ParseError(Event):
|
|
||||||
"""
|
|
||||||
Event to indicate parse failed
|
|
||||||
"""
|
|
||||||
def __init__(self, msg):
|
|
||||||
super().__init__()
|
|
||||||
self._msg = msg
|
|
||||||
|
|||||||
94
bitbake/lib/bb/exceptions.py
Normal file
94
bitbake/lib/bb/exceptions.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
#
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
import traceback
|
||||||
|
import bb.namedtuple_with_abc
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
|
||||||
|
class TracebackEntry(namedtuple.abc):
|
||||||
|
"""Pickleable representation of a traceback entry"""
|
||||||
|
_fields = 'filename lineno function args code_context index'
|
||||||
|
_header = ' File "{0.filename}", line {0.lineno}, in {0.function}{0.args}'
|
||||||
|
|
||||||
|
def format(self, formatter=None):
|
||||||
|
if not self.code_context:
|
||||||
|
return self._header.format(self) + '\n'
|
||||||
|
|
||||||
|
formatted = [self._header.format(self) + ':\n']
|
||||||
|
|
||||||
|
for lineindex, line in enumerate(self.code_context):
|
||||||
|
if formatter:
|
||||||
|
line = formatter(line)
|
||||||
|
|
||||||
|
if lineindex == self.index:
|
||||||
|
formatted.append(' >%s' % line)
|
||||||
|
else:
|
||||||
|
formatted.append(' %s' % line)
|
||||||
|
return formatted
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return ''.join(self.format())
|
||||||
|
|
||||||
|
def _get_frame_args(frame):
|
||||||
|
"""Get the formatted arguments and class (if available) for a frame"""
|
||||||
|
arginfo = inspect.getargvalues(frame)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not arginfo.args:
|
||||||
|
return '', None
|
||||||
|
# There have been reports from the field of python 2.6 which doesn't
|
||||||
|
# return a namedtuple here but simply a tuple so fallback gracefully if
|
||||||
|
# args isn't present.
|
||||||
|
except AttributeError:
|
||||||
|
return '', None
|
||||||
|
|
||||||
|
firstarg = arginfo.args[0]
|
||||||
|
if firstarg == 'self':
|
||||||
|
self = arginfo.locals['self']
|
||||||
|
cls = self.__class__.__name__
|
||||||
|
|
||||||
|
arginfo.args.pop(0)
|
||||||
|
del arginfo.locals['self']
|
||||||
|
else:
|
||||||
|
cls = None
|
||||||
|
|
||||||
|
formatted = inspect.formatargvalues(*arginfo)
|
||||||
|
return formatted, cls
|
||||||
|
|
||||||
|
def extract_traceback(tb, context=1):
|
||||||
|
frames = inspect.getinnerframes(tb, context)
|
||||||
|
for frame, filename, lineno, function, code_context, index in frames:
|
||||||
|
formatted_args, cls = _get_frame_args(frame)
|
||||||
|
if cls:
|
||||||
|
function = '%s.%s' % (cls, function)
|
||||||
|
yield TracebackEntry(filename, lineno, function, formatted_args,
|
||||||
|
code_context, index)
|
||||||
|
|
||||||
|
def format_extracted(extracted, formatter=None, limit=None):
|
||||||
|
if limit:
|
||||||
|
extracted = extracted[-limit:]
|
||||||
|
|
||||||
|
formatted = []
|
||||||
|
for tracebackinfo in extracted:
|
||||||
|
formatted.extend(tracebackinfo.format(formatter))
|
||||||
|
return formatted
|
||||||
|
|
||||||
|
|
||||||
|
def format_exception(etype, value, tb, context=1, limit=None, formatter=None):
|
||||||
|
formatted = ['Traceback (most recent call last):\n']
|
||||||
|
|
||||||
|
if hasattr(tb, 'tb_next'):
|
||||||
|
tb = extract_traceback(tb, context)
|
||||||
|
|
||||||
|
formatted.extend(format_extracted(tb, formatter, limit))
|
||||||
|
formatted.extend(traceback.format_exception_only(etype, value))
|
||||||
|
return formatted
|
||||||
|
|
||||||
|
def to_string(exc):
|
||||||
|
if isinstance(exc, SystemExit):
|
||||||
|
if not isinstance(exc.code, str):
|
||||||
|
return 'Exited with "%d"' % exc.code
|
||||||
|
return str(exc)
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
There are expectations of users of the fetcher code. This file attempts to document
|
|
||||||
some of the constraints that are present. Some are obvious, some are less so. It is
|
|
||||||
documented in the context of how OE uses it but the API calls are generic.
|
|
||||||
|
|
||||||
a) network access for sources is only expected to happen in the do_fetch task.
|
|
||||||
This is not enforced or tested but is required so that we can:
|
|
||||||
|
|
||||||
i) audit the sources used (i.e. for license/manifest reasons)
|
|
||||||
ii) support offline builds with a suitable cache
|
|
||||||
iii) allow work to continue even with downtime upstream
|
|
||||||
iv) allow for changes upstream in incompatible ways
|
|
||||||
v) allow rebuilding of the software in X years time
|
|
||||||
|
|
||||||
b) network access is not expected in do_unpack task.
|
|
||||||
|
|
||||||
c) you can take DL_DIR and use it as a mirror for offline builds.
|
|
||||||
|
|
||||||
d) access to the network is only made when explicitly configured in recipes
|
|
||||||
(e.g. use of AUTOREV, or use of git tags which change revision).
|
|
||||||
|
|
||||||
e) fetcher output is deterministic (i.e. if you fetch configuration XXX now it
|
|
||||||
will match in future exactly in a clean build with a new DL_DIR).
|
|
||||||
One specific pain point example are git tags. They can be replaced and change
|
|
||||||
so the git fetcher has to resolve them with the network. We use git revisions
|
|
||||||
where possible to avoid this and ensure determinism.
|
|
||||||
|
|
||||||
f) network access is expected to work with the standard linux proxy variables
|
|
||||||
so that access behind firewalls works (the fetcher sets these in the
|
|
||||||
environment but only in the do_fetch tasks).
|
|
||||||
|
|
||||||
g) access during parsing has to be minimal, a "git ls-remote" for an AUTOREV
|
|
||||||
git recipe might be ok but you can't expect to checkout a git tree.
|
|
||||||
|
|
||||||
h) we need to provide revision information during parsing such that a version
|
|
||||||
for the recipe can be constructed.
|
|
||||||
|
|
||||||
i) versions are expected to be able to increase in a way which sorts allowing
|
|
||||||
package feeds to operate (see PR server required for git revisions to sort).
|
|
||||||
|
|
||||||
j) API to query for possible version upgrades of a url is highly desireable to
|
|
||||||
allow our automated upgrage code to function (it is implied this does always
|
|
||||||
have network access).
|
|
||||||
|
|
||||||
k) Where fixes or changes to behaviour in the fetcher are made, we ask that
|
|
||||||
test cases are added (run with "bitbake-selftest bb.tests.fetch"). We do
|
|
||||||
have fairly extensive test coverage of the fetcher as it is the only way
|
|
||||||
to track all of its corner cases, it still doesn't give entire coverage
|
|
||||||
though sadly.
|
|
||||||
|
|
||||||
l) If using tools during parse time, they will have to be in ASSUME_PROVIDED
|
|
||||||
in OE's context as we can't build git-native, then parse a recipe and use
|
|
||||||
git ls-remote.
|
|
||||||
|
|
||||||
Not all fetchers support all features, autorev is optional and doesn't make
|
|
||||||
sense for some. Upgrade detection means different things in different contexts
|
|
||||||
too.
|
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ class MissingParameterError(BBFetchException):
|
|||||||
self.args = (missing, url)
|
self.args = (missing, url)
|
||||||
|
|
||||||
class ParameterError(BBFetchException):
|
class ParameterError(BBFetchException):
|
||||||
"""Exception raised when a url cannot be processed due to invalid parameters."""
|
"""Exception raised when a url cannot be proccessed due to invalid parameters."""
|
||||||
def __init__(self, message, url):
|
def __init__(self, message, url):
|
||||||
msg = "URL: '%s' has invalid parameters. %s" % (url, message)
|
msg = "URL: '%s' has invalid parameters. %s" % (url, message)
|
||||||
self.url = url
|
self.url = url
|
||||||
@@ -182,7 +182,7 @@ class URI(object):
|
|||||||
Some notes about relative URIs: while it's specified that
|
Some notes about relative URIs: while it's specified that
|
||||||
a URI beginning with <scheme>:// should either be directly
|
a URI beginning with <scheme>:// should either be directly
|
||||||
followed by a hostname or a /, the old URI handling of the
|
followed by a hostname or a /, the old URI handling of the
|
||||||
fetch2 library did not conform to this. Therefore, this URI
|
fetch2 library did not comform to this. Therefore, this URI
|
||||||
class has some kludges to make sure that URIs are parsed in
|
class has some kludges to make sure that URIs are parsed in
|
||||||
a way comforming to bitbake's current usage. This URI class
|
a way comforming to bitbake's current usage. This URI class
|
||||||
supports the following:
|
supports the following:
|
||||||
@@ -199,7 +199,7 @@ class URI(object):
|
|||||||
file://hostname/absolute/path.diff (would be IETF compliant)
|
file://hostname/absolute/path.diff (would be IETF compliant)
|
||||||
|
|
||||||
Note that the last case only applies to a list of
|
Note that the last case only applies to a list of
|
||||||
explicitly allowed schemes (currently only file://), that requires
|
"whitelisted" schemes (currently only file://), that requires
|
||||||
its URIs to not have a network location.
|
its URIs to not have a network location.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ class URI(object):
|
|||||||
# to RFC compliant URL format. E.g.:
|
# to RFC compliant URL format. E.g.:
|
||||||
# file://foo.diff -> file:foo.diff
|
# file://foo.diff -> file:foo.diff
|
||||||
if urlp.scheme in self._netloc_forbidden:
|
if urlp.scheme in self._netloc_forbidden:
|
||||||
uri = re.sub(r"(?<=:)//(?!/)", "", uri, count=1)
|
uri = re.sub("(?<=:)//(?!/)", "", uri, 1)
|
||||||
reparse = 1
|
reparse = 1
|
||||||
|
|
||||||
if reparse:
|
if reparse:
|
||||||
@@ -290,12 +290,12 @@ class URI(object):
|
|||||||
|
|
||||||
def _param_str_split(self, string, elmdelim, kvdelim="="):
|
def _param_str_split(self, string, elmdelim, kvdelim="="):
|
||||||
ret = collections.OrderedDict()
|
ret = collections.OrderedDict()
|
||||||
for k, v in [x.split(kvdelim, 1) if kvdelim in x else (x, None) for x in string.split(elmdelim) if x]:
|
for k, v in [x.split(kvdelim, 1) for x in string.split(elmdelim)]:
|
||||||
ret[k] = v
|
ret[k] = v
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _param_str_join(self, dict_, elmdelim, kvdelim="="):
|
def _param_str_join(self, dict_, elmdelim, kvdelim="="):
|
||||||
return elmdelim.join([kvdelim.join([k, v]) if v else k for k, v in dict_.items()])
|
return elmdelim.join([kvdelim.join([k, v]) for k, v in dict_.items()])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hostport(self):
|
def hostport(self):
|
||||||
@@ -388,7 +388,7 @@ def decodeurl(url):
|
|||||||
if s:
|
if s:
|
||||||
if not '=' in s:
|
if not '=' in s:
|
||||||
raise MalformedUrl(url, "The URL: '%s' is invalid: parameter %s does not specify a value (missing '=')" % (url, s))
|
raise MalformedUrl(url, "The URL: '%s' is invalid: parameter %s does not specify a value (missing '=')" % (url, s))
|
||||||
s1, s2 = s.split('=', 1)
|
s1, s2 = s.split('=')
|
||||||
p[s1] = s2
|
p[s1] = s2
|
||||||
|
|
||||||
return type, host, urllib.parse.unquote(path), user, pswd, p
|
return type, host, urllib.parse.unquote(path), user, pswd, p
|
||||||
@@ -402,24 +402,24 @@ def encodeurl(decoded):
|
|||||||
|
|
||||||
if not type:
|
if not type:
|
||||||
raise MissingParameterError('type', "encoded from the data %s" % str(decoded))
|
raise MissingParameterError('type', "encoded from the data %s" % str(decoded))
|
||||||
url = ['%s://' % type]
|
url = '%s://' % type
|
||||||
if user and type != "file":
|
if user and type != "file":
|
||||||
url.append("%s" % user)
|
url += "%s" % user
|
||||||
if pswd:
|
if pswd:
|
||||||
url.append(":%s" % pswd)
|
url += ":%s" % pswd
|
||||||
url.append("@")
|
url += "@"
|
||||||
if host and type != "file":
|
if host and type != "file":
|
||||||
url.append("%s" % host)
|
url += "%s" % host
|
||||||
if path:
|
if path:
|
||||||
# Standardise path to ensure comparisons work
|
# Standardise path to ensure comparisons work
|
||||||
while '//' in path:
|
while '//' in path:
|
||||||
path = path.replace("//", "/")
|
path = path.replace("//", "/")
|
||||||
url.append("%s" % urllib.parse.quote(path))
|
url += "%s" % urllib.parse.quote(path)
|
||||||
if p:
|
if p:
|
||||||
for parm in p:
|
for parm in p:
|
||||||
url.append(";%s=%s" % (parm, p[parm]))
|
url += ";%s=%s" % (parm, p[parm])
|
||||||
|
|
||||||
return "".join(url)
|
return url
|
||||||
|
|
||||||
def uri_replace(ud, uri_find, uri_replace, replacements, d, mirrortarball=None):
|
def uri_replace(ud, uri_find, uri_replace, replacements, d, mirrortarball=None):
|
||||||
if not ud.url or not uri_find or not uri_replace:
|
if not ud.url or not uri_find or not uri_replace:
|
||||||
@@ -428,9 +428,8 @@ def uri_replace(ud, uri_find, uri_replace, replacements, d, mirrortarball=None):
|
|||||||
uri_decoded = list(decodeurl(ud.url))
|
uri_decoded = list(decodeurl(ud.url))
|
||||||
uri_find_decoded = list(decodeurl(uri_find))
|
uri_find_decoded = list(decodeurl(uri_find))
|
||||||
uri_replace_decoded = list(decodeurl(uri_replace))
|
uri_replace_decoded = list(decodeurl(uri_replace))
|
||||||
logger.debug2("For url %s comparing %s to %s" % (uri_decoded, uri_find_decoded, uri_replace_decoded))
|
logger.debug(2, "For url %s comparing %s to %s" % (uri_decoded, uri_find_decoded, uri_replace_decoded))
|
||||||
result_decoded = ['', '', '', '', '', {}]
|
result_decoded = ['', '', '', '', '', {}]
|
||||||
# 0 - type, 1 - host, 2 - path, 3 - user, 4- pswd, 5 - params
|
|
||||||
for loc, i in enumerate(uri_find_decoded):
|
for loc, i in enumerate(uri_find_decoded):
|
||||||
result_decoded[loc] = uri_decoded[loc]
|
result_decoded[loc] = uri_decoded[loc]
|
||||||
regexp = i
|
regexp = i
|
||||||
@@ -450,9 +449,6 @@ def uri_replace(ud, uri_find, uri_replace, replacements, d, mirrortarball=None):
|
|||||||
for l in replacements:
|
for l in replacements:
|
||||||
uri_replace_decoded[loc][k] = uri_replace_decoded[loc][k].replace(l, replacements[l])
|
uri_replace_decoded[loc][k] = uri_replace_decoded[loc][k].replace(l, replacements[l])
|
||||||
result_decoded[loc][k] = uri_replace_decoded[loc][k]
|
result_decoded[loc][k] = uri_replace_decoded[loc][k]
|
||||||
elif (loc == 3 or loc == 4) and uri_replace_decoded[loc]:
|
|
||||||
# User/password in the replacement is just a straight replacement
|
|
||||||
result_decoded[loc] = uri_replace_decoded[loc]
|
|
||||||
elif (re.match(regexp, uri_decoded[loc])):
|
elif (re.match(regexp, uri_decoded[loc])):
|
||||||
if not uri_replace_decoded[loc]:
|
if not uri_replace_decoded[loc]:
|
||||||
result_decoded[loc] = ""
|
result_decoded[loc] = ""
|
||||||
@@ -460,7 +456,7 @@ def uri_replace(ud, uri_find, uri_replace, replacements, d, mirrortarball=None):
|
|||||||
for k in replacements:
|
for k in replacements:
|
||||||
uri_replace_decoded[loc] = uri_replace_decoded[loc].replace(k, replacements[k])
|
uri_replace_decoded[loc] = uri_replace_decoded[loc].replace(k, replacements[k])
|
||||||
#bb.note("%s %s %s" % (regexp, uri_replace_decoded[loc], uri_decoded[loc]))
|
#bb.note("%s %s %s" % (regexp, uri_replace_decoded[loc], uri_decoded[loc]))
|
||||||
result_decoded[loc] = re.sub(regexp, uri_replace_decoded[loc], uri_decoded[loc], count=1)
|
result_decoded[loc] = re.sub(regexp, uri_replace_decoded[loc], uri_decoded[loc], 1)
|
||||||
if loc == 2:
|
if loc == 2:
|
||||||
# Handle path manipulations
|
# Handle path manipulations
|
||||||
basename = None
|
basename = None
|
||||||
@@ -469,24 +465,16 @@ def uri_replace(ud, uri_find, uri_replace, replacements, d, mirrortarball=None):
|
|||||||
basename = os.path.basename(mirrortarball)
|
basename = os.path.basename(mirrortarball)
|
||||||
# Kill parameters, they make no sense for mirror tarballs
|
# Kill parameters, they make no sense for mirror tarballs
|
||||||
uri_decoded[5] = {}
|
uri_decoded[5] = {}
|
||||||
uri_find_decoded[5] = {}
|
|
||||||
elif ud.localpath and ud.method.supports_checksum(ud):
|
elif ud.localpath and ud.method.supports_checksum(ud):
|
||||||
basename = os.path.basename(ud.localpath)
|
basename = os.path.basename(ud.localpath)
|
||||||
if basename:
|
if basename and not result_decoded[loc].endswith(basename):
|
||||||
uri_basename = os.path.basename(uri_decoded[loc])
|
result_decoded[loc] = os.path.join(result_decoded[loc], basename)
|
||||||
# Prefix with a slash as a sentinel in case
|
|
||||||
# result_decoded[loc] does not contain one.
|
|
||||||
path = "/" + result_decoded[loc]
|
|
||||||
if uri_basename and basename != uri_basename and path.endswith("/" + uri_basename):
|
|
||||||
result_decoded[loc] = path[1:-len(uri_basename)] + basename
|
|
||||||
elif not path.endswith("/" + basename):
|
|
||||||
result_decoded[loc] = os.path.join(path[1:], basename)
|
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
result = encodeurl(result_decoded)
|
result = encodeurl(result_decoded)
|
||||||
if result == ud.url:
|
if result == ud.url:
|
||||||
return None
|
return None
|
||||||
logger.debug2("For url %s returning %s" % (ud.url, result))
|
logger.debug(2, "For url %s returning %s" % (ud.url, result))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
methods = []
|
methods = []
|
||||||
@@ -499,30 +487,30 @@ def fetcher_init(d):
|
|||||||
Calls before this must not hit the cache.
|
Calls before this must not hit the cache.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with bb.persist_data.persist('BB_URI_HEADREVS', d) as revs:
|
revs = bb.persist_data.persist('BB_URI_HEADREVS', d)
|
||||||
try:
|
try:
|
||||||
# fetcher_init is called multiple times, so make sure we only save the
|
# fetcher_init is called multiple times, so make sure we only save the
|
||||||
# revs the first time it is called.
|
# revs the first time it is called.
|
||||||
if not bb.fetch2.saved_headrevs:
|
if not bb.fetch2.saved_headrevs:
|
||||||
bb.fetch2.saved_headrevs = dict(revs)
|
bb.fetch2.saved_headrevs = dict(revs)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# When to drop SCM head revisions controlled by user policy
|
# When to drop SCM head revisions controlled by user policy
|
||||||
srcrev_policy = d.getVar('BB_SRCREV_POLICY') or "clear"
|
srcrev_policy = d.getVar('BB_SRCREV_POLICY') or "clear"
|
||||||
if srcrev_policy == "cache":
|
if srcrev_policy == "cache":
|
||||||
logger.debug("Keeping SRCREV cache due to cache policy of: %s", srcrev_policy)
|
logger.debug(1, "Keeping SRCREV cache due to cache policy of: %s", srcrev_policy)
|
||||||
elif srcrev_policy == "clear":
|
elif srcrev_policy == "clear":
|
||||||
logger.debug("Clearing SRCREV cache due to cache policy of: %s", srcrev_policy)
|
logger.debug(1, "Clearing SRCREV cache due to cache policy of: %s", srcrev_policy)
|
||||||
revs.clear()
|
revs.clear()
|
||||||
else:
|
else:
|
||||||
raise FetchError("Invalid SRCREV cache policy of: %s" % srcrev_policy)
|
raise FetchError("Invalid SRCREV cache policy of: %s" % srcrev_policy)
|
||||||
|
|
||||||
_checksum_cache.init_cache(d.getVar("BB_CACHEDIR"))
|
_checksum_cache.init_cache(d)
|
||||||
|
|
||||||
for m in methods:
|
for m in methods:
|
||||||
if hasattr(m, "init"):
|
if hasattr(m, "init"):
|
||||||
m.init(d)
|
m.init(d)
|
||||||
|
|
||||||
def fetcher_parse_save():
|
def fetcher_parse_save():
|
||||||
_checksum_cache.save_extras()
|
_checksum_cache.save_extras()
|
||||||
@@ -536,8 +524,8 @@ def fetcher_compare_revisions(d):
|
|||||||
when bitbake was started and return true if they have changed.
|
when bitbake was started and return true if they have changed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with dict(bb.persist_data.persist('BB_URI_HEADREVS', d)) as headrevs:
|
headrevs = dict(bb.persist_data.persist('BB_URI_HEADREVS', d))
|
||||||
return headrevs != bb.fetch2.saved_headrevs
|
return headrevs != bb.fetch2.saved_headrevs
|
||||||
|
|
||||||
def mirror_from_string(data):
|
def mirror_from_string(data):
|
||||||
mirrors = (data or "").replace('\\n',' ').split()
|
mirrors = (data or "").replace('\\n',' ').split()
|
||||||
@@ -546,7 +534,7 @@ def mirror_from_string(data):
|
|||||||
bb.warn('Invalid mirror data %s, should have paired members.' % data)
|
bb.warn('Invalid mirror data %s, should have paired members.' % data)
|
||||||
return list(zip(*[iter(mirrors)]*2))
|
return list(zip(*[iter(mirrors)]*2))
|
||||||
|
|
||||||
def verify_checksum(ud, d, precomputed={}, localpath=None, fatal_nochecksum=True):
|
def verify_checksum(ud, d, precomputed={}):
|
||||||
"""
|
"""
|
||||||
verify the MD5 and SHA256 checksum for downloaded src
|
verify the MD5 and SHA256 checksum for downloaded src
|
||||||
|
|
||||||
@@ -560,25 +548,20 @@ def verify_checksum(ud, d, precomputed={}, localpath=None, fatal_nochecksum=True
|
|||||||
file against those in the recipe each time, rather than only after
|
file against those in the recipe each time, rather than only after
|
||||||
downloading. See https://bugzilla.yoctoproject.org/show_bug.cgi?id=5571.
|
downloading. See https://bugzilla.yoctoproject.org/show_bug.cgi?id=5571.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if ud.ignore_checksums or not ud.method.supports_checksum(ud):
|
if ud.ignore_checksums or not ud.method.supports_checksum(ud):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
if localpath is None:
|
|
||||||
localpath = ud.localpath
|
|
||||||
|
|
||||||
def compute_checksum_info(checksum_id):
|
def compute_checksum_info(checksum_id):
|
||||||
checksum_name = getattr(ud, "%s_name" % checksum_id)
|
checksum_name = getattr(ud, "%s_name" % checksum_id)
|
||||||
|
|
||||||
if checksum_id in precomputed:
|
if checksum_id in precomputed:
|
||||||
checksum_data = precomputed[checksum_id]
|
checksum_data = precomputed[checksum_id]
|
||||||
else:
|
else:
|
||||||
checksum_data = getattr(bb.utils, "%s_file" % checksum_id)(localpath)
|
checksum_data = getattr(bb.utils, "%s_file" % checksum_id)(ud.localpath)
|
||||||
|
|
||||||
checksum_expected = getattr(ud, "%s_expected" % checksum_id)
|
checksum_expected = getattr(ud, "%s_expected" % checksum_id)
|
||||||
|
|
||||||
if checksum_expected == '':
|
|
||||||
checksum_expected = None
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"id": checksum_id,
|
"id": checksum_id,
|
||||||
"name": checksum_name,
|
"name": checksum_name,
|
||||||
@@ -598,13 +581,17 @@ def verify_checksum(ud, d, precomputed={}, localpath=None, fatal_nochecksum=True
|
|||||||
checksum_lines = ["SRC_URI[%s] = \"%s\"" % (ci["name"], ci["data"])]
|
checksum_lines = ["SRC_URI[%s] = \"%s\"" % (ci["name"], ci["data"])]
|
||||||
|
|
||||||
# If no checksum has been provided
|
# If no checksum has been provided
|
||||||
if fatal_nochecksum and ud.method.recommends_checksum(ud) and all(ci["expected"] is None for ci in checksum_infos):
|
if ud.method.recommends_checksum(ud) and all(ci["expected"] is None for ci in checksum_infos):
|
||||||
messages = []
|
messages = []
|
||||||
strict = d.getVar("BB_STRICT_CHECKSUM") or "0"
|
strict = d.getVar("BB_STRICT_CHECKSUM") or "0"
|
||||||
|
|
||||||
# If strict checking enabled and neither sum defined, raise error
|
# If strict checking enabled and neither sum defined, raise error
|
||||||
if strict == "1":
|
if strict == "1":
|
||||||
raise NoChecksumError("\n".join(checksum_lines))
|
messages.append("No checksum specified for '%s', please add at " \
|
||||||
|
"least one to the recipe:" % ud.localpath)
|
||||||
|
messages.extend(checksum_lines)
|
||||||
|
logger.error("\n".join(messages))
|
||||||
|
raise NoChecksumError("Missing SRC_URI checksum", ud.url)
|
||||||
|
|
||||||
bb.event.fire(MissingChecksumEvent(ud.url, **checksum_event), d)
|
bb.event.fire(MissingChecksumEvent(ud.url, **checksum_event), d)
|
||||||
|
|
||||||
@@ -625,8 +612,8 @@ def verify_checksum(ud, d, precomputed={}, localpath=None, fatal_nochecksum=True
|
|||||||
|
|
||||||
for ci in checksum_infos:
|
for ci in checksum_infos:
|
||||||
if ci["expected"] and ci["expected"] != ci["data"]:
|
if ci["expected"] and ci["expected"] != ci["data"]:
|
||||||
messages.append("File: '%s' has %s checksum '%s' when '%s' was " \
|
messages.append("File: '%s' has %s checksum %s when %s was " \
|
||||||
"expected" % (localpath, ci["id"], ci["data"], ci["expected"]))
|
"expected" % (ud.localpath, ci["id"], ci["data"], ci["expected"]))
|
||||||
bad_checksum = ci["data"]
|
bad_checksum = ci["data"]
|
||||||
|
|
||||||
if bad_checksum:
|
if bad_checksum:
|
||||||
@@ -744,16 +731,13 @@ def subprocess_setup():
|
|||||||
# SIGPIPE errors are known issues with gzip/bash
|
# SIGPIPE errors are known issues with gzip/bash
|
||||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||||
|
|
||||||
def mark_recipe_nocache(d):
|
def get_autorev(d):
|
||||||
|
# only not cache src rev in autorev case
|
||||||
if d.getVar('BB_SRCREV_POLICY') != "cache":
|
if d.getVar('BB_SRCREV_POLICY') != "cache":
|
||||||
d.setVar('BB_DONT_CACHE', '1')
|
d.setVar('BB_DONT_CACHE', '1')
|
||||||
|
|
||||||
def get_autorev(d):
|
|
||||||
mark_recipe_nocache(d)
|
|
||||||
d.setVar("__BBAUTOREV_SEEN", True)
|
|
||||||
return "AUTOINC"
|
return "AUTOINC"
|
||||||
|
|
||||||
def _get_srcrev(d, method_name='sortable_revision'):
|
def get_srcrev(d, method_name='sortable_revision'):
|
||||||
"""
|
"""
|
||||||
Return the revision string, usually for use in the version string (PV) of the current package
|
Return the revision string, usually for use in the version string (PV) of the current package
|
||||||
Most packages usually only have one SCM so we just pass on the call.
|
Most packages usually only have one SCM so we just pass on the call.
|
||||||
@@ -767,34 +751,23 @@ def _get_srcrev(d, method_name='sortable_revision'):
|
|||||||
that fetcher provides a method with the given name and the same signature as sortable_revision.
|
that fetcher provides a method with the given name and the same signature as sortable_revision.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
d.setVar("__BBSRCREV_SEEN", "1")
|
|
||||||
recursion = d.getVar("__BBINSRCREV")
|
|
||||||
if recursion:
|
|
||||||
raise FetchError("There are recursive references in fetcher variables, likely through SRC_URI")
|
|
||||||
d.setVar("__BBINSRCREV", True)
|
|
||||||
|
|
||||||
scms = []
|
scms = []
|
||||||
revs = []
|
|
||||||
fetcher = Fetch(d.getVar('SRC_URI').split(), d)
|
fetcher = Fetch(d.getVar('SRC_URI').split(), d)
|
||||||
urldata = fetcher.ud
|
urldata = fetcher.ud
|
||||||
for u in urldata:
|
for u in urldata:
|
||||||
if urldata[u].method.supports_srcrev():
|
if urldata[u].method.supports_srcrev():
|
||||||
scms.append(u)
|
scms.append(u)
|
||||||
|
|
||||||
if not scms:
|
if len(scms) == 0:
|
||||||
d.delVar("__BBINSRCREV")
|
raise FetchError("SRCREV was used yet no valid SCM was found in SRC_URI")
|
||||||
return "", revs
|
|
||||||
|
|
||||||
|
|
||||||
if len(scms) == 1 and len(urldata[scms[0]].names) == 1:
|
if len(scms) == 1 and len(urldata[scms[0]].names) == 1:
|
||||||
autoinc, rev = getattr(urldata[scms[0]].method, method_name)(urldata[scms[0]], d, urldata[scms[0]].names[0])
|
autoinc, rev = getattr(urldata[scms[0]].method, method_name)(urldata[scms[0]], d, urldata[scms[0]].names[0])
|
||||||
revs.append(rev)
|
|
||||||
if len(rev) > 10:
|
if len(rev) > 10:
|
||||||
rev = rev[:10]
|
rev = rev[:10]
|
||||||
d.delVar("__BBINSRCREV")
|
|
||||||
if autoinc:
|
if autoinc:
|
||||||
return "AUTOINC+" + rev, revs
|
return "AUTOINC+" + rev
|
||||||
return rev, revs
|
return rev
|
||||||
|
|
||||||
#
|
#
|
||||||
# Mutiple SCMs are in SRC_URI so we resort to SRCREV_FORMAT
|
# Mutiple SCMs are in SRC_URI so we resort to SRCREV_FORMAT
|
||||||
@@ -810,7 +783,6 @@ def _get_srcrev(d, method_name='sortable_revision'):
|
|||||||
ud = urldata[scm]
|
ud = urldata[scm]
|
||||||
for name in ud.names:
|
for name in ud.names:
|
||||||
autoinc, rev = getattr(ud.method, method_name)(ud, d, name)
|
autoinc, rev = getattr(ud.method, method_name)(ud, d, name)
|
||||||
revs.append(rev)
|
|
||||||
seenautoinc = seenautoinc or autoinc
|
seenautoinc = seenautoinc or autoinc
|
||||||
if len(rev) > 10:
|
if len(rev) > 10:
|
||||||
rev = rev[:10]
|
rev = rev[:10]
|
||||||
@@ -827,70 +799,12 @@ def _get_srcrev(d, method_name='sortable_revision'):
|
|||||||
if seenautoinc:
|
if seenautoinc:
|
||||||
format = "AUTOINC+" + format
|
format = "AUTOINC+" + format
|
||||||
|
|
||||||
d.delVar("__BBINSRCREV")
|
return format
|
||||||
return format, revs
|
|
||||||
|
|
||||||
def get_hashvalue(d, method_name='sortable_revision'):
|
|
||||||
pkgv, revs = _get_srcrev(d, method_name=method_name)
|
|
||||||
return " ".join(revs)
|
|
||||||
|
|
||||||
def get_pkgv_string(d, method_name='sortable_revision'):
|
|
||||||
pkgv, revs = _get_srcrev(d, method_name=method_name)
|
|
||||||
return pkgv
|
|
||||||
|
|
||||||
def get_srcrev(d, method_name='sortable_revision'):
|
|
||||||
pkgv, revs = _get_srcrev(d, method_name=method_name)
|
|
||||||
if not pkgv:
|
|
||||||
raise FetchError("SRCREV was used yet no valid SCM was found in SRC_URI")
|
|
||||||
return pkgv
|
|
||||||
|
|
||||||
def localpath(url, d):
|
def localpath(url, d):
|
||||||
fetcher = bb.fetch2.Fetch([url], d)
|
fetcher = bb.fetch2.Fetch([url], d)
|
||||||
return fetcher.localpath(url)
|
return fetcher.localpath(url)
|
||||||
|
|
||||||
# Need to export PATH as binary could be in metadata paths
|
|
||||||
# rather than host provided
|
|
||||||
# Also include some other variables.
|
|
||||||
FETCH_EXPORT_VARS = ['HOME', 'PATH',
|
|
||||||
'HTTP_PROXY', 'http_proxy',
|
|
||||||
'HTTPS_PROXY', 'https_proxy',
|
|
||||||
'FTP_PROXY', 'ftp_proxy',
|
|
||||||
'FTPS_PROXY', 'ftps_proxy',
|
|
||||||
'NO_PROXY', 'no_proxy',
|
|
||||||
'ALL_PROXY', 'all_proxy',
|
|
||||||
'GIT_PROXY_COMMAND',
|
|
||||||
'GIT_SSH',
|
|
||||||
'GIT_SSH_COMMAND',
|
|
||||||
'GIT_SSL_CAINFO',
|
|
||||||
'GIT_SMART_HTTP',
|
|
||||||
'SSH_AUTH_SOCK', 'SSH_AGENT_PID',
|
|
||||||
'SOCKS5_USER', 'SOCKS5_PASSWD',
|
|
||||||
'DBUS_SESSION_BUS_ADDRESS',
|
|
||||||
'P4CONFIG',
|
|
||||||
'SSL_CERT_FILE',
|
|
||||||
'NODE_EXTRA_CA_CERTS',
|
|
||||||
'AWS_PROFILE',
|
|
||||||
'AWS_ACCESS_KEY_ID',
|
|
||||||
'AWS_SECRET_ACCESS_KEY',
|
|
||||||
'AWS_ROLE_ARN',
|
|
||||||
'AWS_WEB_IDENTITY_TOKEN_FILE',
|
|
||||||
'AWS_DEFAULT_REGION',
|
|
||||||
'AWS_SESSION_TOKEN',
|
|
||||||
'GIT_CACHE_PATH',
|
|
||||||
'REMOTE_CONTAINERS_IPC',
|
|
||||||
'SSL_CERT_DIR']
|
|
||||||
|
|
||||||
def get_fetcher_environment(d):
|
|
||||||
newenv = {}
|
|
||||||
origenv = d.getVar("BB_ORIGENV")
|
|
||||||
for name in bb.fetch2.FETCH_EXPORT_VARS:
|
|
||||||
value = d.getVar(name)
|
|
||||||
if not value and origenv:
|
|
||||||
value = origenv.getVar(name)
|
|
||||||
if value:
|
|
||||||
newenv[name] = value
|
|
||||||
return newenv
|
|
||||||
|
|
||||||
def runfetchcmd(cmd, d, quiet=False, cleanup=None, log=None, workdir=None):
|
def runfetchcmd(cmd, d, quiet=False, cleanup=None, log=None, workdir=None):
|
||||||
"""
|
"""
|
||||||
Run cmd returning the command output
|
Run cmd returning the command output
|
||||||
@@ -899,7 +813,25 @@ def runfetchcmd(cmd, d, quiet=False, cleanup=None, log=None, workdir=None):
|
|||||||
Optionally remove the files/directories listed in cleanup upon failure
|
Optionally remove the files/directories listed in cleanup upon failure
|
||||||
"""
|
"""
|
||||||
|
|
||||||
exportvars = FETCH_EXPORT_VARS
|
# Need to export PATH as binary could be in metadata paths
|
||||||
|
# rather than host provided
|
||||||
|
# Also include some other variables.
|
||||||
|
# FIXME: Should really include all export varaiables?
|
||||||
|
exportvars = ['HOME', 'PATH',
|
||||||
|
'HTTP_PROXY', 'http_proxy',
|
||||||
|
'HTTPS_PROXY', 'https_proxy',
|
||||||
|
'FTP_PROXY', 'ftp_proxy',
|
||||||
|
'FTPS_PROXY', 'ftps_proxy',
|
||||||
|
'NO_PROXY', 'no_proxy',
|
||||||
|
'ALL_PROXY', 'all_proxy',
|
||||||
|
'GIT_PROXY_COMMAND',
|
||||||
|
'GIT_SSH',
|
||||||
|
'GIT_SSL_CAINFO',
|
||||||
|
'GIT_SMART_HTTP',
|
||||||
|
'SSH_AUTH_SOCK', 'SSH_AGENT_PID',
|
||||||
|
'SOCKS5_USER', 'SOCKS5_PASSWD',
|
||||||
|
'DBUS_SESSION_BUS_ADDRESS',
|
||||||
|
'P4CONFIG']
|
||||||
|
|
||||||
if not cleanup:
|
if not cleanup:
|
||||||
cleanup = []
|
cleanup = []
|
||||||
@@ -925,9 +857,9 @@ def runfetchcmd(cmd, d, quiet=False, cleanup=None, log=None, workdir=None):
|
|||||||
cmd = 'export PSEUDO_DISABLED=1; ' + cmd
|
cmd = 'export PSEUDO_DISABLED=1; ' + cmd
|
||||||
|
|
||||||
if workdir:
|
if workdir:
|
||||||
logger.debug("Running '%s' in %s" % (cmd, workdir))
|
logger.debug(1, "Running '%s' in %s" % (cmd, workdir))
|
||||||
else:
|
else:
|
||||||
logger.debug("Running %s", cmd)
|
logger.debug(1, "Running %s", cmd)
|
||||||
|
|
||||||
success = False
|
success = False
|
||||||
error_message = ""
|
error_message = ""
|
||||||
@@ -936,17 +868,14 @@ def runfetchcmd(cmd, d, quiet=False, cleanup=None, log=None, workdir=None):
|
|||||||
(output, errors) = bb.process.run(cmd, log=log, shell=True, stderr=subprocess.PIPE, cwd=workdir)
|
(output, errors) = bb.process.run(cmd, log=log, shell=True, stderr=subprocess.PIPE, cwd=workdir)
|
||||||
success = True
|
success = True
|
||||||
except bb.process.NotFoundError as e:
|
except bb.process.NotFoundError as e:
|
||||||
error_message = "Fetch command %s not found" % (e.command)
|
error_message = "Fetch command %s" % (e.command)
|
||||||
except bb.process.ExecutionError as e:
|
except bb.process.ExecutionError as e:
|
||||||
if e.stdout:
|
if e.stdout:
|
||||||
output = "output:\n%s\n%s" % (e.stdout, e.stderr)
|
output = "output:\n%s\n%s" % (e.stdout, e.stderr)
|
||||||
elif e.stderr:
|
elif e.stderr:
|
||||||
output = "output:\n%s" % e.stderr
|
output = "output:\n%s" % e.stderr
|
||||||
else:
|
else:
|
||||||
if log:
|
output = "no output"
|
||||||
output = "see logfile for output"
|
|
||||||
else:
|
|
||||||
output = "no output"
|
|
||||||
error_message = "Fetch command %s failed with exit code %s, %s" % (e.command, e.exitcode, output)
|
error_message = "Fetch command %s failed with exit code %s, %s" % (e.command, e.exitcode, output)
|
||||||
except bb.process.CmdError as e:
|
except bb.process.CmdError as e:
|
||||||
error_message = "Fetch command %s could not be run:\n%s" % (e.command, e.msg)
|
error_message = "Fetch command %s could not be run:\n%s" % (e.command, e.msg)
|
||||||
@@ -971,7 +900,7 @@ def check_network_access(d, info, url):
|
|||||||
elif not trusted_network(d, url):
|
elif not trusted_network(d, url):
|
||||||
raise UntrustedUrl(url, info)
|
raise UntrustedUrl(url, info)
|
||||||
else:
|
else:
|
||||||
logger.debug("Fetcher accessed the network with the command %s" % info)
|
logger.debug(1, "Fetcher accessed the network with the command %s" % info)
|
||||||
|
|
||||||
def build_mirroruris(origud, mirrors, ld):
|
def build_mirroruris(origud, mirrors, ld):
|
||||||
uris = []
|
uris = []
|
||||||
@@ -997,7 +926,7 @@ def build_mirroruris(origud, mirrors, ld):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if not trusted_network(ld, newuri):
|
if not trusted_network(ld, newuri):
|
||||||
logger.debug("Mirror %s not in the list of trusted networks, skipping" % (newuri))
|
logger.debug(1, "Mirror %s not in the list of trusted networks, skipping" % (newuri))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Create a local copy of the mirrors minus the current line
|
# Create a local copy of the mirrors minus the current line
|
||||||
@@ -1008,11 +937,10 @@ def build_mirroruris(origud, mirrors, ld):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
newud = FetchData(newuri, ld)
|
newud = FetchData(newuri, ld)
|
||||||
newud.ignore_checksums = True
|
|
||||||
newud.setup_localpath(ld)
|
newud.setup_localpath(ld)
|
||||||
except bb.fetch2.BBFetchException as e:
|
except bb.fetch2.BBFetchException as e:
|
||||||
logger.debug("Mirror fetch failure for url %s (original url: %s)" % (newuri, origud.url))
|
logger.debug(1, "Mirror fetch failure for url %s (original url: %s)" % (newuri, origud.url))
|
||||||
logger.debug(str(e))
|
logger.debug(1, str(e))
|
||||||
try:
|
try:
|
||||||
# setup_localpath of file:// urls may fail, we should still see
|
# setup_localpath of file:// urls may fail, we should still see
|
||||||
# if mirrors of the url exist
|
# if mirrors of the url exist
|
||||||
@@ -1115,11 +1043,10 @@ def try_mirror_url(fetch, origud, ud, ld, check = False):
|
|||||||
elif isinstance(e, NoChecksumError):
|
elif isinstance(e, NoChecksumError):
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
logger.debug("Mirror fetch failure for url %s (original url: %s)" % (ud.url, origud.url))
|
logger.debug(1, "Mirror fetch failure for url %s (original url: %s)" % (ud.url, origud.url))
|
||||||
logger.debug(str(e))
|
logger.debug(1, str(e))
|
||||||
try:
|
try:
|
||||||
if ud.method.cleanup_upon_failure():
|
ud.method.clean(ud, ld)
|
||||||
ud.method.clean(ud, ld)
|
|
||||||
except UnboundLocalError:
|
except UnboundLocalError:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
@@ -1130,8 +1057,6 @@ def try_mirror_url(fetch, origud, ud, ld, check = False):
|
|||||||
|
|
||||||
def ensure_symlink(target, link_name):
|
def ensure_symlink(target, link_name):
|
||||||
if not os.path.exists(link_name):
|
if not os.path.exists(link_name):
|
||||||
dirname = os.path.dirname(link_name)
|
|
||||||
bb.utils.mkdirhier(dirname)
|
|
||||||
if os.path.islink(link_name):
|
if os.path.islink(link_name):
|
||||||
# Broken symbolic link
|
# Broken symbolic link
|
||||||
os.unlink(link_name)
|
os.unlink(link_name)
|
||||||
@@ -1215,11 +1140,11 @@ def srcrev_internal_helper(ud, d, name):
|
|||||||
pn = d.getVar("PN")
|
pn = d.getVar("PN")
|
||||||
attempts = []
|
attempts = []
|
||||||
if name != '' and pn:
|
if name != '' and pn:
|
||||||
attempts.append("SRCREV_%s:pn-%s" % (name, pn))
|
attempts.append("SRCREV_%s_pn-%s" % (name, pn))
|
||||||
if name != '':
|
if name != '':
|
||||||
attempts.append("SRCREV_%s" % name)
|
attempts.append("SRCREV_%s" % name)
|
||||||
if pn:
|
if pn:
|
||||||
attempts.append("SRCREV:pn-%s" % pn)
|
attempts.append("SRCREV_pn-%s" % pn)
|
||||||
attempts.append("SRCREV")
|
attempts.append("SRCREV")
|
||||||
|
|
||||||
for a in attempts:
|
for a in attempts:
|
||||||
@@ -1244,7 +1169,6 @@ def srcrev_internal_helper(ud, d, name):
|
|||||||
if srcrev == "INVALID" or not srcrev:
|
if srcrev == "INVALID" or not srcrev:
|
||||||
raise FetchError("Please set a valid SRCREV for url %s (possible key names are %s, or use a ;rev=X URL parameter)" % (str(attempts), ud.url), ud.url)
|
raise FetchError("Please set a valid SRCREV for url %s (possible key names are %s, or use a ;rev=X URL parameter)" % (str(attempts), ud.url), ud.url)
|
||||||
if srcrev == "AUTOINC":
|
if srcrev == "AUTOINC":
|
||||||
d.setVar("__BBAUTOREV_ACTED_UPON", True)
|
|
||||||
srcrev = ud.method.latest_revision(ud, d, name)
|
srcrev = ud.method.latest_revision(ud, d, name)
|
||||||
|
|
||||||
return srcrev
|
return srcrev
|
||||||
@@ -1256,21 +1180,23 @@ def get_checksum_file_list(d):
|
|||||||
SRC_URI as a space-separated string
|
SRC_URI as a space-separated string
|
||||||
"""
|
"""
|
||||||
fetch = Fetch([], d, cache = False, localonly = True)
|
fetch = Fetch([], d, cache = False, localonly = True)
|
||||||
|
|
||||||
|
dl_dir = d.getVar('DL_DIR')
|
||||||
filelist = []
|
filelist = []
|
||||||
for u in fetch.urls:
|
for u in fetch.urls:
|
||||||
ud = fetch.ud[u]
|
ud = fetch.ud[u]
|
||||||
|
|
||||||
if ud and isinstance(ud.method, local.Local):
|
if ud and isinstance(ud.method, local.Local):
|
||||||
found = False
|
paths = ud.method.localpaths(ud, d)
|
||||||
paths = ud.method.localfile_searchpaths(ud, d)
|
|
||||||
for f in paths:
|
for f in paths:
|
||||||
pth = ud.decodedurl
|
pth = ud.decodedurl
|
||||||
if os.path.exists(f):
|
if f.startswith(dl_dir):
|
||||||
found = True
|
# The local fetcher's behaviour is to return a path under DL_DIR if it couldn't find the file anywhere else
|
||||||
|
if os.path.exists(f):
|
||||||
|
bb.warn("Getting checksum for %s SRC_URI entry %s: file not found except in DL_DIR" % (d.getVar('PN'), os.path.basename(f)))
|
||||||
|
else:
|
||||||
|
bb.warn("Unable to get checksum for %s SRC_URI entry %s: file could not be found" % (d.getVar('PN'), os.path.basename(f)))
|
||||||
filelist.append(f + ":" + str(os.path.exists(f)))
|
filelist.append(f + ":" + str(os.path.exists(f)))
|
||||||
if not found:
|
|
||||||
bb.fatal(("Unable to get checksum for %s SRC_URI entry %s: file could not be found"
|
|
||||||
"\nThe following paths were searched:"
|
|
||||||
"\n%s") % (d.getVar('PN'), os.path.basename(f), '\n'.join(paths)))
|
|
||||||
|
|
||||||
return " ".join(filelist)
|
return " ".join(filelist)
|
||||||
|
|
||||||
@@ -1317,13 +1243,18 @@ class FetchData(object):
|
|||||||
|
|
||||||
if checksum_name in self.parm:
|
if checksum_name in self.parm:
|
||||||
checksum_expected = self.parm[checksum_name]
|
checksum_expected = self.parm[checksum_name]
|
||||||
elif self.type not in ["http", "https", "ftp", "ftps", "sftp", "s3", "az", "crate", "gs"]:
|
elif self.type not in ["http", "https", "ftp", "ftps", "sftp", "s3"]:
|
||||||
checksum_expected = None
|
checksum_expected = None
|
||||||
else:
|
else:
|
||||||
checksum_expected = d.getVarFlag("SRC_URI", checksum_name)
|
checksum_expected = d.getVarFlag("SRC_URI", checksum_name)
|
||||||
|
|
||||||
setattr(self, "%s_expected" % checksum_id, checksum_expected)
|
setattr(self, "%s_expected" % checksum_id, checksum_expected)
|
||||||
|
|
||||||
|
for checksum_id in CHECKSUM_LIST:
|
||||||
|
configure_checksum(checksum_id)
|
||||||
|
|
||||||
|
self.ignore_checksums = False
|
||||||
|
|
||||||
self.names = self.parm.get("name",'default').split(',')
|
self.names = self.parm.get("name",'default').split(',')
|
||||||
|
|
||||||
self.method = None
|
self.method = None
|
||||||
@@ -1345,11 +1276,6 @@ class FetchData(object):
|
|||||||
if hasattr(self.method, "urldata_init"):
|
if hasattr(self.method, "urldata_init"):
|
||||||
self.method.urldata_init(self, d)
|
self.method.urldata_init(self, d)
|
||||||
|
|
||||||
for checksum_id in CHECKSUM_LIST:
|
|
||||||
configure_checksum(checksum_id)
|
|
||||||
|
|
||||||
self.ignore_checksums = False
|
|
||||||
|
|
||||||
if "localpath" in self.parm:
|
if "localpath" in self.parm:
|
||||||
# if user sets localpath for file, use it instead.
|
# if user sets localpath for file, use it instead.
|
||||||
self.localpath = self.parm["localpath"]
|
self.localpath = self.parm["localpath"]
|
||||||
@@ -1429,9 +1355,6 @@ class FetchMethod(object):
|
|||||||
Is localpath something that can be represented by a checksum?
|
Is localpath something that can be represented by a checksum?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# We cannot compute checksums for None
|
|
||||||
if urldata.localpath is None:
|
|
||||||
return False
|
|
||||||
# We cannot compute checksums for directories
|
# We cannot compute checksums for directories
|
||||||
if os.path.isdir(urldata.localpath):
|
if os.path.isdir(urldata.localpath):
|
||||||
return False
|
return False
|
||||||
@@ -1444,12 +1367,6 @@ class FetchMethod(object):
|
|||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def cleanup_upon_failure(self):
|
|
||||||
"""
|
|
||||||
When a fetch fails, should clean() be called?
|
|
||||||
"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
def verify_donestamp(self, ud, d):
|
def verify_donestamp(self, ud, d):
|
||||||
"""
|
"""
|
||||||
Verify the donestamp file
|
Verify the donestamp file
|
||||||
@@ -1517,35 +1434,28 @@ class FetchMethod(object):
|
|||||||
cmd = None
|
cmd = None
|
||||||
|
|
||||||
if unpack:
|
if unpack:
|
||||||
tar_cmd = 'tar --extract --no-same-owner'
|
|
||||||
if 'striplevel' in urldata.parm:
|
|
||||||
tar_cmd += ' --strip-components=%s' % urldata.parm['striplevel']
|
|
||||||
if file.endswith('.tar'):
|
if file.endswith('.tar'):
|
||||||
cmd = '%s -f %s' % (tar_cmd, file)
|
cmd = 'tar x --no-same-owner -f %s' % file
|
||||||
elif file.endswith('.tgz') or file.endswith('.tar.gz') or file.endswith('.tar.Z'):
|
elif file.endswith('.tgz') or file.endswith('.tar.gz') or file.endswith('.tar.Z'):
|
||||||
cmd = '%s -z -f %s' % (tar_cmd, file)
|
cmd = 'tar xz --no-same-owner -f %s' % file
|
||||||
elif file.endswith('.tbz') or file.endswith('.tbz2') or file.endswith('.tar.bz2'):
|
elif file.endswith('.tbz') or file.endswith('.tbz2') or file.endswith('.tar.bz2'):
|
||||||
cmd = 'bzip2 -dc %s | %s -f -' % (file, tar_cmd)
|
cmd = 'bzip2 -dc %s | tar x --no-same-owner -f -' % file
|
||||||
elif file.endswith('.gz') or file.endswith('.Z') or file.endswith('.z'):
|
elif file.endswith('.gz') or file.endswith('.Z') or file.endswith('.z'):
|
||||||
cmd = 'gzip -dc %s > %s' % (file, efile)
|
cmd = 'gzip -dc %s > %s' % (file, efile)
|
||||||
elif file.endswith('.bz2'):
|
elif file.endswith('.bz2'):
|
||||||
cmd = 'bzip2 -dc %s > %s' % (file, efile)
|
cmd = 'bzip2 -dc %s > %s' % (file, efile)
|
||||||
elif file.endswith('.txz') or file.endswith('.tar.xz'):
|
elif file.endswith('.txz') or file.endswith('.tar.xz'):
|
||||||
cmd = 'xz -dc %s | %s -f -' % (file, tar_cmd)
|
cmd = 'xz -dc %s | tar x --no-same-owner -f -' % file
|
||||||
elif file.endswith('.xz'):
|
elif file.endswith('.xz'):
|
||||||
cmd = 'xz -dc %s > %s' % (file, efile)
|
cmd = 'xz -dc %s > %s' % (file, efile)
|
||||||
elif file.endswith('.tar.lz'):
|
elif file.endswith('.tar.lz'):
|
||||||
cmd = 'lzip -dc %s | %s -f -' % (file, tar_cmd)
|
cmd = 'lzip -dc %s | tar x --no-same-owner -f -' % file
|
||||||
elif file.endswith('.lz'):
|
elif file.endswith('.lz'):
|
||||||
cmd = 'lzip -dc %s > %s' % (file, efile)
|
cmd = 'lzip -dc %s > %s' % (file, efile)
|
||||||
elif file.endswith('.tar.7z'):
|
elif file.endswith('.tar.7z'):
|
||||||
cmd = '7z x -so %s | %s -f -' % (file, tar_cmd)
|
cmd = '7z x -so %s | tar x --no-same-owner -f -' % file
|
||||||
elif file.endswith('.7z'):
|
elif file.endswith('.7z'):
|
||||||
cmd = '7za x -y %s 1>/dev/null' % file
|
cmd = '7za x -y %s 1>/dev/null' % file
|
||||||
elif file.endswith('.tzst') or file.endswith('.tar.zst'):
|
|
||||||
cmd = 'zstd --decompress --stdout %s | %s -f -' % (file, tar_cmd)
|
|
||||||
elif file.endswith('.zst'):
|
|
||||||
cmd = 'zstd --decompress --stdout %s > %s' % (file, efile)
|
|
||||||
elif file.endswith('.zip') or file.endswith('.jar'):
|
elif file.endswith('.zip') or file.endswith('.jar'):
|
||||||
try:
|
try:
|
||||||
dos = bb.utils.to_boolean(urldata.parm.get('dos'), False)
|
dos = bb.utils.to_boolean(urldata.parm.get('dos'), False)
|
||||||
@@ -1576,7 +1486,7 @@ class FetchMethod(object):
|
|||||||
raise UnpackError("Unable to unpack deb/ipk package - does not contain data.tar.* file", urldata.url)
|
raise UnpackError("Unable to unpack deb/ipk package - does not contain data.tar.* file", urldata.url)
|
||||||
else:
|
else:
|
||||||
raise UnpackError("Unable to unpack deb/ipk package - could not list contents", urldata.url)
|
raise UnpackError("Unable to unpack deb/ipk package - could not list contents", urldata.url)
|
||||||
cmd = 'ar x %s %s && %s -p -f %s && rm %s' % (file, datafile, tar_cmd, datafile, datafile)
|
cmd = 'ar x %s %s && tar --no-same-owner -xpf %s && rm %s' % (file, datafile, datafile, datafile)
|
||||||
|
|
||||||
# If 'subdir' param exists, create a dir and use it as destination for unpack cmd
|
# If 'subdir' param exists, create a dir and use it as destination for unpack cmd
|
||||||
if 'subdir' in urldata.parm:
|
if 'subdir' in urldata.parm:
|
||||||
@@ -1592,7 +1502,6 @@ class FetchMethod(object):
|
|||||||
unpackdir = rootdir
|
unpackdir = rootdir
|
||||||
|
|
||||||
if not unpack or not cmd:
|
if not unpack or not cmd:
|
||||||
urldata.unpack_tracer.unpack("file-copy", unpackdir)
|
|
||||||
# If file == dest, then avoid any copies, as we already put the file into dest!
|
# If file == dest, then avoid any copies, as we already put the file into dest!
|
||||||
dest = os.path.join(unpackdir, os.path.basename(file))
|
dest = os.path.join(unpackdir, os.path.basename(file))
|
||||||
if file != dest and not (os.path.exists(dest) and os.path.samefile(file, dest)):
|
if file != dest and not (os.path.exists(dest) and os.path.samefile(file, dest)):
|
||||||
@@ -1607,8 +1516,6 @@ class FetchMethod(object):
|
|||||||
destdir = urlpath.rsplit("/", 1)[0] + '/'
|
destdir = urlpath.rsplit("/", 1)[0] + '/'
|
||||||
bb.utils.mkdirhier("%s/%s" % (unpackdir, destdir))
|
bb.utils.mkdirhier("%s/%s" % (unpackdir, destdir))
|
||||||
cmd = 'cp -fpPRH "%s" "%s"' % (file, destdir)
|
cmd = 'cp -fpPRH "%s" "%s"' % (file, destdir)
|
||||||
else:
|
|
||||||
urldata.unpack_tracer.unpack("archive-extract", unpackdir)
|
|
||||||
|
|
||||||
if not cmd:
|
if not cmd:
|
||||||
return
|
return
|
||||||
@@ -1662,13 +1569,13 @@ class FetchMethod(object):
|
|||||||
if not hasattr(self, "_latest_revision"):
|
if not hasattr(self, "_latest_revision"):
|
||||||
raise ParameterError("The fetcher for this URL does not support _latest_revision", ud.url)
|
raise ParameterError("The fetcher for this URL does not support _latest_revision", ud.url)
|
||||||
|
|
||||||
with bb.persist_data.persist('BB_URI_HEADREVS', d) as revs:
|
revs = bb.persist_data.persist('BB_URI_HEADREVS', d)
|
||||||
key = self.generate_revision_key(ud, d, name)
|
key = self.generate_revision_key(ud, d, name)
|
||||||
try:
|
try:
|
||||||
return revs[key]
|
return revs[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
revs[key] = rev = self._latest_revision(ud, d, name)
|
revs[key] = rev = self._latest_revision(ud, d, name)
|
||||||
return rev
|
return rev
|
||||||
|
|
||||||
def sortable_revision(self, ud, d, name):
|
def sortable_revision(self, ud, d, name):
|
||||||
latest_rev = self._build_revision(ud, d, name)
|
latest_rev = self._build_revision(ud, d, name)
|
||||||
@@ -1700,61 +1607,12 @@ class FetchMethod(object):
|
|||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
class DummyUnpackTracer(object):
|
|
||||||
"""
|
|
||||||
Abstract API definition for a class that traces unpacked source files back
|
|
||||||
to their respective upstream SRC_URI entries, for software composition
|
|
||||||
analysis, license compliance and detailed SBOM generation purposes.
|
|
||||||
User may load their own unpack tracer class (instead of the dummy
|
|
||||||
one) by setting the BB_UNPACK_TRACER_CLASS config parameter.
|
|
||||||
"""
|
|
||||||
def start(self, unpackdir, urldata_dict, d):
|
|
||||||
"""
|
|
||||||
Start tracing the core Fetch.unpack process, using an index to map
|
|
||||||
unpacked files to each SRC_URI entry.
|
|
||||||
This method is called by Fetch.unpack and it may receive nested calls by
|
|
||||||
gitsm and npmsw fetchers, that expand SRC_URI entries by adding implicit
|
|
||||||
URLs and by recursively calling Fetch.unpack from new (nested) Fetch
|
|
||||||
instances.
|
|
||||||
"""
|
|
||||||
return
|
|
||||||
def start_url(self, url):
|
|
||||||
"""Start tracing url unpack process.
|
|
||||||
This method is called by Fetch.unpack before the fetcher-specific unpack
|
|
||||||
method starts, and it may receive nested calls by gitsm and npmsw
|
|
||||||
fetchers.
|
|
||||||
"""
|
|
||||||
return
|
|
||||||
def unpack(self, unpack_type, destdir):
|
|
||||||
"""
|
|
||||||
Set unpack_type and destdir for current url.
|
|
||||||
This method is called by the fetcher-specific unpack method after url
|
|
||||||
tracing started.
|
|
||||||
"""
|
|
||||||
return
|
|
||||||
def finish_url(self, url):
|
|
||||||
"""Finish tracing url unpack process and update the file index.
|
|
||||||
This method is called by Fetch.unpack after the fetcher-specific unpack
|
|
||||||
method finished its job, and it may receive nested calls by gitsm
|
|
||||||
and npmsw fetchers.
|
|
||||||
"""
|
|
||||||
return
|
|
||||||
def complete(self):
|
|
||||||
"""
|
|
||||||
Finish tracing the Fetch.unpack process, and check if all nested
|
|
||||||
Fecth.unpack calls (if any) have been completed; if so, save collected
|
|
||||||
metadata.
|
|
||||||
"""
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
class Fetch(object):
|
class Fetch(object):
|
||||||
def __init__(self, urls, d, cache = True, localonly = False, connection_cache = None):
|
def __init__(self, urls, d, cache = True, localonly = False, connection_cache = None):
|
||||||
if localonly and cache:
|
if localonly and cache:
|
||||||
raise Exception("bb.fetch2.Fetch.__init__: cannot set cache and localonly at same time")
|
raise Exception("bb.fetch2.Fetch.__init__: cannot set cache and localonly at same time")
|
||||||
|
|
||||||
if not urls:
|
if len(urls) == 0:
|
||||||
urls = d.getVar("SRC_URI").split()
|
urls = d.getVar("SRC_URI").split()
|
||||||
self.urls = urls
|
self.urls = urls
|
||||||
self.d = d
|
self.d = d
|
||||||
@@ -1769,30 +1627,10 @@ class Fetch(object):
|
|||||||
if key in urldata_cache:
|
if key in urldata_cache:
|
||||||
self.ud = urldata_cache[key]
|
self.ud = urldata_cache[key]
|
||||||
|
|
||||||
# the unpack_tracer object needs to be made available to possible nested
|
|
||||||
# Fetch instances (when those are created by gitsm and npmsw fetchers)
|
|
||||||
# so we set it as a global variable
|
|
||||||
global unpack_tracer
|
|
||||||
try:
|
|
||||||
unpack_tracer
|
|
||||||
except NameError:
|
|
||||||
class_path = d.getVar("BB_UNPACK_TRACER_CLASS")
|
|
||||||
if class_path:
|
|
||||||
# use user-defined unpack tracer class
|
|
||||||
import importlib
|
|
||||||
module_name, _, class_name = class_path.rpartition(".")
|
|
||||||
module = importlib.import_module(module_name)
|
|
||||||
class_ = getattr(module, class_name)
|
|
||||||
unpack_tracer = class_()
|
|
||||||
else:
|
|
||||||
# fall back to the dummy/abstract class
|
|
||||||
unpack_tracer = DummyUnpackTracer()
|
|
||||||
|
|
||||||
for url in urls:
|
for url in urls:
|
||||||
if url not in self.ud:
|
if url not in self.ud:
|
||||||
try:
|
try:
|
||||||
self.ud[url] = FetchData(url, d, localonly)
|
self.ud[url] = FetchData(url, d, localonly)
|
||||||
self.ud[url].unpack_tracer = unpack_tracer
|
|
||||||
except NonLocalMethod:
|
except NonLocalMethod:
|
||||||
if localonly:
|
if localonly:
|
||||||
self.ud[url] = None
|
self.ud[url] = None
|
||||||
@@ -1831,7 +1669,6 @@ class Fetch(object):
|
|||||||
network = self.d.getVar("BB_NO_NETWORK")
|
network = self.d.getVar("BB_NO_NETWORK")
|
||||||
premirroronly = bb.utils.to_boolean(self.d.getVar("BB_FETCH_PREMIRRORONLY"))
|
premirroronly = bb.utils.to_boolean(self.d.getVar("BB_FETCH_PREMIRRORONLY"))
|
||||||
|
|
||||||
checksum_missing_messages = []
|
|
||||||
for u in urls:
|
for u in urls:
|
||||||
ud = self.ud[u]
|
ud = self.ud[u]
|
||||||
ud.setup_localpath(self.d)
|
ud.setup_localpath(self.d)
|
||||||
@@ -1843,10 +1680,11 @@ class Fetch(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.d.setVar("BB_NO_NETWORK", network)
|
self.d.setVar("BB_NO_NETWORK", network)
|
||||||
|
|
||||||
if m.verify_donestamp(ud, self.d) and not m.need_update(ud, self.d):
|
if m.verify_donestamp(ud, self.d) and not m.need_update(ud, self.d):
|
||||||
done = True
|
done = True
|
||||||
elif m.try_premirror(ud, self.d):
|
elif m.try_premirror(ud, self.d):
|
||||||
logger.debug("Trying PREMIRRORS")
|
logger.debug(1, "Trying PREMIRRORS")
|
||||||
mirrors = mirror_from_string(self.d.getVar('PREMIRRORS'))
|
mirrors = mirror_from_string(self.d.getVar('PREMIRRORS'))
|
||||||
done = m.try_mirrors(self, ud, self.d, mirrors)
|
done = m.try_mirrors(self, ud, self.d, mirrors)
|
||||||
if done:
|
if done:
|
||||||
@@ -1856,21 +1694,19 @@ class Fetch(object):
|
|||||||
m.update_donestamp(ud, self.d)
|
m.update_donestamp(ud, self.d)
|
||||||
except ChecksumError as e:
|
except ChecksumError as e:
|
||||||
logger.warning("Checksum failure encountered with premirror download of %s - will attempt other sources." % u)
|
logger.warning("Checksum failure encountered with premirror download of %s - will attempt other sources." % u)
|
||||||
logger.debug(str(e))
|
logger.debug(1, str(e))
|
||||||
done = False
|
done = False
|
||||||
|
|
||||||
if premirroronly:
|
if premirroronly:
|
||||||
self.d.setVar("BB_NO_NETWORK", "1")
|
self.d.setVar("BB_NO_NETWORK", "1")
|
||||||
|
|
||||||
firsterr = None
|
firsterr = None
|
||||||
verified_stamp = False
|
verified_stamp = m.verify_donestamp(ud, self.d)
|
||||||
if done:
|
|
||||||
verified_stamp = m.verify_donestamp(ud, self.d)
|
|
||||||
if not done and (not verified_stamp or m.need_update(ud, self.d)):
|
if not done and (not verified_stamp or m.need_update(ud, self.d)):
|
||||||
try:
|
try:
|
||||||
if not trusted_network(self.d, ud.url):
|
if not trusted_network(self.d, ud.url):
|
||||||
raise UntrustedUrl(ud.url)
|
raise UntrustedUrl(ud.url)
|
||||||
logger.debug("Trying Upstream")
|
logger.debug(1, "Trying Upstream")
|
||||||
m.download(ud, self.d)
|
m.download(ud, self.d)
|
||||||
if hasattr(m, "build_mirror_data"):
|
if hasattr(m, "build_mirror_data"):
|
||||||
m.build_mirror_data(ud, self.d)
|
m.build_mirror_data(ud, self.d)
|
||||||
@@ -1885,19 +1721,19 @@ class Fetch(object):
|
|||||||
except BBFetchException as e:
|
except BBFetchException as e:
|
||||||
if isinstance(e, ChecksumError):
|
if isinstance(e, ChecksumError):
|
||||||
logger.warning("Checksum failure encountered with download of %s - will attempt other sources if available" % u)
|
logger.warning("Checksum failure encountered with download of %s - will attempt other sources if available" % u)
|
||||||
logger.debug(str(e))
|
logger.debug(1, str(e))
|
||||||
if os.path.exists(ud.localpath):
|
if os.path.exists(ud.localpath):
|
||||||
rename_bad_checksum(ud, e.checksum)
|
rename_bad_checksum(ud, e.checksum)
|
||||||
elif isinstance(e, NoChecksumError):
|
elif isinstance(e, NoChecksumError):
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
logger.warning('Failed to fetch URL %s, attempting MIRRORS if available' % u)
|
logger.warning('Failed to fetch URL %s, attempting MIRRORS if available' % u)
|
||||||
logger.debug(str(e))
|
logger.debug(1, str(e))
|
||||||
firsterr = e
|
firsterr = e
|
||||||
# Remove any incomplete fetch
|
# Remove any incomplete fetch
|
||||||
if not verified_stamp and m.cleanup_upon_failure():
|
if not verified_stamp:
|
||||||
m.clean(ud, self.d)
|
m.clean(ud, self.d)
|
||||||
logger.debug("Trying MIRRORS")
|
logger.debug(1, "Trying MIRRORS")
|
||||||
mirrors = mirror_from_string(self.d.getVar('MIRRORS'))
|
mirrors = mirror_from_string(self.d.getVar('MIRRORS'))
|
||||||
done = m.try_mirrors(self, ud, self.d, mirrors)
|
done = m.try_mirrors(self, ud, self.d, mirrors)
|
||||||
|
|
||||||
@@ -1914,28 +1750,17 @@ class Fetch(object):
|
|||||||
raise ChecksumError("Stale Error Detected")
|
raise ChecksumError("Stale Error Detected")
|
||||||
|
|
||||||
except BBFetchException as e:
|
except BBFetchException as e:
|
||||||
if isinstance(e, NoChecksumError):
|
if isinstance(e, ChecksumError):
|
||||||
(message, _) = e.args
|
|
||||||
checksum_missing_messages.append(message)
|
|
||||||
continue
|
|
||||||
elif isinstance(e, ChecksumError):
|
|
||||||
logger.error("Checksum failure fetching %s" % u)
|
logger.error("Checksum failure fetching %s" % u)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if ud.lockfile:
|
if ud.lockfile:
|
||||||
bb.utils.unlockfile(lf)
|
bb.utils.unlockfile(lf)
|
||||||
if checksum_missing_messages:
|
|
||||||
logger.error("Missing SRC_URI checksum, please add those to the recipe: \n%s", "\n".join(checksum_missing_messages))
|
|
||||||
raise BBFetchException("There was some missing checksums in the recipe")
|
|
||||||
|
|
||||||
def checkstatus(self, urls=None):
|
def checkstatus(self, urls=None):
|
||||||
"""
|
"""
|
||||||
Check all URLs exist upstream.
|
Check all urls exist upstream
|
||||||
|
|
||||||
Returns None if the URLs exist, raises FetchError if the check wasn't
|
|
||||||
successful but there wasn't an error (such as file not found), and
|
|
||||||
raises other exceptions in error cases.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not urls:
|
if not urls:
|
||||||
@@ -1945,7 +1770,7 @@ class Fetch(object):
|
|||||||
ud = self.ud[u]
|
ud = self.ud[u]
|
||||||
ud.setup_localpath(self.d)
|
ud.setup_localpath(self.d)
|
||||||
m = ud.method
|
m = ud.method
|
||||||
logger.debug("Testing URL %s", u)
|
logger.debug(1, "Testing URL %s", u)
|
||||||
# First try checking uri, u, from PREMIRRORS
|
# First try checking uri, u, from PREMIRRORS
|
||||||
mirrors = mirror_from_string(self.d.getVar('PREMIRRORS'))
|
mirrors = mirror_from_string(self.d.getVar('PREMIRRORS'))
|
||||||
ret = m.try_mirrors(self, ud, self.d, mirrors, True)
|
ret = m.try_mirrors(self, ud, self.d, mirrors, True)
|
||||||
@@ -1958,7 +1783,7 @@ class Fetch(object):
|
|||||||
ret = m.try_mirrors(self, ud, self.d, mirrors, True)
|
ret = m.try_mirrors(self, ud, self.d, mirrors, True)
|
||||||
|
|
||||||
if not ret:
|
if not ret:
|
||||||
raise FetchError("URL doesn't work", u)
|
raise FetchError("URL %s doesn't work" % u, u)
|
||||||
|
|
||||||
def unpack(self, root, urls=None):
|
def unpack(self, root, urls=None):
|
||||||
"""
|
"""
|
||||||
@@ -1968,8 +1793,6 @@ class Fetch(object):
|
|||||||
if not urls:
|
if not urls:
|
||||||
urls = self.urls
|
urls = self.urls
|
||||||
|
|
||||||
unpack_tracer.start(root, self.ud, self.d)
|
|
||||||
|
|
||||||
for u in urls:
|
for u in urls:
|
||||||
ud = self.ud[u]
|
ud = self.ud[u]
|
||||||
ud.setup_localpath(self.d)
|
ud.setup_localpath(self.d)
|
||||||
@@ -1977,15 +1800,11 @@ class Fetch(object):
|
|||||||
if ud.lockfile:
|
if ud.lockfile:
|
||||||
lf = bb.utils.lockfile(ud.lockfile)
|
lf = bb.utils.lockfile(ud.lockfile)
|
||||||
|
|
||||||
unpack_tracer.start_url(u)
|
|
||||||
ud.method.unpack(ud, root, self.d)
|
ud.method.unpack(ud, root, self.d)
|
||||||
unpack_tracer.finish_url(u)
|
|
||||||
|
|
||||||
if ud.lockfile:
|
if ud.lockfile:
|
||||||
bb.utils.unlockfile(lf)
|
bb.utils.unlockfile(lf)
|
||||||
|
|
||||||
unpack_tracer.complete()
|
|
||||||
|
|
||||||
def clean(self, urls=None):
|
def clean(self, urls=None):
|
||||||
"""
|
"""
|
||||||
Clean files that the fetcher gets or places
|
Clean files that the fetcher gets or places
|
||||||
@@ -2085,9 +1904,6 @@ from . import repo
|
|||||||
from . import clearcase
|
from . import clearcase
|
||||||
from . import npm
|
from . import npm
|
||||||
from . import npmsw
|
from . import npmsw
|
||||||
from . import az
|
|
||||||
from . import crate
|
|
||||||
from . import gcp
|
|
||||||
|
|
||||||
methods.append(local.Local())
|
methods.append(local.Local())
|
||||||
methods.append(wget.Wget())
|
methods.append(wget.Wget())
|
||||||
@@ -2107,6 +1923,3 @@ methods.append(repo.Repo())
|
|||||||
methods.append(clearcase.ClearCase())
|
methods.append(clearcase.ClearCase())
|
||||||
methods.append(npm.Npm())
|
methods.append(npm.Npm())
|
||||||
methods.append(npmsw.NpmShrinkWrap())
|
methods.append(npmsw.NpmShrinkWrap())
|
||||||
methods.append(az.Az())
|
|
||||||
methods.append(crate.Crate())
|
|
||||||
methods.append(gcp.GCP())
|
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
"""
|
|
||||||
BitBake 'Fetch' Azure Storage implementation
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Copyright (C) 2021 Alejandro Hernandez Samaniego
|
|
||||||
#
|
|
||||||
# Based on bb.fetch2.wget:
|
|
||||||
# Copyright (C) 2003, 2004 Chris Larson
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
# Based on functions from the base bb module, Copyright 2003 Holger Schurig
|
|
||||||
|
|
||||||
import shlex
|
|
||||||
import os
|
|
||||||
import bb
|
|
||||||
from bb.fetch2 import FetchError
|
|
||||||
from bb.fetch2 import logger
|
|
||||||
from bb.fetch2.wget import Wget
|
|
||||||
|
|
||||||
|
|
||||||
class Az(Wget):
|
|
||||||
|
|
||||||
def supports(self, ud, d):
|
|
||||||
"""
|
|
||||||
Check to see if a given url can be fetched from Azure Storage
|
|
||||||
"""
|
|
||||||
return ud.type in ['az']
|
|
||||||
|
|
||||||
|
|
||||||
def checkstatus(self, fetch, ud, d, try_again=True):
|
|
||||||
|
|
||||||
# checkstatus discards parameters either way, we need to do this before adding the SAS
|
|
||||||
ud.url = ud.url.replace('az://','https://').split(';')[0]
|
|
||||||
|
|
||||||
az_sas = d.getVar('AZ_SAS')
|
|
||||||
if az_sas and az_sas not in ud.url:
|
|
||||||
ud.url += az_sas
|
|
||||||
|
|
||||||
return Wget.checkstatus(self, fetch, ud, d, try_again)
|
|
||||||
|
|
||||||
# Override download method, include retries
|
|
||||||
def download(self, ud, d, retries=3):
|
|
||||||
"""Fetch urls"""
|
|
||||||
|
|
||||||
# If were reaching the account transaction limit we might be refused a connection,
|
|
||||||
# retrying allows us to avoid false negatives since the limit changes over time
|
|
||||||
fetchcmd = self.basecmd + ' --retry-connrefused --waitretry=5'
|
|
||||||
|
|
||||||
# We need to provide a localpath to avoid wget using the SAS
|
|
||||||
# ud.localfile either has the downloadfilename or ud.path
|
|
||||||
localpath = os.path.join(d.getVar("DL_DIR"), ud.localfile)
|
|
||||||
bb.utils.mkdirhier(os.path.dirname(localpath))
|
|
||||||
fetchcmd += " -O %s" % shlex.quote(localpath)
|
|
||||||
|
|
||||||
|
|
||||||
if ud.user and ud.pswd:
|
|
||||||
fetchcmd += " --user=%s --password=%s --auth-no-challenge" % (ud.user, ud.pswd)
|
|
||||||
|
|
||||||
# Check if a Shared Access Signature was given and use it
|
|
||||||
az_sas = d.getVar('AZ_SAS')
|
|
||||||
|
|
||||||
if az_sas:
|
|
||||||
azuri = '%s%s%s%s' % ('https://', ud.host, ud.path, az_sas)
|
|
||||||
else:
|
|
||||||
azuri = '%s%s%s' % ('https://', ud.host, ud.path)
|
|
||||||
|
|
||||||
if os.path.exists(ud.localpath):
|
|
||||||
# file exists, but we didnt complete it.. trying again.
|
|
||||||
fetchcmd += d.expand(" -c -P ${DL_DIR} '%s'" % azuri)
|
|
||||||
else:
|
|
||||||
fetchcmd += d.expand(" -P ${DL_DIR} '%s'" % azuri)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._runwget(ud, d, fetchcmd, False)
|
|
||||||
except FetchError as e:
|
|
||||||
# Azure fails on handshake sometimes when using wget after some stress, producing a
|
|
||||||
# FetchError from the fetcher, if the artifact exists retyring should succeed
|
|
||||||
if 'Unable to establish SSL connection' in str(e):
|
|
||||||
logger.debug2('Unable to establish SSL connection: Retries remaining: %s, Retrying...' % retries)
|
|
||||||
self.download(ud, d, retries -1)
|
|
||||||
|
|
||||||
# Sanity check since wget can pretend it succeed when it didn't
|
|
||||||
# Also, this used to happen if sourceforge sent us to the mirror page
|
|
||||||
if not os.path.exists(ud.localpath):
|
|
||||||
raise FetchError("The fetch command returned success for url %s but %s doesn't exist?!" % (azuri, ud.localpath), azuri)
|
|
||||||
|
|
||||||
if os.path.getsize(ud.localpath) == 0:
|
|
||||||
os.remove(ud.localpath)
|
|
||||||
raise FetchError("The fetch of %s resulted in a zero size file?! Deleting and failing since this isn't right." % (azuri), azuri)
|
|
||||||
|
|
||||||
return True
|
|
||||||
@@ -74,16 +74,16 @@ class Bzr(FetchMethod):
|
|||||||
|
|
||||||
if os.access(os.path.join(ud.pkgdir, os.path.basename(ud.pkgdir), '.bzr'), os.R_OK):
|
if os.access(os.path.join(ud.pkgdir, os.path.basename(ud.pkgdir), '.bzr'), os.R_OK):
|
||||||
bzrcmd = self._buildbzrcommand(ud, d, "update")
|
bzrcmd = self._buildbzrcommand(ud, d, "update")
|
||||||
logger.debug("BZR Update %s", ud.url)
|
logger.debug(1, "BZR Update %s", ud.url)
|
||||||
bb.fetch2.check_network_access(d, bzrcmd, ud.url)
|
bb.fetch2.check_network_access(d, bzrcmd, ud.url)
|
||||||
runfetchcmd(bzrcmd, d, workdir=os.path.join(ud.pkgdir, os.path.basename(ud.path)))
|
runfetchcmd(bzrcmd, d, workdir=os.path.join(ud.pkgdir, os.path.basename(ud.path)))
|
||||||
else:
|
else:
|
||||||
bb.utils.remove(os.path.join(ud.pkgdir, os.path.basename(ud.pkgdir)), True)
|
bb.utils.remove(os.path.join(ud.pkgdir, os.path.basename(ud.pkgdir)), True)
|
||||||
bzrcmd = self._buildbzrcommand(ud, d, "fetch")
|
bzrcmd = self._buildbzrcommand(ud, d, "fetch")
|
||||||
bb.fetch2.check_network_access(d, bzrcmd, ud.url)
|
bb.fetch2.check_network_access(d, bzrcmd, ud.url)
|
||||||
logger.debug("BZR Checkout %s", ud.url)
|
logger.debug(1, "BZR Checkout %s", ud.url)
|
||||||
bb.utils.mkdirhier(ud.pkgdir)
|
bb.utils.mkdirhier(ud.pkgdir)
|
||||||
logger.debug("Running %s", bzrcmd)
|
logger.debug(1, "Running %s", bzrcmd)
|
||||||
runfetchcmd(bzrcmd, d, workdir=ud.pkgdir)
|
runfetchcmd(bzrcmd, d, workdir=ud.pkgdir)
|
||||||
|
|
||||||
scmdata = ud.parm.get("scmdata", "")
|
scmdata = ud.parm.get("scmdata", "")
|
||||||
@@ -109,7 +109,7 @@ class Bzr(FetchMethod):
|
|||||||
"""
|
"""
|
||||||
Return the latest upstream revision number
|
Return the latest upstream revision number
|
||||||
"""
|
"""
|
||||||
logger.debug2("BZR fetcher hitting network for %s", ud.url)
|
logger.debug(2, "BZR fetcher hitting network for %s", ud.url)
|
||||||
|
|
||||||
bb.fetch2.check_network_access(d, self._buildbzrcommand(ud, d, "revno"), ud.url)
|
bb.fetch2.check_network_access(d, self._buildbzrcommand(ud, d, "revno"), ud.url)
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class ClearCase(FetchMethod):
|
|||||||
return ud.type in ['ccrc']
|
return ud.type in ['ccrc']
|
||||||
|
|
||||||
def debug(self, msg):
|
def debug(self, msg):
|
||||||
logger.debug("ClearCase: %s", msg)
|
logger.debug(1, "ClearCase: %s", msg)
|
||||||
|
|
||||||
def urldata_init(self, ud, d):
|
def urldata_init(self, ud, d):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
# ex:ts=4:sw=4:sts=4:et
|
|
||||||
# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
|
|
||||||
"""
|
|
||||||
BitBake 'Fetch' implementation for crates.io
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Copyright (C) 2016 Doug Goldstein
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
# Based on functions from the base bb module, Copyright 2003 Holger Schurig
|
|
||||||
|
|
||||||
import hashlib
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import bb
|
|
||||||
from bb.fetch2 import logger, subprocess_setup, UnpackError
|
|
||||||
from bb.fetch2.wget import Wget
|
|
||||||
|
|
||||||
|
|
||||||
class Crate(Wget):
|
|
||||||
|
|
||||||
"""Class to fetch crates via wget"""
|
|
||||||
|
|
||||||
def _cargo_bitbake_path(self, rootdir):
|
|
||||||
return os.path.join(rootdir, "cargo_home", "bitbake")
|
|
||||||
|
|
||||||
def supports(self, ud, d):
|
|
||||||
"""
|
|
||||||
Check to see if a given url is for this fetcher
|
|
||||||
"""
|
|
||||||
return ud.type in ['crate']
|
|
||||||
|
|
||||||
def recommends_checksum(self, urldata):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def urldata_init(self, ud, d):
|
|
||||||
"""
|
|
||||||
Sets up to download the respective crate from crates.io
|
|
||||||
"""
|
|
||||||
|
|
||||||
if ud.type == 'crate':
|
|
||||||
self._crate_urldata_init(ud, d)
|
|
||||||
|
|
||||||
super(Crate, self).urldata_init(ud, d)
|
|
||||||
|
|
||||||
def _crate_urldata_init(self, ud, d):
|
|
||||||
"""
|
|
||||||
Sets up the download for a crate
|
|
||||||
"""
|
|
||||||
|
|
||||||
# URL syntax is: crate://NAME/VERSION
|
|
||||||
# break the URL apart by /
|
|
||||||
parts = ud.url.split('/')
|
|
||||||
if len(parts) < 5:
|
|
||||||
raise bb.fetch2.ParameterError("Invalid URL: Must be crate://HOST/NAME/VERSION", ud.url)
|
|
||||||
|
|
||||||
# version is expected to be the last token
|
|
||||||
# but ignore possible url parameters which will be used
|
|
||||||
# by the top fetcher class
|
|
||||||
version = parts[-1].split(";")[0]
|
|
||||||
# second to last field is name
|
|
||||||
name = parts[-2]
|
|
||||||
# host (this is to allow custom crate registries to be specified
|
|
||||||
host = '/'.join(parts[2:-2])
|
|
||||||
|
|
||||||
# if using upstream just fix it up nicely
|
|
||||||
if host == 'crates.io':
|
|
||||||
host = 'crates.io/api/v1/crates'
|
|
||||||
|
|
||||||
ud.url = "https://%s/%s/%s/download" % (host, name, version)
|
|
||||||
ud.parm['downloadfilename'] = "%s-%s.crate" % (name, version)
|
|
||||||
if 'name' not in ud.parm:
|
|
||||||
ud.parm['name'] = '%s-%s' % (name, version)
|
|
||||||
|
|
||||||
logger.debug2("Fetching %s to %s" % (ud.url, ud.parm['downloadfilename']))
|
|
||||||
|
|
||||||
def unpack(self, ud, rootdir, d):
|
|
||||||
"""
|
|
||||||
Uses the crate to build the necessary paths for cargo to utilize it
|
|
||||||
"""
|
|
||||||
if ud.type == 'crate':
|
|
||||||
return self._crate_unpack(ud, rootdir, d)
|
|
||||||
else:
|
|
||||||
super(Crate, self).unpack(ud, rootdir, d)
|
|
||||||
|
|
||||||
def _crate_unpack(self, ud, rootdir, d):
|
|
||||||
"""
|
|
||||||
Unpacks a crate
|
|
||||||
"""
|
|
||||||
thefile = ud.localpath
|
|
||||||
|
|
||||||
# possible metadata we need to write out
|
|
||||||
metadata = {}
|
|
||||||
|
|
||||||
# change to the rootdir to unpack but save the old working dir
|
|
||||||
save_cwd = os.getcwd()
|
|
||||||
os.chdir(rootdir)
|
|
||||||
|
|
||||||
bp = d.getVar('BP')
|
|
||||||
if bp == ud.parm.get('name'):
|
|
||||||
cmd = "tar -xz --no-same-owner -f %s" % thefile
|
|
||||||
ud.unpack_tracer.unpack("crate-extract", rootdir)
|
|
||||||
else:
|
|
||||||
cargo_bitbake = self._cargo_bitbake_path(rootdir)
|
|
||||||
ud.unpack_tracer.unpack("cargo-extract", cargo_bitbake)
|
|
||||||
|
|
||||||
cmd = "tar -xz --no-same-owner -f %s -C %s" % (thefile, cargo_bitbake)
|
|
||||||
|
|
||||||
# ensure we've got these paths made
|
|
||||||
bb.utils.mkdirhier(cargo_bitbake)
|
|
||||||
|
|
||||||
# generate metadata necessary
|
|
||||||
with open(thefile, 'rb') as f:
|
|
||||||
# get the SHA256 of the original tarball
|
|
||||||
tarhash = hashlib.sha256(f.read()).hexdigest()
|
|
||||||
|
|
||||||
metadata['files'] = {}
|
|
||||||
metadata['package'] = tarhash
|
|
||||||
|
|
||||||
path = d.getVar('PATH')
|
|
||||||
if path:
|
|
||||||
cmd = "PATH=\"%s\" %s" % (path, cmd)
|
|
||||||
bb.note("Unpacking %s to %s/" % (thefile, os.getcwd()))
|
|
||||||
|
|
||||||
ret = subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True)
|
|
||||||
|
|
||||||
os.chdir(save_cwd)
|
|
||||||
|
|
||||||
if ret != 0:
|
|
||||||
raise UnpackError("Unpack command %s failed with return value %s" % (cmd, ret), ud.url)
|
|
||||||
|
|
||||||
# if we have metadata to write out..
|
|
||||||
if len(metadata) > 0:
|
|
||||||
cratepath = os.path.splitext(os.path.basename(thefile))[0]
|
|
||||||
bbpath = self._cargo_bitbake_path(rootdir)
|
|
||||||
mdfile = '.cargo-checksum.json'
|
|
||||||
mdpath = os.path.join(bbpath, cratepath, mdfile)
|
|
||||||
with open(mdpath, "w") as f:
|
|
||||||
json.dump(metadata, f)
|
|
||||||
@@ -109,7 +109,7 @@ class Cvs(FetchMethod):
|
|||||||
cvsupdatecmd = "CVS_RSH=\"%s\" %s" % (cvs_rsh, cvsupdatecmd)
|
cvsupdatecmd = "CVS_RSH=\"%s\" %s" % (cvs_rsh, cvsupdatecmd)
|
||||||
|
|
||||||
# create module directory
|
# create module directory
|
||||||
logger.debug2("Fetch: checking for module directory")
|
logger.debug(2, "Fetch: checking for module directory")
|
||||||
moddir = os.path.join(ud.pkgdir, localdir)
|
moddir = os.path.join(ud.pkgdir, localdir)
|
||||||
workdir = None
|
workdir = None
|
||||||
if os.access(os.path.join(moddir, 'CVS'), os.R_OK):
|
if os.access(os.path.join(moddir, 'CVS'), os.R_OK):
|
||||||
@@ -123,7 +123,7 @@ class Cvs(FetchMethod):
|
|||||||
# check out sources there
|
# check out sources there
|
||||||
bb.utils.mkdirhier(ud.pkgdir)
|
bb.utils.mkdirhier(ud.pkgdir)
|
||||||
workdir = ud.pkgdir
|
workdir = ud.pkgdir
|
||||||
logger.debug("Running %s", cvscmd)
|
logger.debug(1, "Running %s", cvscmd)
|
||||||
bb.fetch2.check_network_access(d, cvscmd, ud.url)
|
bb.fetch2.check_network_access(d, cvscmd, ud.url)
|
||||||
cmd = cvscmd
|
cmd = cvscmd
|
||||||
|
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
"""
|
|
||||||
BitBake 'Fetch' implementation for Google Cloup Platform Storage.
|
|
||||||
|
|
||||||
Class for fetching files from Google Cloud Storage using the
|
|
||||||
Google Cloud Storage Python Client. The GCS Python Client must
|
|
||||||
be correctly installed, configured and authenticated prior to use.
|
|
||||||
Additionally, gsutil must also be installed.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Copyright (C) 2023, Snap Inc.
|
|
||||||
#
|
|
||||||
# Based in part on bb.fetch2.s3:
|
|
||||||
# Copyright (C) 2017 Andre McCurdy
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
# Based on functions from the base bb module, Copyright 2003 Holger Schurig
|
|
||||||
|
|
||||||
import os
|
|
||||||
import bb
|
|
||||||
import urllib.parse, urllib.error
|
|
||||||
from bb.fetch2 import FetchMethod
|
|
||||||
from bb.fetch2 import FetchError
|
|
||||||
from bb.fetch2 import logger
|
|
||||||
|
|
||||||
class GCP(FetchMethod):
|
|
||||||
"""
|
|
||||||
Class to fetch urls via GCP's Python API.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self.gcp_client = None
|
|
||||||
|
|
||||||
def supports(self, ud, d):
|
|
||||||
"""
|
|
||||||
Check to see if a given url can be fetched with GCP.
|
|
||||||
"""
|
|
||||||
return ud.type in ['gs']
|
|
||||||
|
|
||||||
def recommends_checksum(self, urldata):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def urldata_init(self, ud, d):
|
|
||||||
if 'downloadfilename' in ud.parm:
|
|
||||||
ud.basename = ud.parm['downloadfilename']
|
|
||||||
else:
|
|
||||||
ud.basename = os.path.basename(ud.path)
|
|
||||||
|
|
||||||
ud.localfile = d.expand(urllib.parse.unquote(ud.basename))
|
|
||||||
|
|
||||||
def get_gcp_client(self):
|
|
||||||
from google.cloud import storage
|
|
||||||
self.gcp_client = storage.Client(project=None)
|
|
||||||
|
|
||||||
def download(self, ud, d):
|
|
||||||
"""
|
|
||||||
Fetch urls using the GCP API.
|
|
||||||
Assumes localpath was called first.
|
|
||||||
"""
|
|
||||||
from google.api_core.exceptions import NotFound
|
|
||||||
logger.debug2(f"Trying to download gs://{ud.host}{ud.path} to {ud.localpath}")
|
|
||||||
if self.gcp_client is None:
|
|
||||||
self.get_gcp_client()
|
|
||||||
|
|
||||||
bb.fetch2.check_network_access(d, "blob.download_to_filename", f"gs://{ud.host}{ud.path}")
|
|
||||||
|
|
||||||
# Path sometimes has leading slash, so strip it
|
|
||||||
path = ud.path.lstrip("/")
|
|
||||||
blob = self.gcp_client.bucket(ud.host).blob(path)
|
|
||||||
try:
|
|
||||||
blob.download_to_filename(ud.localpath)
|
|
||||||
except NotFound:
|
|
||||||
raise FetchError("The GCP API threw a NotFound exception")
|
|
||||||
|
|
||||||
# Additional sanity checks copied from the wget class (although there
|
|
||||||
# are no known issues which mean these are required, treat the GCP API
|
|
||||||
# tool with a little healthy suspicion).
|
|
||||||
if not os.path.exists(ud.localpath):
|
|
||||||
raise FetchError(f"The GCP API returned success for gs://{ud.host}{ud.path} but {ud.localpath} doesn't exist?!")
|
|
||||||
|
|
||||||
if os.path.getsize(ud.localpath) == 0:
|
|
||||||
os.remove(ud.localpath)
|
|
||||||
raise FetchError(f"The downloaded file for gs://{ud.host}{ud.path} resulted in a zero size file?! Deleting and failing since this isn't right.")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def checkstatus(self, fetch, ud, d):
|
|
||||||
"""
|
|
||||||
Check the status of a URL.
|
|
||||||
"""
|
|
||||||
logger.debug2(f"Checking status of gs://{ud.host}{ud.path}")
|
|
||||||
if self.gcp_client is None:
|
|
||||||
self.get_gcp_client()
|
|
||||||
|
|
||||||
bb.fetch2.check_network_access(d, "gcp_client.bucket(ud.host).blob(path).exists()", f"gs://{ud.host}{ud.path}")
|
|
||||||
|
|
||||||
# Path sometimes has leading slash, so strip it
|
|
||||||
path = ud.path.lstrip("/")
|
|
||||||
if self.gcp_client.bucket(ud.host).blob(path).exists() == False:
|
|
||||||
raise FetchError(f"The GCP API reported that gs://{ud.host}{ud.path} does not exist")
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
@@ -44,27 +44,13 @@ Supported SRC_URI options are:
|
|||||||
|
|
||||||
- nobranch
|
- nobranch
|
||||||
Don't check the SHA validation for branch. set this option for the recipe
|
Don't check the SHA validation for branch. set this option for the recipe
|
||||||
referring to commit which is valid in any namespace (branch, tag, ...)
|
referring to commit which is valid in tag instead of branch.
|
||||||
instead of branch.
|
|
||||||
The default is "0", set nobranch=1 if needed.
|
The default is "0", set nobranch=1 if needed.
|
||||||
|
|
||||||
- subpath
|
|
||||||
Limit the checkout to a specific subpath of the tree.
|
|
||||||
By default, checkout the whole tree, set subpath=<path> if needed
|
|
||||||
|
|
||||||
- destsuffix
|
|
||||||
The name of the path in which to place the checkout.
|
|
||||||
By default, the path is git/, set destsuffix=<suffix> if needed
|
|
||||||
|
|
||||||
- usehead
|
- usehead
|
||||||
For local git:// urls to use the current branch HEAD as the revision for use with
|
For local git:// urls to use the current branch HEAD as the revision for use with
|
||||||
AUTOREV. Implies nobranch.
|
AUTOREV. Implies nobranch.
|
||||||
|
|
||||||
- lfs
|
|
||||||
Enable the checkout to use LFS for large files. This will download all LFS files
|
|
||||||
in the download step, as the unpack step does not have network access.
|
|
||||||
The default is "1", set lfs=0 to skip.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Copyright (C) 2005 Richard Purdie
|
# Copyright (C) 2005 Richard Purdie
|
||||||
@@ -78,21 +64,15 @@ import fnmatch
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import bb
|
import bb
|
||||||
import bb.progress
|
import bb.progress
|
||||||
from contextlib import contextmanager
|
|
||||||
from bb.fetch2 import FetchMethod
|
from bb.fetch2 import FetchMethod
|
||||||
from bb.fetch2 import runfetchcmd
|
from bb.fetch2 import runfetchcmd
|
||||||
from bb.fetch2 import logger
|
from bb.fetch2 import logger
|
||||||
from bb.fetch2 import trusted_network
|
|
||||||
|
|
||||||
|
|
||||||
sha1_re = re.compile(r'^[0-9a-f]{40}$')
|
|
||||||
slash_re = re.compile(r"/+")
|
|
||||||
|
|
||||||
class GitProgressHandler(bb.progress.LineFilterProgressHandler):
|
class GitProgressHandler(bb.progress.LineFilterProgressHandler):
|
||||||
"""Extract progress information from git output"""
|
"""Extract progress information from git output"""
|
||||||
def __init__(self, d):
|
def __init__(self, d):
|
||||||
@@ -150,9 +130,6 @@ class Git(FetchMethod):
|
|||||||
def supports_checksum(self, urldata):
|
def supports_checksum(self, urldata):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def cleanup_upon_failure(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def urldata_init(self, ud, d):
|
def urldata_init(self, ud, d):
|
||||||
"""
|
"""
|
||||||
init git specific variable within url data
|
init git specific variable within url data
|
||||||
@@ -164,11 +141,6 @@ class Git(FetchMethod):
|
|||||||
ud.proto = 'file'
|
ud.proto = 'file'
|
||||||
else:
|
else:
|
||||||
ud.proto = "git"
|
ud.proto = "git"
|
||||||
if ud.host == "github.com" and ud.proto == "git":
|
|
||||||
# github stopped supporting git protocol
|
|
||||||
# https://github.blog/2021-09-01-improving-git-protocol-security-github/#no-more-unauthenticated-git
|
|
||||||
ud.proto = "https"
|
|
||||||
bb.warn("URL: %s uses git protocol which is no longer supported by github. Please change to ;protocol=https in the url." % ud.url)
|
|
||||||
|
|
||||||
if not ud.proto in ('git', 'file', 'ssh', 'http', 'https', 'rsync'):
|
if not ud.proto in ('git', 'file', 'ssh', 'http', 'https', 'rsync'):
|
||||||
raise bb.fetch2.ParameterError("Invalid protocol type", ud.url)
|
raise bb.fetch2.ParameterError("Invalid protocol type", ud.url)
|
||||||
@@ -192,18 +164,11 @@ class Git(FetchMethod):
|
|||||||
ud.nocheckout = 1
|
ud.nocheckout = 1
|
||||||
|
|
||||||
ud.unresolvedrev = {}
|
ud.unresolvedrev = {}
|
||||||
branches = ud.parm.get("branch", "").split(',')
|
branches = ud.parm.get("branch", "master").split(',')
|
||||||
if branches == [""] and not ud.nobranch:
|
|
||||||
bb.warn("URL: %s does not set any branch parameter. The future default branch used by tools and repositories is uncertain and we will therefore soon require this is set in all git urls." % ud.url)
|
|
||||||
branches = ["master"]
|
|
||||||
if len(branches) != len(ud.names):
|
if len(branches) != len(ud.names):
|
||||||
raise bb.fetch2.ParameterError("The number of name and branch parameters is not balanced", ud.url)
|
raise bb.fetch2.ParameterError("The number of name and branch parameters is not balanced", ud.url)
|
||||||
|
|
||||||
ud.noshared = d.getVar("BB_GIT_NOSHARED") == "1"
|
ud.cloneflags = "-s -n"
|
||||||
|
|
||||||
ud.cloneflags = "-n"
|
|
||||||
if not ud.noshared:
|
|
||||||
ud.cloneflags += " -s"
|
|
||||||
if ud.bareclone:
|
if ud.bareclone:
|
||||||
ud.cloneflags += " --mirror"
|
ud.cloneflags += " --mirror"
|
||||||
|
|
||||||
@@ -255,14 +220,9 @@ class Git(FetchMethod):
|
|||||||
ud.shallow = False
|
ud.shallow = False
|
||||||
|
|
||||||
if ud.usehead:
|
if ud.usehead:
|
||||||
# When usehead is set let's associate 'HEAD' with the unresolved
|
ud.unresolvedrev['default'] = 'HEAD'
|
||||||
# rev of this repository. This will get resolved into a revision
|
|
||||||
# later. If an actual revision happens to have also been provided
|
|
||||||
# then this setting will be overridden.
|
|
||||||
for name in ud.names:
|
|
||||||
ud.unresolvedrev[name] = 'HEAD'
|
|
||||||
|
|
||||||
ud.basecmd = d.getVar("FETCHCMD_git") or "git -c gc.autoDetach=false -c core.pager=cat -c safe.bareRepository=all"
|
ud.basecmd = d.getVar("FETCHCMD_git") or "git -c core.fsyncobjectfiles=0"
|
||||||
|
|
||||||
write_tarballs = d.getVar("BB_GENERATE_MIRROR_TARBALLS") or "0"
|
write_tarballs = d.getVar("BB_GENERATE_MIRROR_TARBALLS") or "0"
|
||||||
ud.write_tarballs = write_tarballs != "0" or ud.rebaseable
|
ud.write_tarballs = write_tarballs != "0" or ud.rebaseable
|
||||||
@@ -271,20 +231,20 @@ class Git(FetchMethod):
|
|||||||
ud.setup_revisions(d)
|
ud.setup_revisions(d)
|
||||||
|
|
||||||
for name in ud.names:
|
for name in ud.names:
|
||||||
# Ensure any revision that doesn't look like a SHA-1 is translated into one
|
# Ensure anything that doesn't look like a sha256 checksum/revision is translated into one
|
||||||
if not sha1_re.match(ud.revisions[name] or ''):
|
if not ud.revisions[name] or len(ud.revisions[name]) != 40 or (False in [c in "abcdef0123456789" for c in ud.revisions[name]]):
|
||||||
if ud.revisions[name]:
|
if ud.revisions[name]:
|
||||||
ud.unresolvedrev[name] = ud.revisions[name]
|
ud.unresolvedrev[name] = ud.revisions[name]
|
||||||
ud.revisions[name] = self.latest_revision(ud, d, name)
|
ud.revisions[name] = self.latest_revision(ud, d, name)
|
||||||
|
|
||||||
gitsrcname = '%s%s' % (ud.host.replace(':', '.'), ud.path.replace('/', '.').replace('*', '.').replace(' ','_').replace('(', '_').replace(')', '_'))
|
gitsrcname = '%s%s' % (ud.host.replace(':', '.'), ud.path.replace('/', '.').replace('*', '.').replace(' ','_'))
|
||||||
if gitsrcname.startswith('.'):
|
if gitsrcname.startswith('.'):
|
||||||
gitsrcname = gitsrcname[1:]
|
gitsrcname = gitsrcname[1:]
|
||||||
|
|
||||||
# For a rebaseable git repo, it is necessary to keep a mirror tar ball
|
# for rebaseable git repo, it is necessary to keep mirror tar ball
|
||||||
# per revision, so that even if the revision disappears from the
|
# per revision, so that even the revision disappears from the
|
||||||
# upstream repo in the future, the mirror will remain intact and still
|
# upstream repo in the future, the mirror will remain intact and still
|
||||||
# contain the revision
|
# contains the revision
|
||||||
if ud.rebaseable:
|
if ud.rebaseable:
|
||||||
for name in ud.names:
|
for name in ud.names:
|
||||||
gitsrcname = gitsrcname + '_' + ud.revisions[name]
|
gitsrcname = gitsrcname + '_' + ud.revisions[name]
|
||||||
@@ -328,10 +288,7 @@ class Git(FetchMethod):
|
|||||||
return ud.clonedir
|
return ud.clonedir
|
||||||
|
|
||||||
def need_update(self, ud, d):
|
def need_update(self, ud, d):
|
||||||
return self.clonedir_need_update(ud, d) \
|
return self.clonedir_need_update(ud, d) or self.shallow_tarball_need_update(ud) or self.tarball_need_update(ud)
|
||||||
or self.shallow_tarball_need_update(ud) \
|
|
||||||
or self.tarball_need_update(ud) \
|
|
||||||
or self.lfs_need_update(ud, d)
|
|
||||||
|
|
||||||
def clonedir_need_update(self, ud, d):
|
def clonedir_need_update(self, ud, d):
|
||||||
if not os.path.exists(ud.clonedir):
|
if not os.path.exists(ud.clonedir):
|
||||||
@@ -343,15 +300,6 @@ class Git(FetchMethod):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def lfs_need_update(self, ud, d):
|
|
||||||
if self.clonedir_need_update(ud, d):
|
|
||||||
return True
|
|
||||||
|
|
||||||
for name in ud.names:
|
|
||||||
if not self._lfs_objects_downloaded(ud, d, name, ud.clonedir):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def clonedir_need_shallow_revs(self, ud, d):
|
def clonedir_need_shallow_revs(self, ud, d):
|
||||||
for rev in ud.shallow_revs:
|
for rev in ud.shallow_revs:
|
||||||
try:
|
try:
|
||||||
@@ -371,16 +319,6 @@ class Git(FetchMethod):
|
|||||||
# is not possible
|
# is not possible
|
||||||
if bb.utils.to_boolean(d.getVar("BB_FETCH_PREMIRRORONLY")):
|
if bb.utils.to_boolean(d.getVar("BB_FETCH_PREMIRRORONLY")):
|
||||||
return True
|
return True
|
||||||
# If the url is not in trusted network, that is, BB_NO_NETWORK is set to 0
|
|
||||||
# and BB_ALLOWED_NETWORKS does not contain the host that ud.url uses, then
|
|
||||||
# we need to try premirrors first as using upstream is destined to fail.
|
|
||||||
if not trusted_network(d, ud.url):
|
|
||||||
return True
|
|
||||||
# the following check is to ensure incremental fetch in downloads, this is
|
|
||||||
# because the premirror might be old and does not contain the new rev required,
|
|
||||||
# and this will cause a total removal and new clone. So if we can reach to
|
|
||||||
# network, we prefer upstream over premirror, though the premirror might contain
|
|
||||||
# the new rev.
|
|
||||||
if os.path.exists(ud.clonedir):
|
if os.path.exists(ud.clonedir):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@@ -394,54 +332,17 @@ class Git(FetchMethod):
|
|||||||
if ud.shallow and os.path.exists(ud.fullshallow) and self.need_update(ud, d):
|
if ud.shallow and os.path.exists(ud.fullshallow) and self.need_update(ud, d):
|
||||||
ud.localpath = ud.fullshallow
|
ud.localpath = ud.fullshallow
|
||||||
return
|
return
|
||||||
elif os.path.exists(ud.fullmirror) and self.need_update(ud, d):
|
elif os.path.exists(ud.fullmirror) and not os.path.exists(ud.clonedir):
|
||||||
if not os.path.exists(ud.clonedir):
|
bb.utils.mkdirhier(ud.clonedir)
|
||||||
bb.utils.mkdirhier(ud.clonedir)
|
runfetchcmd("tar -xzf %s" % ud.fullmirror, d, workdir=ud.clonedir)
|
||||||
runfetchcmd("tar -xzf %s" % ud.fullmirror, d, workdir=ud.clonedir)
|
|
||||||
else:
|
|
||||||
tmpdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
|
|
||||||
runfetchcmd("tar -xzf %s" % ud.fullmirror, d, workdir=tmpdir)
|
|
||||||
output = runfetchcmd("%s remote" % ud.basecmd, d, quiet=True, workdir=ud.clonedir)
|
|
||||||
if 'mirror' in output:
|
|
||||||
runfetchcmd("%s remote rm mirror" % ud.basecmd, d, workdir=ud.clonedir)
|
|
||||||
runfetchcmd("%s remote add --mirror=fetch mirror %s" % (ud.basecmd, tmpdir), d, workdir=ud.clonedir)
|
|
||||||
fetch_cmd = "LANG=C %s fetch -f --update-head-ok --progress mirror " % (ud.basecmd)
|
|
||||||
runfetchcmd(fetch_cmd, d, workdir=ud.clonedir)
|
|
||||||
repourl = self._get_repo_url(ud)
|
repourl = self._get_repo_url(ud)
|
||||||
|
|
||||||
needs_clone = False
|
|
||||||
if os.path.exists(ud.clonedir):
|
|
||||||
# The directory may exist, but not be the top level of a bare git
|
|
||||||
# repository in which case it needs to be deleted and re-cloned.
|
|
||||||
try:
|
|
||||||
# Since clones can be bare, use --absolute-git-dir instead of --show-toplevel
|
|
||||||
output = runfetchcmd("LANG=C %s rev-parse --absolute-git-dir" % ud.basecmd, d, workdir=ud.clonedir)
|
|
||||||
toplevel = output.rstrip()
|
|
||||||
|
|
||||||
if not bb.utils.path_is_descendant(toplevel, ud.clonedir):
|
|
||||||
logger.warning("Top level directory '%s' is not a descendant of '%s'. Re-cloning", toplevel, ud.clonedir)
|
|
||||||
needs_clone = True
|
|
||||||
except bb.fetch2.FetchError as e:
|
|
||||||
logger.warning("Unable to get top level for %s (not a git directory?): %s", ud.clonedir, e)
|
|
||||||
needs_clone = True
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
logger.warning("%s", e)
|
|
||||||
needs_clone = True
|
|
||||||
|
|
||||||
if needs_clone:
|
|
||||||
shutil.rmtree(ud.clonedir)
|
|
||||||
else:
|
|
||||||
needs_clone = True
|
|
||||||
|
|
||||||
# If the repo still doesn't exist, fallback to cloning it
|
# If the repo still doesn't exist, fallback to cloning it
|
||||||
if needs_clone:
|
if not os.path.exists(ud.clonedir):
|
||||||
# We do this since git will use a "-l" option automatically for local urls where possible,
|
# We do this since git will use a "-l" option automatically for local urls where possible
|
||||||
# but it doesn't work when git/objects is a symlink, only works when it is a directory.
|
|
||||||
if repourl.startswith("file://"):
|
if repourl.startswith("file://"):
|
||||||
repourl_path = repourl[7:]
|
repourl = repourl[7:]
|
||||||
objects = os.path.join(repourl_path, 'objects')
|
|
||||||
if os.path.isdir(objects) and not os.path.islink(objects):
|
|
||||||
repourl = repourl_path
|
|
||||||
clone_cmd = "LANG=C %s clone --bare --mirror %s %s --progress" % (ud.basecmd, shlex.quote(repourl), ud.clonedir)
|
clone_cmd = "LANG=C %s clone --bare --mirror %s %s --progress" % (ud.basecmd, shlex.quote(repourl), ud.clonedir)
|
||||||
if ud.proto.lower() != 'file':
|
if ud.proto.lower() != 'file':
|
||||||
bb.fetch2.check_network_access(d, clone_cmd, ud.url)
|
bb.fetch2.check_network_access(d, clone_cmd, ud.url)
|
||||||
@@ -455,11 +356,7 @@ class Git(FetchMethod):
|
|||||||
runfetchcmd("%s remote rm origin" % ud.basecmd, d, workdir=ud.clonedir)
|
runfetchcmd("%s remote rm origin" % ud.basecmd, d, workdir=ud.clonedir)
|
||||||
|
|
||||||
runfetchcmd("%s remote add --mirror=fetch origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=ud.clonedir)
|
runfetchcmd("%s remote add --mirror=fetch origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=ud.clonedir)
|
||||||
|
fetch_cmd = "LANG=C %s fetch -f --progress %s refs/*:refs/*" % (ud.basecmd, shlex.quote(repourl))
|
||||||
if ud.nobranch:
|
|
||||||
fetch_cmd = "LANG=C %s fetch -f --progress %s refs/*:refs/*" % (ud.basecmd, shlex.quote(repourl))
|
|
||||||
else:
|
|
||||||
fetch_cmd = "LANG=C %s fetch -f --progress %s refs/heads/*:refs/heads/* refs/tags/*:refs/tags/*" % (ud.basecmd, shlex.quote(repourl))
|
|
||||||
if ud.proto.lower() != 'file':
|
if ud.proto.lower() != 'file':
|
||||||
bb.fetch2.check_network_access(d, fetch_cmd, ud.url)
|
bb.fetch2.check_network_access(d, fetch_cmd, ud.url)
|
||||||
progresshandler = GitProgressHandler(d)
|
progresshandler = GitProgressHandler(d)
|
||||||
@@ -482,16 +379,17 @@ class Git(FetchMethod):
|
|||||||
if missing_rev:
|
if missing_rev:
|
||||||
raise bb.fetch2.FetchError("Unable to find revision %s even from upstream" % missing_rev)
|
raise bb.fetch2.FetchError("Unable to find revision %s even from upstream" % missing_rev)
|
||||||
|
|
||||||
if self.lfs_need_update(ud, d):
|
if self._contains_lfs(ud, d, ud.clonedir) and self._need_lfs(ud):
|
||||||
# Unpack temporary working copy, use it to run 'git checkout' to force pre-fetching
|
# Unpack temporary working copy, use it to run 'git checkout' to force pre-fetching
|
||||||
# of all LFS blobs needed at the srcrev.
|
# of all LFS blobs needed at the the srcrev.
|
||||||
#
|
#
|
||||||
# It would be nice to just do this inline here by running 'git-lfs fetch'
|
# It would be nice to just do this inline here by running 'git-lfs fetch'
|
||||||
# on the bare clonedir, but that operation requires a working copy on some
|
# on the bare clonedir, but that operation requires a working copy on some
|
||||||
# releases of Git LFS.
|
# releases of Git LFS.
|
||||||
with tempfile.TemporaryDirectory(dir=d.getVar('DL_DIR')) as tmpdir:
|
tmpdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
|
||||||
|
try:
|
||||||
# Do the checkout. This implicitly involves a Git LFS fetch.
|
# Do the checkout. This implicitly involves a Git LFS fetch.
|
||||||
Git.unpack(self, ud, tmpdir, d)
|
self.unpack(ud, tmpdir, d)
|
||||||
|
|
||||||
# Scoop up a copy of any stuff that Git LFS downloaded. Merge them into
|
# Scoop up a copy of any stuff that Git LFS downloaded. Merge them into
|
||||||
# the bare clonedir.
|
# the bare clonedir.
|
||||||
@@ -505,24 +403,12 @@ class Git(FetchMethod):
|
|||||||
# Only do this if the unpack resulted in a .git/lfs directory being
|
# Only do this if the unpack resulted in a .git/lfs directory being
|
||||||
# created; this only happens if at least one blob needed to be
|
# created; this only happens if at least one blob needed to be
|
||||||
# downloaded.
|
# downloaded.
|
||||||
if os.path.exists(os.path.join(ud.destdir, ".git", "lfs")):
|
if os.path.exists(os.path.join(tmpdir, "git", ".git", "lfs")):
|
||||||
runfetchcmd("tar -cf - lfs | tar -xf - -C %s" % ud.clonedir, d, workdir="%s/.git" % ud.destdir)
|
runfetchcmd("tar -cf - lfs | tar -xf - -C %s" % ud.clonedir, d, workdir="%s/git/.git" % tmpdir)
|
||||||
|
finally:
|
||||||
|
bb.utils.remove(tmpdir, recurse=True)
|
||||||
|
|
||||||
def build_mirror_data(self, ud, d):
|
def build_mirror_data(self, ud, d):
|
||||||
|
|
||||||
# Create as a temp file and move atomically into position to avoid races
|
|
||||||
@contextmanager
|
|
||||||
def create_atomic(filename):
|
|
||||||
fd, tfile = tempfile.mkstemp(dir=os.path.dirname(filename))
|
|
||||||
try:
|
|
||||||
yield tfile
|
|
||||||
umask = os.umask(0o666)
|
|
||||||
os.umask(umask)
|
|
||||||
os.chmod(tfile, (0o666 & ~umask))
|
|
||||||
os.rename(tfile, filename)
|
|
||||||
finally:
|
|
||||||
os.close(fd)
|
|
||||||
|
|
||||||
if ud.shallow and ud.write_shallow_tarballs:
|
if ud.shallow and ud.write_shallow_tarballs:
|
||||||
if not os.path.exists(ud.fullshallow):
|
if not os.path.exists(ud.fullshallow):
|
||||||
if os.path.islink(ud.fullshallow):
|
if os.path.islink(ud.fullshallow):
|
||||||
@@ -533,8 +419,7 @@ class Git(FetchMethod):
|
|||||||
self.clone_shallow_local(ud, shallowclone, d)
|
self.clone_shallow_local(ud, shallowclone, d)
|
||||||
|
|
||||||
logger.info("Creating tarball of git repository")
|
logger.info("Creating tarball of git repository")
|
||||||
with create_atomic(ud.fullshallow) as tfile:
|
runfetchcmd("tar -czf %s ." % ud.fullshallow, d, workdir=shallowclone)
|
||||||
runfetchcmd("tar -czf %s ." % tfile, d, workdir=shallowclone)
|
|
||||||
runfetchcmd("touch %s.done" % ud.fullshallow, d)
|
runfetchcmd("touch %s.done" % ud.fullshallow, d)
|
||||||
finally:
|
finally:
|
||||||
bb.utils.remove(tempdir, recurse=True)
|
bb.utils.remove(tempdir, recurse=True)
|
||||||
@@ -543,11 +428,7 @@ class Git(FetchMethod):
|
|||||||
os.unlink(ud.fullmirror)
|
os.unlink(ud.fullmirror)
|
||||||
|
|
||||||
logger.info("Creating tarball of git repository")
|
logger.info("Creating tarball of git repository")
|
||||||
with create_atomic(ud.fullmirror) as tfile:
|
runfetchcmd("tar -czf %s ." % ud.fullmirror, d, workdir=ud.clonedir)
|
||||||
mtime = runfetchcmd("{} log --all -1 --format=%cD".format(ud.basecmd), d,
|
|
||||||
quiet=True, workdir=ud.clonedir)
|
|
||||||
runfetchcmd("tar -czf %s --owner oe:0 --group oe:0 --mtime \"%s\" ."
|
|
||||||
% (tfile, mtime), d, workdir=ud.clonedir)
|
|
||||||
runfetchcmd("touch %s.done" % ud.fullmirror, d)
|
runfetchcmd("touch %s.done" % ud.fullmirror, d)
|
||||||
|
|
||||||
def clone_shallow_local(self, ud, dest, d):
|
def clone_shallow_local(self, ud, dest, d):
|
||||||
@@ -609,31 +490,18 @@ class Git(FetchMethod):
|
|||||||
def unpack(self, ud, destdir, d):
|
def unpack(self, ud, destdir, d):
|
||||||
""" unpack the downloaded src to destdir"""
|
""" unpack the downloaded src to destdir"""
|
||||||
|
|
||||||
subdir = ud.parm.get("subdir")
|
subdir = ud.parm.get("subpath", "")
|
||||||
subpath = ud.parm.get("subpath")
|
if subdir != "":
|
||||||
readpathspec = ""
|
readpathspec = ":%s" % subdir
|
||||||
def_destsuffix = "git/"
|
def_destsuffix = "%s/" % os.path.basename(subdir.rstrip('/'))
|
||||||
|
else:
|
||||||
if subpath:
|
readpathspec = ""
|
||||||
readpathspec = ":%s" % subpath
|
def_destsuffix = "git/"
|
||||||
def_destsuffix = "%s/" % os.path.basename(subpath.rstrip('/'))
|
|
||||||
|
|
||||||
if subdir:
|
|
||||||
# If 'subdir' param exists, create a dir and use it as destination for unpack cmd
|
|
||||||
if os.path.isabs(subdir):
|
|
||||||
if not os.path.realpath(subdir).startswith(os.path.realpath(destdir)):
|
|
||||||
raise bb.fetch2.UnpackError("subdir argument isn't a subdirectory of unpack root %s" % destdir, ud.url)
|
|
||||||
destdir = subdir
|
|
||||||
else:
|
|
||||||
destdir = os.path.join(destdir, subdir)
|
|
||||||
def_destsuffix = ""
|
|
||||||
|
|
||||||
destsuffix = ud.parm.get("destsuffix", def_destsuffix)
|
destsuffix = ud.parm.get("destsuffix", def_destsuffix)
|
||||||
destdir = ud.destdir = os.path.join(destdir, destsuffix)
|
destdir = ud.destdir = os.path.join(destdir, destsuffix)
|
||||||
if os.path.exists(destdir):
|
if os.path.exists(destdir):
|
||||||
bb.utils.prunedir(destdir)
|
bb.utils.prunedir(destdir)
|
||||||
if not ud.bareclone:
|
|
||||||
ud.unpack_tracer.unpack("git", destdir)
|
|
||||||
|
|
||||||
need_lfs = self._need_lfs(ud)
|
need_lfs = self._need_lfs(ud)
|
||||||
|
|
||||||
@@ -643,12 +511,13 @@ class Git(FetchMethod):
|
|||||||
source_found = False
|
source_found = False
|
||||||
source_error = []
|
source_error = []
|
||||||
|
|
||||||
clonedir_is_up_to_date = not self.clonedir_need_update(ud, d)
|
if not source_found:
|
||||||
if clonedir_is_up_to_date:
|
clonedir_is_up_to_date = not self.clonedir_need_update(ud, d)
|
||||||
runfetchcmd("%s clone %s %s/ %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, destdir), d)
|
if clonedir_is_up_to_date:
|
||||||
source_found = True
|
runfetchcmd("%s clone %s %s/ %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, destdir), d)
|
||||||
else:
|
source_found = True
|
||||||
source_error.append("clone directory not available or not up to date: " + ud.clonedir)
|
else:
|
||||||
|
source_error.append("clone directory not available or not up to date: " + ud.clonedir)
|
||||||
|
|
||||||
if not source_found:
|
if not source_found:
|
||||||
if ud.shallow:
|
if ud.shallow:
|
||||||
@@ -672,11 +541,9 @@ class Git(FetchMethod):
|
|||||||
raise bb.fetch2.FetchError("Repository %s has LFS content, install git-lfs on host to download (or set lfs=0 to ignore it)" % (repourl))
|
raise bb.fetch2.FetchError("Repository %s has LFS content, install git-lfs on host to download (or set lfs=0 to ignore it)" % (repourl))
|
||||||
elif not need_lfs:
|
elif not need_lfs:
|
||||||
bb.note("Repository %s has LFS content but it is not being fetched" % (repourl))
|
bb.note("Repository %s has LFS content but it is not being fetched" % (repourl))
|
||||||
else:
|
|
||||||
runfetchcmd("%s lfs install --local" % ud.basecmd, d, workdir=destdir)
|
|
||||||
|
|
||||||
if not ud.nocheckout:
|
if not ud.nocheckout:
|
||||||
if subpath:
|
if subdir != "":
|
||||||
runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d,
|
runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d,
|
||||||
workdir=destdir)
|
workdir=destdir)
|
||||||
runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d, workdir=destdir)
|
runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d, workdir=destdir)
|
||||||
@@ -725,35 +592,6 @@ class Git(FetchMethod):
|
|||||||
raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
|
raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
|
||||||
return output.split()[0] != "0"
|
return output.split()[0] != "0"
|
||||||
|
|
||||||
def _lfs_objects_downloaded(self, ud, d, name, wd):
|
|
||||||
"""
|
|
||||||
Verifies whether the LFS objects for requested revisions have already been downloaded
|
|
||||||
"""
|
|
||||||
# Bail out early if this repository doesn't use LFS
|
|
||||||
if not self._need_lfs(ud) or not self._contains_lfs(ud, d, wd):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# The Git LFS specification specifies ([1]) the LFS folder layout so it should be safe to check for file
|
|
||||||
# existence.
|
|
||||||
# [1] https://github.com/git-lfs/git-lfs/blob/main/docs/spec.md#intercepting-git
|
|
||||||
cmd = "%s lfs ls-files -l %s" \
|
|
||||||
% (ud.basecmd, ud.revisions[name])
|
|
||||||
output = runfetchcmd(cmd, d, quiet=True, workdir=wd).rstrip()
|
|
||||||
# Do not do any further matching if no objects are managed by LFS
|
|
||||||
if not output:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Match all lines beginning with the hexadecimal OID
|
|
||||||
oid_regex = re.compile("^(([a-fA-F0-9]{2})([a-fA-F0-9]{2})[A-Fa-f0-9]+)")
|
|
||||||
for line in output.split("\n"):
|
|
||||||
oid = re.search(oid_regex, line)
|
|
||||||
if not oid:
|
|
||||||
bb.warn("git lfs ls-files output '%s' did not match expected format." % line)
|
|
||||||
if not os.path.exists(os.path.join(wd, "lfs", "objects", oid.group(2), oid.group(3), oid.group(1))):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _need_lfs(self, ud):
|
def _need_lfs(self, ud):
|
||||||
return ud.parm.get("lfs", "1") == "1"
|
return ud.parm.get("lfs", "1") == "1"
|
||||||
|
|
||||||
@@ -762,11 +600,13 @@ class Git(FetchMethod):
|
|||||||
Check if the repository has 'lfs' (large file) content
|
Check if the repository has 'lfs' (large file) content
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if ud.nobranch:
|
if not ud.nobranch:
|
||||||
# If no branch is specified, use the current git commit
|
branchname = ud.branches[ud.names[0]]
|
||||||
refname = self._build_revision(ud, d, ud.names[0])
|
else:
|
||||||
elif wd == ud.clonedir:
|
branchname = "master"
|
||||||
# The bare clonedir doesn't use the remote names; it has the branch immediately.
|
|
||||||
|
# The bare clonedir doesn't use the remote names; it has the branch immediately.
|
||||||
|
if wd == ud.clonedir:
|
||||||
refname = ud.branches[ud.names[0]]
|
refname = ud.branches[ud.names[0]]
|
||||||
else:
|
else:
|
||||||
refname = "origin/%s" % ud.branches[ud.names[0]]
|
refname = "origin/%s" % ud.branches[ud.names[0]]
|
||||||
@@ -793,11 +633,6 @@ class Git(FetchMethod):
|
|||||||
"""
|
"""
|
||||||
Return the repository URL
|
Return the repository URL
|
||||||
"""
|
"""
|
||||||
# Note that we do not support passwords directly in the git urls. There are several
|
|
||||||
# reasons. SRC_URI can be written out to things like buildhistory and people don't
|
|
||||||
# want to leak passwords like that. Its also all too easy to share metadata without
|
|
||||||
# removing the password. ssh keys, ~/.netrc and ~/.ssh/config files can be used as
|
|
||||||
# alternatives so we will not take patches adding password support here.
|
|
||||||
if ud.user:
|
if ud.user:
|
||||||
username = ud.user + '@'
|
username = ud.user + '@'
|
||||||
else:
|
else:
|
||||||
@@ -809,6 +644,7 @@ class Git(FetchMethod):
|
|||||||
Return a unique key for the url
|
Return a unique key for the url
|
||||||
"""
|
"""
|
||||||
# Collapse adjacent slashes
|
# Collapse adjacent slashes
|
||||||
|
slash_re = re.compile(r"/+")
|
||||||
return "git:" + ud.host + slash_re.sub(".", ud.path) + ud.unresolvedrev[name]
|
return "git:" + ud.host + slash_re.sub(".", ud.path) + ud.unresolvedrev[name]
|
||||||
|
|
||||||
def _lsremote(self, ud, d, search):
|
def _lsremote(self, ud, d, search):
|
||||||
@@ -841,12 +677,6 @@ class Git(FetchMethod):
|
|||||||
"""
|
"""
|
||||||
Compute the HEAD revision for the url
|
Compute the HEAD revision for the url
|
||||||
"""
|
"""
|
||||||
if not d.getVar("__BBSRCREV_SEEN"):
|
|
||||||
raise bb.fetch2.FetchError("Recipe uses a floating tag/branch '%s' for repo '%s' without a fixed SRCREV yet doesn't call bb.fetch2.get_srcrev() (use SRCPV in PV for OE)." % (ud.unresolvedrev[name], ud.host+ud.path))
|
|
||||||
|
|
||||||
# Ensure we mark as not cached
|
|
||||||
bb.fetch2.mark_recipe_nocache(d)
|
|
||||||
|
|
||||||
output = self._lsremote(ud, d, "")
|
output = self._lsremote(ud, d, "")
|
||||||
# Tags of the form ^{} may not work, need to fallback to other form
|
# Tags of the form ^{} may not work, need to fallback to other form
|
||||||
if ud.unresolvedrev[name][:5] == "refs/" or ud.usehead:
|
if ud.unresolvedrev[name][:5] == "refs/" or ud.usehead:
|
||||||
@@ -871,42 +701,38 @@ class Git(FetchMethod):
|
|||||||
"""
|
"""
|
||||||
pupver = ('', '')
|
pupver = ('', '')
|
||||||
|
|
||||||
|
tagregex = re.compile(d.getVar('UPSTREAM_CHECK_GITTAGREGEX') or r"(?P<pver>([0-9][\.|_]?)+)")
|
||||||
try:
|
try:
|
||||||
output = self._lsremote(ud, d, "refs/tags/*")
|
output = self._lsremote(ud, d, "refs/tags/*")
|
||||||
except (bb.fetch2.FetchError, bb.fetch2.NetworkAccess) as e:
|
except (bb.fetch2.FetchError, bb.fetch2.NetworkAccess) as e:
|
||||||
bb.note("Could not list remote: %s" % str(e))
|
bb.note("Could not list remote: %s" % str(e))
|
||||||
return pupver
|
return pupver
|
||||||
|
|
||||||
rev_tag_re = re.compile(r"([0-9a-f]{40})\s+refs/tags/(.*)")
|
|
||||||
pver_re = re.compile(d.getVar('UPSTREAM_CHECK_GITTAGREGEX') or r"(?P<pver>([0-9][\.|_]?)+)")
|
|
||||||
nonrel_re = re.compile(r"(alpha|beta|rc|final)+")
|
|
||||||
|
|
||||||
verstring = ""
|
verstring = ""
|
||||||
|
revision = ""
|
||||||
for line in output.split("\n"):
|
for line in output.split("\n"):
|
||||||
if not line:
|
if not line:
|
||||||
break
|
break
|
||||||
|
|
||||||
m = rev_tag_re.match(line)
|
tag_head = line.split("/")[-1]
|
||||||
if not m:
|
|
||||||
continue
|
|
||||||
|
|
||||||
(revision, tag) = m.groups()
|
|
||||||
|
|
||||||
# Ignore non-released branches
|
# Ignore non-released branches
|
||||||
if nonrel_re.search(tag):
|
m = re.search(r"(alpha|beta|rc|final)+", tag_head)
|
||||||
|
if m:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# search for version in the line
|
# search for version in the line
|
||||||
m = pver_re.search(tag)
|
tag = tagregex.search(tag_head)
|
||||||
if not m:
|
if tag is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
pver = m.group('pver').replace("_", ".")
|
tag = tag.group('pver')
|
||||||
|
tag = tag.replace("_", ".")
|
||||||
|
|
||||||
if verstring and bb.utils.vercmp(("0", pver, ""), ("0", verstring, "")) < 0:
|
if verstring and bb.utils.vercmp(("0", tag, ""), ("0", verstring, "")) < 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
verstring = pver
|
verstring = tag
|
||||||
|
revision = line.split()[0]
|
||||||
pupver = (verstring, revision)
|
pupver = (verstring, revision)
|
||||||
|
|
||||||
return pupver
|
return pupver
|
||||||
@@ -926,8 +752,9 @@ class Git(FetchMethod):
|
|||||||
commits = None
|
commits = None
|
||||||
else:
|
else:
|
||||||
if not os.path.exists(rev_file) or not os.path.getsize(rev_file):
|
if not os.path.exists(rev_file) or not os.path.getsize(rev_file):
|
||||||
|
from pipes import quote
|
||||||
commits = bb.fetch2.runfetchcmd(
|
commits = bb.fetch2.runfetchcmd(
|
||||||
"git rev-list %s -- | wc -l" % shlex.quote(rev),
|
"git rev-list %s -- | wc -l" % quote(rev),
|
||||||
d, quiet=True).strip().lstrip('0')
|
d, quiet=True).strip().lstrip('0')
|
||||||
if commits:
|
if commits:
|
||||||
open(rev_file, "w").write("%d\n" % int(commits))
|
open(rev_file, "w").write("%d\n" % int(commits))
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class GitSM(Git):
|
|||||||
module_hash = ""
|
module_hash = ""
|
||||||
|
|
||||||
if not module_hash:
|
if not module_hash:
|
||||||
logger.debug("submodule %s is defined, but is not initialized in the repository. Skipping", m)
|
logger.debug(1, "submodule %s is defined, but is not initialized in the repository. Skipping", m)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
submodules.append(m)
|
submodules.append(m)
|
||||||
@@ -88,9 +88,9 @@ class GitSM(Git):
|
|||||||
subrevision[m] = module_hash.split()[2]
|
subrevision[m] = module_hash.split()[2]
|
||||||
|
|
||||||
# Convert relative to absolute uri based on parent uri
|
# Convert relative to absolute uri based on parent uri
|
||||||
if uris[m].startswith('..') or uris[m].startswith('./'):
|
if uris[m].startswith('..'):
|
||||||
newud = copy.copy(ud)
|
newud = copy.copy(ud)
|
||||||
newud.path = os.path.normpath(os.path.join(newud.path, uris[m]))
|
newud.path = os.path.realpath(os.path.join(newud.path, uris[m]))
|
||||||
uris[m] = Git._get_repo_url(self, newud)
|
uris[m] = Git._get_repo_url(self, newud)
|
||||||
|
|
||||||
for module in submodules:
|
for module in submodules:
|
||||||
@@ -115,21 +115,10 @@ class GitSM(Git):
|
|||||||
# This has to be a file reference
|
# This has to be a file reference
|
||||||
proto = "file"
|
proto = "file"
|
||||||
url = "gitsm://" + uris[module]
|
url = "gitsm://" + uris[module]
|
||||||
if url.endswith("{}{}".format(ud.host, ud.path)):
|
|
||||||
raise bb.fetch2.FetchError("Submodule refers to the parent repository. This will cause deadlock situation in current version of Bitbake." \
|
|
||||||
"Consider using git fetcher instead.")
|
|
||||||
|
|
||||||
url += ';protocol=%s' % proto
|
url += ';protocol=%s' % proto
|
||||||
url += ";name=%s" % module
|
url += ";name=%s" % module
|
||||||
url += ";subpath=%s" % module
|
url += ";subpath=%s" % module
|
||||||
url += ";nobranch=1"
|
|
||||||
url += ";lfs=%s" % self._need_lfs(ud)
|
|
||||||
# Note that adding "user=" here to give credentials to the
|
|
||||||
# submodule is not supported. Since using SRC_URI to give git://
|
|
||||||
# URL a password is not supported, one have to use one of the
|
|
||||||
# recommended way (eg. ~/.netrc or SSH config) which does specify
|
|
||||||
# the user (See comment in git.py).
|
|
||||||
# So, we will not take patches adding "user=" support here.
|
|
||||||
|
|
||||||
ld = d.createCopy()
|
ld = d.createCopy()
|
||||||
# Not necessary to set SRC_URI, since we're passing the URI to
|
# Not necessary to set SRC_URI, since we're passing the URI to
|
||||||
@@ -147,23 +136,20 @@ class GitSM(Git):
|
|||||||
|
|
||||||
return submodules != []
|
return submodules != []
|
||||||
|
|
||||||
def call_process_submodules(self, ud, d, extra_check, subfunc):
|
|
||||||
# If we're using a shallow mirror tarball it needs to be
|
|
||||||
# unpacked temporarily so that we can examine the .gitmodules file
|
|
||||||
if ud.shallow and os.path.exists(ud.fullshallow) and extra_check:
|
|
||||||
tmpdir = tempfile.mkdtemp(dir=d.getVar("DL_DIR"))
|
|
||||||
try:
|
|
||||||
runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=tmpdir)
|
|
||||||
self.process_submodules(ud, tmpdir, subfunc, d)
|
|
||||||
finally:
|
|
||||||
shutil.rmtree(tmpdir)
|
|
||||||
else:
|
|
||||||
self.process_submodules(ud, ud.clonedir, subfunc, d)
|
|
||||||
|
|
||||||
def need_update(self, ud, d):
|
def need_update(self, ud, d):
|
||||||
if Git.need_update(self, ud, d):
|
if Git.need_update(self, ud, d):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
if ud.revisions[ud.names[0]] in known_srcrevs.split():
|
||||||
|
return False
|
||||||
|
except bb.fetch2.FetchError:
|
||||||
|
pass
|
||||||
|
|
||||||
need_update_list = []
|
need_update_list = []
|
||||||
def need_update_submodule(ud, url, module, modpath, workdir, d):
|
def need_update_submodule(ud, url, module, modpath, workdir, d):
|
||||||
url += ";bareclone=1;nobranch=1"
|
url += ";bareclone=1;nobranch=1"
|
||||||
@@ -177,10 +163,23 @@ class GitSM(Git):
|
|||||||
logger.error('gitsm: submodule update check failed: %s %s' % (type(e).__name__, str(e)))
|
logger.error('gitsm: submodule update check failed: %s %s' % (type(e).__name__, str(e)))
|
||||||
need_update_result = True
|
need_update_result = True
|
||||||
|
|
||||||
self.call_process_submodules(ud, d, not os.path.exists(ud.clonedir), need_update_submodule)
|
# If we're using a shallow mirror tarball it needs to be unpacked
|
||||||
|
# temporarily so that we can examine the .gitmodules file
|
||||||
|
if ud.shallow and os.path.exists(ud.fullshallow) and not os.path.exists(ud.clonedir):
|
||||||
|
tmpdir = tempfile.mkdtemp(dir=d.getVar("DL_DIR"))
|
||||||
|
runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=tmpdir)
|
||||||
|
self.process_submodules(ud, tmpdir, need_update_submodule, d)
|
||||||
|
shutil.rmtree(tmpdir)
|
||||||
|
else:
|
||||||
|
self.process_submodules(ud, ud.clonedir, need_update_submodule, d)
|
||||||
|
if len(need_update_list) == 0:
|
||||||
|
# We already have the required commits of all submodules. Drop
|
||||||
|
# a nugget so we don't need to check again.
|
||||||
|
runfetchcmd("%s config --add bitbake.srcrev %s" % \
|
||||||
|
(ud.basecmd, ud.revisions[ud.names[0]]), d, workdir=ud.clonedir)
|
||||||
|
|
||||||
if need_update_list:
|
if len(need_update_list) > 0:
|
||||||
logger.debug('gitsm: Submodules requiring update: %s' % (' '.join(need_update_list)))
|
logger.debug(1, 'gitsm: Submodules requiring update: %s' % (' '.join(need_update_list)))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@@ -200,7 +199,19 @@ class GitSM(Git):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
Git.download(self, ud, d)
|
Git.download(self, ud, d)
|
||||||
self.call_process_submodules(ud, d, self.need_update(ud, d), download_submodule)
|
|
||||||
|
# If we're using a shallow mirror tarball it needs to be unpacked
|
||||||
|
# temporarily so that we can examine the .gitmodules file
|
||||||
|
if ud.shallow and os.path.exists(ud.fullshallow) and self.need_update(ud, d):
|
||||||
|
tmpdir = tempfile.mkdtemp(dir=d.getVar("DL_DIR"))
|
||||||
|
runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=tmpdir)
|
||||||
|
self.process_submodules(ud, tmpdir, download_submodule, d)
|
||||||
|
shutil.rmtree(tmpdir)
|
||||||
|
else:
|
||||||
|
self.process_submodules(ud, ud.clonedir, download_submodule, d)
|
||||||
|
# Drop a nugget for the srcrev 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)
|
||||||
|
|
||||||
def unpack(self, ud, destdir, d):
|
def unpack(self, ud, destdir, d):
|
||||||
def unpack_submodules(ud, url, module, modpath, workdir, d):
|
def unpack_submodules(ud, url, module, modpath, workdir, d):
|
||||||
@@ -214,10 +225,6 @@ class GitSM(Git):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
newfetch = Fetch([url], d, cache=False)
|
newfetch = Fetch([url], d, cache=False)
|
||||||
# modpath is needed by unpack tracer to calculate submodule
|
|
||||||
# checkout dir
|
|
||||||
new_ud = newfetch.ud[url]
|
|
||||||
new_ud.modpath = modpath
|
|
||||||
newfetch.unpack(root=os.path.dirname(os.path.join(repo_conf, 'modules', module)))
|
newfetch.unpack(root=os.path.dirname(os.path.join(repo_conf, 'modules', module)))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('gitsm: submodule unpack failed: %s %s' % (type(e).__name__, str(e)))
|
logger.error('gitsm: submodule unpack failed: %s %s' % (type(e).__name__, str(e)))
|
||||||
@@ -243,12 +250,10 @@ class GitSM(Git):
|
|||||||
ret = self.process_submodules(ud, ud.destdir, unpack_submodules, d)
|
ret = self.process_submodules(ud, ud.destdir, unpack_submodules, d)
|
||||||
|
|
||||||
if not ud.bareclone and ret:
|
if not ud.bareclone and ret:
|
||||||
# All submodules should already be downloaded and configured in the tree. This simply
|
# All submodules should already be downloaded and configured in the tree. This simply sets
|
||||||
# sets up the configuration and checks out the files. The main project config should
|
# up the configuration and checks out the files. The main project config should remain
|
||||||
# remain unmodified, and no download from the internet should occur. As such, lfs smudge
|
# unmodified, and no download from the internet should occur.
|
||||||
# should also be skipped as these files were already smudged in the fetch stage if lfs
|
runfetchcmd("%s submodule update --recursive --no-fetch" % (ud.basecmd), d, quiet=True, workdir=ud.destdir)
|
||||||
# was enabled.
|
|
||||||
runfetchcmd("GIT_LFS_SKIP_SMUDGE=1 %s submodule update --recursive --no-fetch" % (ud.basecmd), d, quiet=True, workdir=ud.destdir)
|
|
||||||
|
|
||||||
def implicit_urldata(self, ud, d):
|
def implicit_urldata(self, ud, d):
|
||||||
import shutil, subprocess, tempfile
|
import shutil, subprocess, tempfile
|
||||||
@@ -259,6 +264,14 @@ class GitSM(Git):
|
|||||||
newfetch = Fetch([url], d, cache=False)
|
newfetch = Fetch([url], d, cache=False)
|
||||||
urldata.extend(newfetch.expanded_urldata())
|
urldata.extend(newfetch.expanded_urldata())
|
||||||
|
|
||||||
self.call_process_submodules(ud, d, ud.method.need_update(ud, d), add_submodule)
|
# If we're using a shallow mirror tarball it needs to be unpacked
|
||||||
|
# temporarily so that we can examine the .gitmodules file
|
||||||
|
if ud.shallow and os.path.exists(ud.fullshallow) and ud.method.need_update(ud, d):
|
||||||
|
tmpdir = tempfile.mkdtemp(dir=d.getVar("DL_DIR"))
|
||||||
|
subprocess.check_call("tar -xzf %s" % ud.fullshallow, cwd=tmpdir, shell=True)
|
||||||
|
self.process_submodules(ud, tmpdir, add_submodule, d)
|
||||||
|
shutil.rmtree(tmpdir)
|
||||||
|
else:
|
||||||
|
self.process_submodules(ud, ud.clonedir, add_submodule, d)
|
||||||
|
|
||||||
return urldata
|
return urldata
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ class Hg(FetchMethod):
|
|||||||
def download(self, ud, d):
|
def download(self, ud, d):
|
||||||
"""Fetch url"""
|
"""Fetch url"""
|
||||||
|
|
||||||
logger.debug2("Fetch: checking for module directory '" + ud.moddir + "'")
|
logger.debug(2, "Fetch: checking for module directory '" + ud.moddir + "'")
|
||||||
|
|
||||||
# If the checkout doesn't exist and the mirror tarball does, extract it
|
# If the checkout doesn't exist and the mirror tarball does, extract it
|
||||||
if not os.path.exists(ud.pkgdir) and os.path.exists(ud.fullmirror):
|
if not os.path.exists(ud.pkgdir) and os.path.exists(ud.fullmirror):
|
||||||
@@ -160,7 +160,7 @@ class Hg(FetchMethod):
|
|||||||
if os.access(os.path.join(ud.moddir, '.hg'), os.R_OK):
|
if os.access(os.path.join(ud.moddir, '.hg'), os.R_OK):
|
||||||
# Found the source, check whether need pull
|
# Found the source, check whether need pull
|
||||||
updatecmd = self._buildhgcommand(ud, d, "update")
|
updatecmd = self._buildhgcommand(ud, d, "update")
|
||||||
logger.debug("Running %s", updatecmd)
|
logger.debug(1, "Running %s", updatecmd)
|
||||||
try:
|
try:
|
||||||
runfetchcmd(updatecmd, d, workdir=ud.moddir)
|
runfetchcmd(updatecmd, d, workdir=ud.moddir)
|
||||||
except bb.fetch2.FetchError:
|
except bb.fetch2.FetchError:
|
||||||
@@ -168,7 +168,7 @@ class Hg(FetchMethod):
|
|||||||
pullcmd = self._buildhgcommand(ud, d, "pull")
|
pullcmd = self._buildhgcommand(ud, d, "pull")
|
||||||
logger.info("Pulling " + ud.url)
|
logger.info("Pulling " + ud.url)
|
||||||
# update sources there
|
# update sources there
|
||||||
logger.debug("Running %s", pullcmd)
|
logger.debug(1, "Running %s", pullcmd)
|
||||||
bb.fetch2.check_network_access(d, pullcmd, ud.url)
|
bb.fetch2.check_network_access(d, pullcmd, ud.url)
|
||||||
runfetchcmd(pullcmd, d, workdir=ud.moddir)
|
runfetchcmd(pullcmd, d, workdir=ud.moddir)
|
||||||
try:
|
try:
|
||||||
@@ -183,14 +183,14 @@ class Hg(FetchMethod):
|
|||||||
logger.info("Fetch " + ud.url)
|
logger.info("Fetch " + ud.url)
|
||||||
# check out sources there
|
# check out sources there
|
||||||
bb.utils.mkdirhier(ud.pkgdir)
|
bb.utils.mkdirhier(ud.pkgdir)
|
||||||
logger.debug("Running %s", fetchcmd)
|
logger.debug(1, "Running %s", fetchcmd)
|
||||||
bb.fetch2.check_network_access(d, fetchcmd, ud.url)
|
bb.fetch2.check_network_access(d, fetchcmd, ud.url)
|
||||||
runfetchcmd(fetchcmd, d, workdir=ud.pkgdir)
|
runfetchcmd(fetchcmd, d, workdir=ud.pkgdir)
|
||||||
|
|
||||||
# Even when we clone (fetch), we still need to update as hg's clone
|
# Even when we clone (fetch), we still need to update as hg's clone
|
||||||
# won't checkout the specified revision if its on a branch
|
# won't checkout the specified revision if its on a branch
|
||||||
updatecmd = self._buildhgcommand(ud, d, "update")
|
updatecmd = self._buildhgcommand(ud, d, "update")
|
||||||
logger.debug("Running %s", updatecmd)
|
logger.debug(1, "Running %s", updatecmd)
|
||||||
runfetchcmd(updatecmd, d, workdir=ud.moddir)
|
runfetchcmd(updatecmd, d, workdir=ud.moddir)
|
||||||
|
|
||||||
def clean(self, ud, d):
|
def clean(self, ud, d):
|
||||||
@@ -242,15 +242,14 @@ class Hg(FetchMethod):
|
|||||||
revflag = "-r %s" % ud.revision
|
revflag = "-r %s" % ud.revision
|
||||||
subdir = ud.parm.get("destsuffix", ud.module)
|
subdir = ud.parm.get("destsuffix", ud.module)
|
||||||
codir = "%s/%s" % (destdir, subdir)
|
codir = "%s/%s" % (destdir, subdir)
|
||||||
ud.unpack_tracer.unpack("hg", codir)
|
|
||||||
|
|
||||||
scmdata = ud.parm.get("scmdata", "")
|
scmdata = ud.parm.get("scmdata", "")
|
||||||
if scmdata != "nokeep":
|
if scmdata != "nokeep":
|
||||||
proto = ud.parm.get('protocol', 'http')
|
proto = ud.parm.get('protocol', 'http')
|
||||||
if not os.access(os.path.join(codir, '.hg'), os.R_OK):
|
if not os.access(os.path.join(codir, '.hg'), os.R_OK):
|
||||||
logger.debug2("Unpack: creating new hg repository in '" + codir + "'")
|
logger.debug(2, "Unpack: creating new hg repository in '" + codir + "'")
|
||||||
runfetchcmd("%s init %s" % (ud.basecmd, codir), d)
|
runfetchcmd("%s init %s" % (ud.basecmd, codir), d)
|
||||||
logger.debug2("Unpack: updating source in '" + codir + "'")
|
logger.debug(2, "Unpack: updating source in '" + codir + "'")
|
||||||
if ud.user and ud.pswd:
|
if ud.user and ud.pswd:
|
||||||
runfetchcmd("%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" pull %s" % (ud.basecmd, ud.user, ud.pswd, proto, ud.moddir), d, workdir=codir)
|
runfetchcmd("%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" pull %s" % (ud.basecmd, ud.user, ud.pswd, proto, ud.moddir), d, workdir=codir)
|
||||||
else:
|
else:
|
||||||
@@ -260,5 +259,5 @@ class Hg(FetchMethod):
|
|||||||
else:
|
else:
|
||||||
runfetchcmd("%s up -C %s" % (ud.basecmd, revflag), d, workdir=codir)
|
runfetchcmd("%s up -C %s" % (ud.basecmd, revflag), d, workdir=codir)
|
||||||
else:
|
else:
|
||||||
logger.debug2("Unpack: extracting source to '" + codir + "'")
|
logger.debug(2, "Unpack: extracting source to '" + codir + "'")
|
||||||
runfetchcmd("%s archive -t files %s %s" % (ud.basecmd, revflag, codir), d, workdir=ud.moddir)
|
runfetchcmd("%s archive -t files %s %s" % (ud.basecmd, revflag, codir), d, workdir=ud.moddir)
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ class Local(FetchMethod):
|
|||||||
"""
|
"""
|
||||||
Return the local filename of a given url assuming a successful fetch.
|
Return the local filename of a given url assuming a successful fetch.
|
||||||
"""
|
"""
|
||||||
return self.localfile_searchpaths(urldata, d)[-1]
|
return self.localpaths(urldata, d)[-1]
|
||||||
|
|
||||||
def localfile_searchpaths(self, urldata, d):
|
def localpaths(self, urldata, d):
|
||||||
"""
|
"""
|
||||||
Return the local filename of a given url assuming a successful fetch.
|
Return the local filename of a given url assuming a successful fetch.
|
||||||
"""
|
"""
|
||||||
@@ -51,14 +51,18 @@ class Local(FetchMethod):
|
|||||||
path = urldata.decodedurl
|
path = urldata.decodedurl
|
||||||
newpath = path
|
newpath = path
|
||||||
if path[0] == "/":
|
if path[0] == "/":
|
||||||
logger.debug2("Using absolute %s" % (path))
|
|
||||||
return [path]
|
return [path]
|
||||||
filespath = d.getVar('FILESPATH')
|
filespath = d.getVar('FILESPATH')
|
||||||
if filespath:
|
if filespath:
|
||||||
logger.debug2("Searching for %s in paths:\n %s" % (path, "\n ".join(filespath.split(":"))))
|
logger.debug(2, "Searching for %s in paths:\n %s" % (path, "\n ".join(filespath.split(":"))))
|
||||||
newpath, hist = bb.utils.which(filespath, path, history=True)
|
newpath, hist = bb.utils.which(filespath, path, history=True)
|
||||||
logger.debug2("Using %s for %s" % (newpath, path))
|
|
||||||
searched.extend(hist)
|
searched.extend(hist)
|
||||||
|
if not os.path.exists(newpath):
|
||||||
|
dldirfile = os.path.join(d.getVar("DL_DIR"), path)
|
||||||
|
logger.debug(2, "Defaulting to %s for %s" % (dldirfile, path))
|
||||||
|
bb.utils.mkdirhier(os.path.dirname(dldirfile))
|
||||||
|
searched.append(dldirfile)
|
||||||
|
return searched
|
||||||
return searched
|
return searched
|
||||||
|
|
||||||
def need_update(self, ud, d):
|
def need_update(self, ud, d):
|
||||||
@@ -74,7 +78,9 @@ class Local(FetchMethod):
|
|||||||
filespath = d.getVar('FILESPATH')
|
filespath = d.getVar('FILESPATH')
|
||||||
if filespath:
|
if filespath:
|
||||||
locations = filespath.split(":")
|
locations = filespath.split(":")
|
||||||
msg = "Unable to find file " + urldata.url + " anywhere to download to " + urldata.localpath + ". The paths that were searched were:\n " + "\n ".join(locations)
|
locations.append(d.getVar("DL_DIR"))
|
||||||
|
|
||||||
|
msg = "Unable to find file " + urldata.url + " anywhere. The paths that were searched were:\n " + "\n ".join(locations)
|
||||||
raise FetchError(msg)
|
raise FetchError(msg)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -44,24 +44,17 @@ def npm_package(package):
|
|||||||
"""Convert the npm package name to remove unsupported character"""
|
"""Convert the npm package name to remove unsupported character"""
|
||||||
# Scoped package names (with the @) use the same naming convention
|
# Scoped package names (with the @) use the same naming convention
|
||||||
# as the 'npm pack' command.
|
# as the 'npm pack' command.
|
||||||
name = re.sub("/", "-", package)
|
if package.startswith("@"):
|
||||||
name = name.lower()
|
return re.sub("/", "-", package[1:])
|
||||||
name = re.sub(r"[^\-a-z0-9]", "", name)
|
return package
|
||||||
name = name.strip("-")
|
|
||||||
return name
|
|
||||||
|
|
||||||
|
|
||||||
def npm_filename(package, version):
|
def npm_filename(package, version):
|
||||||
"""Get the filename of a npm package"""
|
"""Get the filename of a npm package"""
|
||||||
return npm_package(package) + "-" + version + ".tgz"
|
return npm_package(package) + "-" + version + ".tgz"
|
||||||
|
|
||||||
def npm_localfile(package, version=None):
|
def npm_localfile(package, version):
|
||||||
"""Get the local filename of a npm package"""
|
"""Get the local filename of a npm package"""
|
||||||
if version is not None:
|
return os.path.join("npm2", npm_filename(package, version))
|
||||||
filename = npm_filename(package, version)
|
|
||||||
else:
|
|
||||||
filename = package
|
|
||||||
return os.path.join("npm2", filename)
|
|
||||||
|
|
||||||
def npm_integrity(integrity):
|
def npm_integrity(integrity):
|
||||||
"""
|
"""
|
||||||
@@ -76,52 +69,41 @@ def npm_unpack(tarball, destdir, d):
|
|||||||
bb.utils.mkdirhier(destdir)
|
bb.utils.mkdirhier(destdir)
|
||||||
cmd = "tar --extract --gzip --file=%s" % shlex.quote(tarball)
|
cmd = "tar --extract --gzip --file=%s" % shlex.quote(tarball)
|
||||||
cmd += " --no-same-owner"
|
cmd += " --no-same-owner"
|
||||||
cmd += " --delay-directory-restore"
|
|
||||||
cmd += " --strip-components=1"
|
cmd += " --strip-components=1"
|
||||||
runfetchcmd(cmd, d, workdir=destdir)
|
runfetchcmd(cmd, d, workdir=destdir)
|
||||||
runfetchcmd("chmod -R +X '%s'" % (destdir), d, quiet=True, workdir=destdir)
|
|
||||||
|
|
||||||
class NpmEnvironment(object):
|
class NpmEnvironment(object):
|
||||||
"""
|
"""
|
||||||
Using a npm config file seems more reliable than using cli arguments.
|
Using a npm config file seems more reliable than using cli arguments.
|
||||||
This class allows to create a controlled environment for npm commands.
|
This class allows to create a controlled environment for npm commands.
|
||||||
"""
|
"""
|
||||||
def __init__(self, d, configs=[], npmrc=None):
|
def __init__(self, d, configs=None):
|
||||||
self.d = d
|
self.d = d
|
||||||
|
self.configs = configs
|
||||||
self.user_config = tempfile.NamedTemporaryFile(mode="w", buffering=1)
|
|
||||||
for key, value in configs:
|
|
||||||
self.user_config.write("%s=%s\n" % (key, value))
|
|
||||||
|
|
||||||
if npmrc:
|
|
||||||
self.global_config_name = npmrc
|
|
||||||
else:
|
|
||||||
self.global_config_name = "/dev/null"
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if self.user_config:
|
|
||||||
self.user_config.close()
|
|
||||||
|
|
||||||
def run(self, cmd, args=None, configs=None, workdir=None):
|
def run(self, cmd, args=None, configs=None, workdir=None):
|
||||||
"""Run npm command in a controlled environment"""
|
"""Run npm command in a controlled environment"""
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
d = bb.data.createCopy(self.d)
|
d = bb.data.createCopy(self.d)
|
||||||
d.setVar("PATH", d.getVar("PATH")) # PATH might contain $HOME - evaluate it before patching
|
|
||||||
d.setVar("HOME", tmpdir)
|
d.setVar("HOME", tmpdir)
|
||||||
|
|
||||||
|
cfgfile = os.path.join(tmpdir, "npmrc")
|
||||||
|
|
||||||
if not workdir:
|
if not workdir:
|
||||||
workdir = tmpdir
|
workdir = tmpdir
|
||||||
|
|
||||||
def _run(cmd):
|
def _run(cmd):
|
||||||
cmd = "NPM_CONFIG_USERCONFIG=%s " % (self.user_config.name) + cmd
|
cmd = "NPM_CONFIG_USERCONFIG=%s " % cfgfile + cmd
|
||||||
cmd = "NPM_CONFIG_GLOBALCONFIG=%s " % (self.global_config_name) + cmd
|
cmd = "NPM_CONFIG_GLOBALCONFIG=%s " % cfgfile + cmd
|
||||||
return runfetchcmd(cmd, d, workdir=workdir)
|
return runfetchcmd(cmd, d, workdir=workdir)
|
||||||
|
|
||||||
|
if self.configs:
|
||||||
|
for key, value in self.configs:
|
||||||
|
_run("npm config set %s %s" % (key, shlex.quote(value)))
|
||||||
|
|
||||||
if configs:
|
if configs:
|
||||||
bb.warn("Use of configs argument of NpmEnvironment.run() function"
|
|
||||||
" is deprecated. Please use args argument instead.")
|
|
||||||
for key, value in configs:
|
for key, value in configs:
|
||||||
cmd += " --%s=%s" % (key, shlex.quote(value))
|
_run("npm config set %s %s" % (key, shlex.quote(value)))
|
||||||
|
|
||||||
if args:
|
if args:
|
||||||
for key, value in args:
|
for key, value in args:
|
||||||
@@ -160,12 +142,12 @@ class Npm(FetchMethod):
|
|||||||
raise ParameterError("Invalid 'version' parameter", ud.url)
|
raise ParameterError("Invalid 'version' parameter", ud.url)
|
||||||
|
|
||||||
# Extract the 'registry' part of the url
|
# Extract the 'registry' part of the url
|
||||||
ud.registry = re.sub(r"^npm://", "https://", ud.url.split(";")[0])
|
ud.registry = re.sub(r"^npm://", "http://", ud.url.split(";")[0])
|
||||||
|
|
||||||
# Using the 'downloadfilename' parameter as local filename
|
# Using the 'downloadfilename' parameter as local filename
|
||||||
# or the npm package name.
|
# or the npm package name.
|
||||||
if "downloadfilename" in ud.parm:
|
if "downloadfilename" in ud.parm:
|
||||||
ud.localfile = npm_localfile(d.expand(ud.parm["downloadfilename"]))
|
ud.localfile = d.expand(ud.parm["downloadfilename"])
|
||||||
else:
|
else:
|
||||||
ud.localfile = npm_localfile(ud.package, ud.version)
|
ud.localfile = npm_localfile(ud.package, ud.version)
|
||||||
|
|
||||||
@@ -183,14 +165,14 @@ class Npm(FetchMethod):
|
|||||||
|
|
||||||
def _resolve_proxy_url(self, ud, d):
|
def _resolve_proxy_url(self, ud, d):
|
||||||
def _npm_view():
|
def _npm_view():
|
||||||
args = []
|
configs = []
|
||||||
args.append(("json", "true"))
|
configs.append(("json", "true"))
|
||||||
args.append(("registry", ud.registry))
|
configs.append(("registry", ud.registry))
|
||||||
pkgver = shlex.quote(ud.package + "@" + ud.version)
|
pkgver = shlex.quote(ud.package + "@" + ud.version)
|
||||||
cmd = ud.basecmd + " view %s" % pkgver
|
cmd = ud.basecmd + " view %s" % pkgver
|
||||||
env = NpmEnvironment(d)
|
env = NpmEnvironment(d)
|
||||||
check_network_access(d, cmd, ud.registry)
|
check_network_access(d, cmd, ud.registry)
|
||||||
view_string = env.run(cmd, args=args)
|
view_string = env.run(cmd, configs=configs)
|
||||||
|
|
||||||
if not view_string:
|
if not view_string:
|
||||||
raise FetchError("Unavailable package %s" % pkgver, ud.url)
|
raise FetchError("Unavailable package %s" % pkgver, ud.url)
|
||||||
@@ -298,7 +280,6 @@ class Npm(FetchMethod):
|
|||||||
destsuffix = ud.parm.get("destsuffix", "npm")
|
destsuffix = ud.parm.get("destsuffix", "npm")
|
||||||
destdir = os.path.join(rootdir, destsuffix)
|
destdir = os.path.join(rootdir, destsuffix)
|
||||||
npm_unpack(ud.localpath, destdir, d)
|
npm_unpack(ud.localpath, destdir, d)
|
||||||
ud.unpack_tracer.unpack("npm", destdir)
|
|
||||||
|
|
||||||
def clean(self, ud, d):
|
def clean(self, ud, d):
|
||||||
"""Clean any existing full or partial download"""
|
"""Clean any existing full or partial download"""
|
||||||
|
|||||||
@@ -24,14 +24,11 @@ import bb
|
|||||||
from bb.fetch2 import Fetch
|
from bb.fetch2 import Fetch
|
||||||
from bb.fetch2 import FetchMethod
|
from bb.fetch2 import FetchMethod
|
||||||
from bb.fetch2 import ParameterError
|
from bb.fetch2 import ParameterError
|
||||||
from bb.fetch2 import runfetchcmd
|
|
||||||
from bb.fetch2 import URI
|
from bb.fetch2 import URI
|
||||||
from bb.fetch2.npm import npm_integrity
|
from bb.fetch2.npm import npm_integrity
|
||||||
from bb.fetch2.npm import npm_localfile
|
from bb.fetch2.npm import npm_localfile
|
||||||
from bb.fetch2.npm import npm_unpack
|
from bb.fetch2.npm import npm_unpack
|
||||||
from bb.utils import is_semver
|
from bb.utils import is_semver
|
||||||
from bb.utils import lockfile
|
|
||||||
from bb.utils import unlockfile
|
|
||||||
|
|
||||||
def foreach_dependencies(shrinkwrap, callback=None, dev=False):
|
def foreach_dependencies(shrinkwrap, callback=None, dev=False):
|
||||||
"""
|
"""
|
||||||
@@ -41,9 +38,8 @@ def foreach_dependencies(shrinkwrap, callback=None, dev=False):
|
|||||||
with:
|
with:
|
||||||
name = the package name (string)
|
name = the package name (string)
|
||||||
params = the package parameters (dictionary)
|
params = the package parameters (dictionary)
|
||||||
destdir = the destination of the package (string)
|
deptree = the package dependency tree (array of strings)
|
||||||
"""
|
"""
|
||||||
# For handling old style dependencies entries in shinkwrap files
|
|
||||||
def _walk_deps(deps, deptree):
|
def _walk_deps(deps, deptree):
|
||||||
for name in deps:
|
for name in deps:
|
||||||
subtree = [*deptree, name]
|
subtree = [*deptree, name]
|
||||||
@@ -53,22 +49,9 @@ def foreach_dependencies(shrinkwrap, callback=None, dev=False):
|
|||||||
continue
|
continue
|
||||||
elif deps[name].get("bundled", False):
|
elif deps[name].get("bundled", False):
|
||||||
continue
|
continue
|
||||||
destsubdirs = [os.path.join("node_modules", dep) for dep in subtree]
|
callback(name, deps[name], subtree)
|
||||||
destsuffix = os.path.join(*destsubdirs)
|
|
||||||
callback(name, deps[name], destsuffix)
|
|
||||||
|
|
||||||
# packages entry means new style shrinkwrap file, else use dependencies
|
_walk_deps(shrinkwrap.get("dependencies", {}), [])
|
||||||
packages = shrinkwrap.get("packages", None)
|
|
||||||
if packages is not None:
|
|
||||||
for package in packages:
|
|
||||||
if package != "":
|
|
||||||
name = package.split('node_modules/')[-1]
|
|
||||||
package_infos = packages.get(package, {})
|
|
||||||
if dev == False and package_infos.get("dev", False):
|
|
||||||
continue
|
|
||||||
callback(name, package_infos, package)
|
|
||||||
else:
|
|
||||||
_walk_deps(shrinkwrap.get("dependencies", {}), [])
|
|
||||||
|
|
||||||
class NpmShrinkWrap(FetchMethod):
|
class NpmShrinkWrap(FetchMethod):
|
||||||
"""Class to fetch all package from a shrinkwrap file"""
|
"""Class to fetch all package from a shrinkwrap file"""
|
||||||
@@ -89,22 +72,19 @@ class NpmShrinkWrap(FetchMethod):
|
|||||||
# Resolve the dependencies
|
# Resolve the dependencies
|
||||||
ud.deps = []
|
ud.deps = []
|
||||||
|
|
||||||
def _resolve_dependency(name, params, destsuffix):
|
def _resolve_dependency(name, params, deptree):
|
||||||
url = None
|
url = None
|
||||||
localpath = None
|
localpath = None
|
||||||
extrapaths = []
|
extrapaths = []
|
||||||
unpack = True
|
destsubdirs = [os.path.join("node_modules", dep) for dep in deptree]
|
||||||
|
destsuffix = os.path.join(*destsubdirs)
|
||||||
|
|
||||||
integrity = params.get("integrity", None)
|
integrity = params.get("integrity", None)
|
||||||
resolved = params.get("resolved", None)
|
resolved = params.get("resolved", None)
|
||||||
version = params.get("version", None)
|
version = params.get("version", None)
|
||||||
|
|
||||||
# Handle registry sources
|
# Handle registry sources
|
||||||
if is_semver(version) and integrity:
|
if is_semver(version) and resolved and integrity:
|
||||||
# Handle duplicate dependencies without url
|
|
||||||
if not resolved:
|
|
||||||
return
|
|
||||||
|
|
||||||
localfile = npm_localfile(name, version)
|
localfile = npm_localfile(name, version)
|
||||||
|
|
||||||
uri = URI(resolved)
|
uri = URI(resolved)
|
||||||
@@ -129,7 +109,7 @@ class NpmShrinkWrap(FetchMethod):
|
|||||||
|
|
||||||
# Handle http tarball sources
|
# Handle http tarball sources
|
||||||
elif version.startswith("http") and integrity:
|
elif version.startswith("http") and integrity:
|
||||||
localfile = npm_localfile(os.path.basename(version))
|
localfile = os.path.join("npm2", os.path.basename(version))
|
||||||
|
|
||||||
uri = URI(version)
|
uri = URI(version)
|
||||||
uri.params["downloadfilename"] = localfile
|
uri.params["downloadfilename"] = localfile
|
||||||
@@ -141,28 +121,8 @@ class NpmShrinkWrap(FetchMethod):
|
|||||||
|
|
||||||
localpath = os.path.join(d.getVar("DL_DIR"), localfile)
|
localpath = os.path.join(d.getVar("DL_DIR"), localfile)
|
||||||
|
|
||||||
# Handle local tarball and link sources
|
|
||||||
elif version.startswith("file"):
|
|
||||||
localpath = version[5:]
|
|
||||||
if not version.endswith(".tgz"):
|
|
||||||
unpack = False
|
|
||||||
|
|
||||||
# Handle git sources
|
# Handle git sources
|
||||||
elif version.startswith(("git", "bitbucket","gist")) or (
|
elif version.startswith("git"):
|
||||||
not version.endswith((".tgz", ".tar", ".tar.gz"))
|
|
||||||
and not version.startswith((".", "@", "/"))
|
|
||||||
and "/" in version
|
|
||||||
):
|
|
||||||
if version.startswith("github:"):
|
|
||||||
version = "git+https://github.com/" + version[len("github:"):]
|
|
||||||
elif version.startswith("gist:"):
|
|
||||||
version = "git+https://gist.github.com/" + version[len("gist:"):]
|
|
||||||
elif version.startswith("bitbucket:"):
|
|
||||||
version = "git+https://bitbucket.org/" + version[len("bitbucket:"):]
|
|
||||||
elif version.startswith("gitlab:"):
|
|
||||||
version = "git+https://gitlab.com/" + version[len("gitlab:"):]
|
|
||||||
elif not version.startswith(("git+","git:")):
|
|
||||||
version = "git+https://github.com/" + version
|
|
||||||
regex = re.compile(r"""
|
regex = re.compile(r"""
|
||||||
^
|
^
|
||||||
git\+
|
git\+
|
||||||
@@ -188,17 +148,15 @@ class NpmShrinkWrap(FetchMethod):
|
|||||||
|
|
||||||
url = str(uri)
|
url = str(uri)
|
||||||
|
|
||||||
|
# local tarball sources and local link sources are unsupported
|
||||||
else:
|
else:
|
||||||
raise ParameterError("Unsupported dependency: %s" % name, ud.url)
|
raise ParameterError("Unsupported dependency: %s" % name, ud.url)
|
||||||
|
|
||||||
# name is needed by unpack tracer for module mapping
|
|
||||||
ud.deps.append({
|
ud.deps.append({
|
||||||
"name": name,
|
|
||||||
"url": url,
|
"url": url,
|
||||||
"localpath": localpath,
|
"localpath": localpath,
|
||||||
"extrapaths": extrapaths,
|
"extrapaths": extrapaths,
|
||||||
"destsuffix": destsuffix,
|
"destsuffix": destsuffix,
|
||||||
"unpack": unpack,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -219,23 +177,17 @@ class NpmShrinkWrap(FetchMethod):
|
|||||||
# This fetcher resolves multiple URIs from a shrinkwrap file and then
|
# This fetcher resolves multiple URIs from a shrinkwrap file and then
|
||||||
# forwards it to a proxy fetcher. The management of the donestamp file,
|
# forwards it to a proxy fetcher. The management of the donestamp file,
|
||||||
# the lockfile and the checksums are forwarded to the proxy fetcher.
|
# the lockfile and the checksums are forwarded to the proxy fetcher.
|
||||||
shrinkwrap_urls = [dep["url"] for dep in ud.deps if dep["url"]]
|
ud.proxy = Fetch([dep["url"] for dep in ud.deps], data)
|
||||||
if shrinkwrap_urls:
|
|
||||||
ud.proxy = Fetch(shrinkwrap_urls, data)
|
|
||||||
ud.needdonestamp = False
|
ud.needdonestamp = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _foreach_proxy_method(ud, handle):
|
def _foreach_proxy_method(ud, handle):
|
||||||
returns = []
|
returns = []
|
||||||
#Check if there are dependencies before try to fetch them
|
for proxy_url in ud.proxy.urls:
|
||||||
if len(ud.deps) > 0:
|
proxy_ud = ud.proxy.ud[proxy_url]
|
||||||
for proxy_url in ud.proxy.urls:
|
proxy_d = ud.proxy.d
|
||||||
proxy_ud = ud.proxy.ud[proxy_url]
|
proxy_ud.setup_localpath(proxy_d)
|
||||||
proxy_d = ud.proxy.d
|
returns.append(handle(proxy_ud.method, proxy_ud, proxy_d))
|
||||||
proxy_ud.setup_localpath(proxy_d)
|
|
||||||
lf = lockfile(proxy_ud.lockfile)
|
|
||||||
returns.append(handle(proxy_ud.method, proxy_ud, proxy_d))
|
|
||||||
unlockfile(lf)
|
|
||||||
return returns
|
return returns
|
||||||
|
|
||||||
def verify_donestamp(self, ud, d):
|
def verify_donestamp(self, ud, d):
|
||||||
@@ -272,7 +224,6 @@ class NpmShrinkWrap(FetchMethod):
|
|||||||
destsuffix = ud.parm.get("destsuffix")
|
destsuffix = ud.parm.get("destsuffix")
|
||||||
if destsuffix:
|
if destsuffix:
|
||||||
destdir = os.path.join(rootdir, destsuffix)
|
destdir = os.path.join(rootdir, destsuffix)
|
||||||
ud.unpack_tracer.unpack("npm-shrinkwrap", destdir)
|
|
||||||
|
|
||||||
bb.utils.mkdirhier(destdir)
|
bb.utils.mkdirhier(destdir)
|
||||||
bb.utils.copyfile(ud.shrinkwrap_file,
|
bb.utils.copyfile(ud.shrinkwrap_file,
|
||||||
@@ -286,16 +237,7 @@ class NpmShrinkWrap(FetchMethod):
|
|||||||
|
|
||||||
for dep in manual:
|
for dep in manual:
|
||||||
depdestdir = os.path.join(destdir, dep["destsuffix"])
|
depdestdir = os.path.join(destdir, dep["destsuffix"])
|
||||||
if dep["url"]:
|
npm_unpack(dep["localpath"], depdestdir, d)
|
||||||
npm_unpack(dep["localpath"], depdestdir, d)
|
|
||||||
else:
|
|
||||||
depsrcdir= os.path.join(destdir, dep["localpath"])
|
|
||||||
if dep["unpack"]:
|
|
||||||
npm_unpack(depsrcdir, depdestdir, d)
|
|
||||||
else:
|
|
||||||
bb.utils.mkdirhier(depdestdir)
|
|
||||||
cmd = 'cp -fpPRH "%s/." .' % (depsrcdir)
|
|
||||||
runfetchcmd(cmd, d, workdir=depdestdir)
|
|
||||||
|
|
||||||
def clean(self, ud, d):
|
def clean(self, ud, d):
|
||||||
"""Clean any existing full or partial download"""
|
"""Clean any existing full or partial download"""
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#
|
#
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
#
|
#
|
||||||
"""
|
"""
|
||||||
@@ -11,7 +9,6 @@ Based on the svn "Fetch" implementation.
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import bb
|
import bb
|
||||||
from bb.fetch2 import FetchMethod
|
from bb.fetch2 import FetchMethod
|
||||||
from bb.fetch2 import FetchError
|
from bb.fetch2 import FetchError
|
||||||
@@ -39,7 +36,6 @@ class Osc(FetchMethod):
|
|||||||
# Create paths to osc checkouts
|
# Create paths to osc checkouts
|
||||||
oscdir = d.getVar("OSCDIR") or (d.getVar("DL_DIR") + "/osc")
|
oscdir = d.getVar("OSCDIR") or (d.getVar("DL_DIR") + "/osc")
|
||||||
relpath = self._strip_leading_slashes(ud.path)
|
relpath = self._strip_leading_slashes(ud.path)
|
||||||
ud.oscdir = oscdir
|
|
||||||
ud.pkgdir = os.path.join(oscdir, ud.host)
|
ud.pkgdir = os.path.join(oscdir, ud.host)
|
||||||
ud.moddir = os.path.join(ud.pkgdir, relpath, ud.module)
|
ud.moddir = os.path.join(ud.pkgdir, relpath, ud.module)
|
||||||
|
|
||||||
@@ -47,13 +43,13 @@ class Osc(FetchMethod):
|
|||||||
ud.revision = ud.parm['rev']
|
ud.revision = ud.parm['rev']
|
||||||
else:
|
else:
|
||||||
pv = d.getVar("PV", False)
|
pv = d.getVar("PV", False)
|
||||||
rev = bb.fetch2.srcrev_internal_helper(ud, d, '')
|
rev = bb.fetch2.srcrev_internal_helper(ud, d)
|
||||||
if rev:
|
if rev:
|
||||||
ud.revision = rev
|
ud.revision = rev
|
||||||
else:
|
else:
|
||||||
ud.revision = ""
|
ud.revision = ""
|
||||||
|
|
||||||
ud.localfile = d.expand('%s_%s_%s.tar.gz' % (ud.module.replace('/', '.'), relpath.replace('/', '.'), ud.revision))
|
ud.localfile = d.expand('%s_%s_%s.tar.gz' % (ud.module.replace('/', '.'), ud.path.replace('/', '.'), ud.revision))
|
||||||
|
|
||||||
def _buildosccommand(self, ud, d, command):
|
def _buildosccommand(self, ud, d, command):
|
||||||
"""
|
"""
|
||||||
@@ -63,61 +59,38 @@ class Osc(FetchMethod):
|
|||||||
|
|
||||||
basecmd = d.getVar("FETCHCMD_osc") or "/usr/bin/env osc"
|
basecmd = d.getVar("FETCHCMD_osc") or "/usr/bin/env osc"
|
||||||
|
|
||||||
proto = ud.parm.get('protocol', 'https')
|
proto = ud.parm.get('protocol', 'ocs')
|
||||||
|
|
||||||
options = []
|
options = []
|
||||||
|
|
||||||
config = "-c %s" % self.generate_config(ud, d)
|
config = "-c %s" % self.generate_config(ud, d)
|
||||||
|
|
||||||
if getattr(ud, 'revision', ''):
|
if ud.revision:
|
||||||
options.append("-r %s" % ud.revision)
|
options.append("-r %s" % ud.revision)
|
||||||
|
|
||||||
coroot = self._strip_leading_slashes(ud.path)
|
coroot = self._strip_leading_slashes(ud.path)
|
||||||
|
|
||||||
if command == "fetch":
|
if command == "fetch":
|
||||||
osccmd = "%s %s -A %s://%s co %s/%s %s" % (basecmd, config, proto, ud.host, coroot, ud.module, " ".join(options))
|
osccmd = "%s %s co %s/%s %s" % (basecmd, config, coroot, ud.module, " ".join(options))
|
||||||
elif command == "update":
|
elif command == "update":
|
||||||
osccmd = "%s %s -A %s://%s up %s" % (basecmd, config, proto, ud.host, " ".join(options))
|
osccmd = "%s %s up %s" % (basecmd, config, " ".join(options))
|
||||||
elif command == "api_source":
|
|
||||||
osccmd = "%s %s -A %s://%s api source/%s/%s" % (basecmd, config, proto, ud.host, coroot, ud.module)
|
|
||||||
else:
|
else:
|
||||||
raise FetchError("Invalid osc command %s" % command, ud.url)
|
raise FetchError("Invalid osc command %s" % command, ud.url)
|
||||||
|
|
||||||
return osccmd
|
return osccmd
|
||||||
|
|
||||||
def _latest_revision(self, ud, d, name):
|
|
||||||
"""
|
|
||||||
Fetch latest revision for the given package
|
|
||||||
"""
|
|
||||||
api_source_cmd = self._buildosccommand(ud, d, "api_source")
|
|
||||||
|
|
||||||
output = runfetchcmd(api_source_cmd, d)
|
|
||||||
match = re.match(r'<directory ?.* rev="(\d+)".*>', output)
|
|
||||||
if match is None:
|
|
||||||
raise FetchError("Unable to parse osc response", ud.url)
|
|
||||||
return match.groups()[0]
|
|
||||||
|
|
||||||
def _revision_key(self, ud, d, name):
|
|
||||||
"""
|
|
||||||
Return a unique key for the url
|
|
||||||
"""
|
|
||||||
# Collapse adjacent slashes
|
|
||||||
slash_re = re.compile(r"/+")
|
|
||||||
rev = getattr(ud, 'revision', "latest")
|
|
||||||
return "osc:%s%s.%s.%s" % (ud.host, slash_re.sub(".", ud.path), name, rev)
|
|
||||||
|
|
||||||
def download(self, ud, d):
|
def download(self, ud, d):
|
||||||
"""
|
"""
|
||||||
Fetch url
|
Fetch url
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.debug2("Fetch: checking for module directory '" + ud.moddir + "'")
|
logger.debug(2, "Fetch: checking for module directory '" + ud.moddir + "'")
|
||||||
|
|
||||||
if os.access(ud.moddir, os.R_OK):
|
if os.access(os.path.join(d.getVar('OSCDIR'), ud.path, ud.module), os.R_OK):
|
||||||
oscupdatecmd = self._buildosccommand(ud, d, "update")
|
oscupdatecmd = self._buildosccommand(ud, d, "update")
|
||||||
logger.info("Update "+ ud.url)
|
logger.info("Update "+ ud.url)
|
||||||
# update sources there
|
# update sources there
|
||||||
logger.debug("Running %s", oscupdatecmd)
|
logger.debug(1, "Running %s", oscupdatecmd)
|
||||||
bb.fetch2.check_network_access(d, oscupdatecmd, ud.url)
|
bb.fetch2.check_network_access(d, oscupdatecmd, ud.url)
|
||||||
runfetchcmd(oscupdatecmd, d, workdir=ud.moddir)
|
runfetchcmd(oscupdatecmd, d, workdir=ud.moddir)
|
||||||
else:
|
else:
|
||||||
@@ -125,7 +98,7 @@ class Osc(FetchMethod):
|
|||||||
logger.info("Fetch " + ud.url)
|
logger.info("Fetch " + ud.url)
|
||||||
# check out sources there
|
# check out sources there
|
||||||
bb.utils.mkdirhier(ud.pkgdir)
|
bb.utils.mkdirhier(ud.pkgdir)
|
||||||
logger.debug("Running %s", oscfetchcmd)
|
logger.debug(1, "Running %s", oscfetchcmd)
|
||||||
bb.fetch2.check_network_access(d, oscfetchcmd, ud.url)
|
bb.fetch2.check_network_access(d, oscfetchcmd, ud.url)
|
||||||
runfetchcmd(oscfetchcmd, d, workdir=ud.pkgdir)
|
runfetchcmd(oscfetchcmd, d, workdir=ud.pkgdir)
|
||||||
|
|
||||||
@@ -141,23 +114,20 @@ class Osc(FetchMethod):
|
|||||||
Generate a .oscrc to be used for this run.
|
Generate a .oscrc to be used for this run.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config_path = os.path.join(ud.oscdir, "oscrc")
|
config_path = os.path.join(d.getVar('OSCDIR'), "oscrc")
|
||||||
if not os.path.exists(ud.oscdir):
|
|
||||||
bb.utils.mkdirhier(ud.oscdir)
|
|
||||||
|
|
||||||
if (os.path.exists(config_path)):
|
if (os.path.exists(config_path)):
|
||||||
os.remove(config_path)
|
os.remove(config_path)
|
||||||
|
|
||||||
f = open(config_path, 'w')
|
f = open(config_path, 'w')
|
||||||
proto = ud.parm.get('protocol', 'https')
|
|
||||||
f.write("[general]\n")
|
f.write("[general]\n")
|
||||||
f.write("apiurl = %s://%s\n" % (proto, ud.host))
|
f.write("apisrv = %s\n" % ud.host)
|
||||||
|
f.write("scheme = http\n")
|
||||||
f.write("su-wrapper = su -c\n")
|
f.write("su-wrapper = su -c\n")
|
||||||
f.write("build-root = %s\n" % d.getVar('WORKDIR'))
|
f.write("build-root = %s\n" % d.getVar('WORKDIR'))
|
||||||
f.write("urllist = %s\n" % d.getVar("OSCURLLIST"))
|
f.write("urllist = %s\n" % d.getVar("OSCURLLIST"))
|
||||||
f.write("extra-pkgs = gzip\n")
|
f.write("extra-pkgs = gzip\n")
|
||||||
f.write("\n")
|
f.write("\n")
|
||||||
f.write("[%s://%s]\n" % (proto, ud.host))
|
f.write("[%s]\n" % ud.host)
|
||||||
f.write("user = %s\n" % ud.parm["user"])
|
f.write("user = %s\n" % ud.parm["user"])
|
||||||
f.write("pass = %s\n" % ud.parm["pswd"])
|
f.write("pass = %s\n" % ud.parm["pswd"])
|
||||||
f.close()
|
f.close()
|
||||||
|
|||||||
@@ -90,16 +90,16 @@ class Perforce(FetchMethod):
|
|||||||
p4port = d.getVar('P4PORT')
|
p4port = d.getVar('P4PORT')
|
||||||
|
|
||||||
if p4port:
|
if p4port:
|
||||||
logger.debug('Using recipe provided P4PORT: %s' % p4port)
|
logger.debug(1, 'Using recipe provided P4PORT: %s' % p4port)
|
||||||
ud.host = p4port
|
ud.host = p4port
|
||||||
else:
|
else:
|
||||||
logger.debug('Trying to use P4CONFIG to automatically set P4PORT...')
|
logger.debug(1, 'Trying to use P4CONFIG to automatically set P4PORT...')
|
||||||
ud.usingp4config = True
|
ud.usingp4config = True
|
||||||
p4cmd = '%s info | grep "Server address"' % ud.basecmd
|
p4cmd = '%s info | grep "Server address"' % ud.basecmd
|
||||||
bb.fetch2.check_network_access(d, p4cmd, ud.url)
|
bb.fetch2.check_network_access(d, p4cmd, ud.url)
|
||||||
ud.host = runfetchcmd(p4cmd, d, True)
|
ud.host = runfetchcmd(p4cmd, d, True)
|
||||||
ud.host = ud.host.split(': ')[1].strip()
|
ud.host = ud.host.split(': ')[1].strip()
|
||||||
logger.debug('Determined P4PORT to be: %s' % ud.host)
|
logger.debug(1, 'Determined P4PORT to be: %s' % ud.host)
|
||||||
if not ud.host:
|
if not ud.host:
|
||||||
raise FetchError('Could not determine P4PORT from P4CONFIG')
|
raise FetchError('Could not determine P4PORT from P4CONFIG')
|
||||||
|
|
||||||
@@ -119,7 +119,6 @@ class Perforce(FetchMethod):
|
|||||||
cleanedpath = ud.path.replace('/...', '').replace('/', '.')
|
cleanedpath = ud.path.replace('/...', '').replace('/', '.')
|
||||||
cleanedhost = ud.host.replace(':', '.')
|
cleanedhost = ud.host.replace(':', '.')
|
||||||
|
|
||||||
cleanedmodule = ""
|
|
||||||
# Merge the path and module into the final depot location
|
# Merge the path and module into the final depot location
|
||||||
if ud.module:
|
if ud.module:
|
||||||
if ud.module.find('/') == 0:
|
if ud.module.find('/') == 0:
|
||||||
@@ -134,7 +133,7 @@ class Perforce(FetchMethod):
|
|||||||
|
|
||||||
ud.setup_revisions(d)
|
ud.setup_revisions(d)
|
||||||
|
|
||||||
ud.localfile = d.expand('%s_%s_%s_%s.tar.gz' % (cleanedhost, cleanedpath, cleanedmodule, ud.revision))
|
ud.localfile = d.expand('%s_%s_%s.tar.gz' % (cleanedhost, cleanedpath, ud.revision))
|
||||||
|
|
||||||
def _buildp4command(self, ud, d, command, depot_filename=None):
|
def _buildp4command(self, ud, d, command, depot_filename=None):
|
||||||
"""
|
"""
|
||||||
@@ -208,7 +207,7 @@ class Perforce(FetchMethod):
|
|||||||
for filename in p4fileslist:
|
for filename in p4fileslist:
|
||||||
item = filename.split(' - ')
|
item = filename.split(' - ')
|
||||||
lastaction = item[1].split()
|
lastaction = item[1].split()
|
||||||
logger.debug('File: %s Last Action: %s' % (item[0], lastaction[0]))
|
logger.debug(1, 'File: %s Last Action: %s' % (item[0], lastaction[0]))
|
||||||
if lastaction[0] == 'delete':
|
if lastaction[0] == 'delete':
|
||||||
continue
|
continue
|
||||||
filelist.append(item[0])
|
filelist.append(item[0])
|
||||||
@@ -255,7 +254,7 @@ class Perforce(FetchMethod):
|
|||||||
raise FetchError('Could not determine the latest perforce changelist')
|
raise FetchError('Could not determine the latest perforce changelist')
|
||||||
|
|
||||||
tipcset = tip.split(' ')[1]
|
tipcset = tip.split(' ')[1]
|
||||||
logger.debug('p4 tip found to be changelist %s' % tipcset)
|
logger.debug(1, 'p4 tip found to be changelist %s' % tipcset)
|
||||||
return tipcset
|
return tipcset
|
||||||
|
|
||||||
def sortable_revision(self, ud, d, name):
|
def sortable_revision(self, ud, d, name):
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class Repo(FetchMethod):
|
|||||||
"""Fetch url"""
|
"""Fetch url"""
|
||||||
|
|
||||||
if os.access(os.path.join(d.getVar("DL_DIR"), ud.localfile), os.R_OK):
|
if os.access(os.path.join(d.getVar("DL_DIR"), ud.localfile), os.R_OK):
|
||||||
logger.debug("%s already exists (or was stashed). Skipping repo init / sync.", ud.localpath)
|
logger.debug(1, "%s already exists (or was stashed). Skipping repo init / sync.", ud.localpath)
|
||||||
return
|
return
|
||||||
|
|
||||||
repodir = d.getVar("REPODIR") or (d.getVar("DL_DIR") + "/repo")
|
repodir = d.getVar("REPODIR") or (d.getVar("DL_DIR") + "/repo")
|
||||||
|
|||||||
@@ -18,47 +18,10 @@ The aws tool must be correctly installed and configured prior to use.
|
|||||||
import os
|
import os
|
||||||
import bb
|
import bb
|
||||||
import urllib.request, urllib.parse, urllib.error
|
import urllib.request, urllib.parse, urllib.error
|
||||||
import re
|
|
||||||
from bb.fetch2 import FetchMethod
|
from bb.fetch2 import FetchMethod
|
||||||
from bb.fetch2 import FetchError
|
from bb.fetch2 import FetchError
|
||||||
from bb.fetch2 import runfetchcmd
|
from bb.fetch2 import runfetchcmd
|
||||||
|
|
||||||
def convertToBytes(value, unit):
|
|
||||||
value = float(value)
|
|
||||||
if (unit == "KiB"):
|
|
||||||
value = value*1024.0;
|
|
||||||
elif (unit == "MiB"):
|
|
||||||
value = value*1024.0*1024.0;
|
|
||||||
elif (unit == "GiB"):
|
|
||||||
value = value*1024.0*1024.0*1024.0;
|
|
||||||
return value
|
|
||||||
|
|
||||||
class S3ProgressHandler(bb.progress.LineFilterProgressHandler):
|
|
||||||
"""
|
|
||||||
Extract progress information from s3 cp output, e.g.:
|
|
||||||
Completed 5.1 KiB/8.8 GiB (12.0 MiB/s) with 1 file(s) remaining
|
|
||||||
"""
|
|
||||||
def __init__(self, d):
|
|
||||||
super(S3ProgressHandler, self).__init__(d)
|
|
||||||
# Send an initial progress event so the bar gets shown
|
|
||||||
self._fire_progress(0)
|
|
||||||
|
|
||||||
def writeline(self, line):
|
|
||||||
percs = re.findall(r'^Completed (\d+.{0,1}\d*) (\w+)\/(\d+.{0,1}\d*) (\w+) (\(.+\)) with\s+', line)
|
|
||||||
if percs:
|
|
||||||
completed = (percs[-1][0])
|
|
||||||
completedUnit = (percs[-1][1])
|
|
||||||
total = (percs[-1][2])
|
|
||||||
totalUnit = (percs[-1][3])
|
|
||||||
completed = convertToBytes(completed, completedUnit)
|
|
||||||
total = convertToBytes(total, totalUnit)
|
|
||||||
progress = (completed/total)*100.0
|
|
||||||
rate = percs[-1][4]
|
|
||||||
self.update(progress, rate)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class S3(FetchMethod):
|
class S3(FetchMethod):
|
||||||
"""Class to fetch urls via 'aws s3'"""
|
"""Class to fetch urls via 'aws s3'"""
|
||||||
|
|
||||||
@@ -89,9 +52,7 @@ class S3(FetchMethod):
|
|||||||
|
|
||||||
cmd = '%s cp s3://%s%s %s' % (ud.basecmd, ud.host, ud.path, ud.localpath)
|
cmd = '%s cp s3://%s%s %s' % (ud.basecmd, ud.host, ud.path, ud.localpath)
|
||||||
bb.fetch2.check_network_access(d, cmd, ud.url)
|
bb.fetch2.check_network_access(d, cmd, ud.url)
|
||||||
|
runfetchcmd(cmd, d)
|
||||||
progresshandler = S3ProgressHandler(d)
|
|
||||||
runfetchcmd(cmd, d, False, log=progresshandler)
|
|
||||||
|
|
||||||
# Additional sanity checks copied from the wget class (although there
|
# Additional sanity checks copied from the wget class (although there
|
||||||
# are no known issues which mean these are required, treat the aws cli
|
# are no known issues which mean these are required, treat the aws cli
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class SFTP(FetchMethod):
|
|||||||
if path[:3] == '/~/':
|
if path[:3] == '/~/':
|
||||||
path = path[3:]
|
path = path[3:]
|
||||||
|
|
||||||
remote = '"%s%s:%s"' % (user, urlo.hostname, path)
|
remote = '%s%s:%s' % (user, urlo.hostname, path)
|
||||||
|
|
||||||
cmd = '%s %s %s %s' % (basecmd, port, remote, lpath)
|
cmd = '%s %s %s %s' % (basecmd, port, remote, lpath)
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ IETF secsh internet draft:
|
|||||||
|
|
||||||
import re, os
|
import re, os
|
||||||
from bb.fetch2 import check_network_access, FetchMethod, ParameterError, runfetchcmd
|
from bb.fetch2 import check_network_access, FetchMethod, ParameterError, runfetchcmd
|
||||||
import urllib
|
|
||||||
|
|
||||||
|
|
||||||
__pattern__ = re.compile(r'''
|
__pattern__ = re.compile(r'''
|
||||||
@@ -41,9 +40,9 @@ __pattern__ = re.compile(r'''
|
|||||||
( # Optional username/password block
|
( # Optional username/password block
|
||||||
(?P<user>\S+) # username
|
(?P<user>\S+) # username
|
||||||
(:(?P<pass>\S+))? # colon followed by the password (optional)
|
(:(?P<pass>\S+))? # colon followed by the password (optional)
|
||||||
|
)?
|
||||||
(?P<cparam>(;[^;]+)*)? # connection parameters block (optional)
|
(?P<cparam>(;[^;]+)*)? # connection parameters block (optional)
|
||||||
@
|
@
|
||||||
)?
|
|
||||||
(?P<host>\S+?) # non-greedy match of the host
|
(?P<host>\S+?) # non-greedy match of the host
|
||||||
(:(?P<port>[0-9]+))? # colon followed by the port (optional)
|
(:(?P<port>[0-9]+))? # colon followed by the port (optional)
|
||||||
/
|
/
|
||||||
@@ -71,7 +70,6 @@ class SSH(FetchMethod):
|
|||||||
"git:// prefix with protocol=ssh", urldata.url)
|
"git:// prefix with protocol=ssh", urldata.url)
|
||||||
m = __pattern__.match(urldata.url)
|
m = __pattern__.match(urldata.url)
|
||||||
path = m.group('path')
|
path = m.group('path')
|
||||||
path = urllib.parse.unquote(path)
|
|
||||||
host = m.group('host')
|
host = m.group('host')
|
||||||
urldata.localpath = os.path.join(d.getVar('DL_DIR'),
|
urldata.localpath = os.path.join(d.getVar('DL_DIR'),
|
||||||
os.path.basename(os.path.normpath(path)))
|
os.path.basename(os.path.normpath(path)))
|
||||||
@@ -98,11 +96,6 @@ class SSH(FetchMethod):
|
|||||||
fr += '@%s' % host
|
fr += '@%s' % host
|
||||||
else:
|
else:
|
||||||
fr = host
|
fr = host
|
||||||
|
|
||||||
if path[0] != '~':
|
|
||||||
path = '/%s' % path
|
|
||||||
path = urllib.parse.unquote(path)
|
|
||||||
|
|
||||||
fr += ':%s' % path
|
fr += ':%s' % path
|
||||||
|
|
||||||
cmd = 'scp -B -r %s %s %s/' % (
|
cmd = 'scp -B -r %s %s %s/' % (
|
||||||
@@ -115,41 +108,3 @@ class SSH(FetchMethod):
|
|||||||
|
|
||||||
runfetchcmd(cmd, d)
|
runfetchcmd(cmd, d)
|
||||||
|
|
||||||
def checkstatus(self, fetch, urldata, d):
|
|
||||||
"""
|
|
||||||
Check the status of the url
|
|
||||||
"""
|
|
||||||
m = __pattern__.match(urldata.url)
|
|
||||||
path = m.group('path')
|
|
||||||
host = m.group('host')
|
|
||||||
port = m.group('port')
|
|
||||||
user = m.group('user')
|
|
||||||
password = m.group('pass')
|
|
||||||
|
|
||||||
if port:
|
|
||||||
portarg = '-P %s' % port
|
|
||||||
else:
|
|
||||||
portarg = ''
|
|
||||||
|
|
||||||
if user:
|
|
||||||
fr = user
|
|
||||||
if password:
|
|
||||||
fr += ':%s' % password
|
|
||||||
fr += '@%s' % host
|
|
||||||
else:
|
|
||||||
fr = host
|
|
||||||
|
|
||||||
if path[0] != '~':
|
|
||||||
path = '/%s' % path
|
|
||||||
path = urllib.parse.unquote(path)
|
|
||||||
|
|
||||||
cmd = 'ssh -o BatchMode=true %s %s [ -f %s ]' % (
|
|
||||||
portarg,
|
|
||||||
fr,
|
|
||||||
path
|
|
||||||
)
|
|
||||||
|
|
||||||
check_network_access(d, cmd, urldata.url)
|
|
||||||
runfetchcmd(cmd, d)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|||||||
@@ -57,12 +57,7 @@ class Svn(FetchMethod):
|
|||||||
if 'rev' in ud.parm:
|
if 'rev' in ud.parm:
|
||||||
ud.revision = ud.parm['rev']
|
ud.revision = ud.parm['rev']
|
||||||
|
|
||||||
# Whether to use the @REV peg-revision syntax in the svn command or not
|
ud.localfile = d.expand('%s_%s_%s_%s_.tar.gz' % (ud.module.replace('/', '.'), ud.host, ud.path.replace('/', '.'), ud.revision))
|
||||||
ud.pegrevision = True
|
|
||||||
if 'nopegrevision' in ud.parm:
|
|
||||||
ud.pegrevision = False
|
|
||||||
|
|
||||||
ud.localfile = d.expand('%s_%s_%s_%s_%s.tar.gz' % (ud.module.replace('/', '.'), ud.host, ud.path.replace('/', '.'), ud.revision, ["0", "1"][ud.pegrevision]))
|
|
||||||
|
|
||||||
def _buildsvncommand(self, ud, d, command):
|
def _buildsvncommand(self, ud, d, command):
|
||||||
"""
|
"""
|
||||||
@@ -91,7 +86,7 @@ class Svn(FetchMethod):
|
|||||||
if command == "info":
|
if command == "info":
|
||||||
svncmd = "%s info %s %s://%s/%s/" % (ud.basecmd, " ".join(options), proto, svnroot, ud.module)
|
svncmd = "%s info %s %s://%s/%s/" % (ud.basecmd, " ".join(options), proto, svnroot, ud.module)
|
||||||
elif command == "log1":
|
elif command == "log1":
|
||||||
svncmd = "%s log --limit 1 --quiet %s %s://%s/%s/" % (ud.basecmd, " ".join(options), proto, svnroot, ud.module)
|
svncmd = "%s log --limit 1 %s %s://%s/%s/" % (ud.basecmd, " ".join(options), proto, svnroot, ud.module)
|
||||||
else:
|
else:
|
||||||
suffix = ""
|
suffix = ""
|
||||||
|
|
||||||
@@ -103,8 +98,7 @@ class Svn(FetchMethod):
|
|||||||
|
|
||||||
if ud.revision:
|
if ud.revision:
|
||||||
options.append("-r %s" % ud.revision)
|
options.append("-r %s" % ud.revision)
|
||||||
if ud.pegrevision:
|
suffix = "@%s" % (ud.revision)
|
||||||
suffix = "@%s" % (ud.revision)
|
|
||||||
|
|
||||||
if command == "fetch":
|
if command == "fetch":
|
||||||
transportuser = ud.parm.get("transportuser", "")
|
transportuser = ud.parm.get("transportuser", "")
|
||||||
@@ -122,7 +116,7 @@ class Svn(FetchMethod):
|
|||||||
def download(self, ud, d):
|
def download(self, ud, d):
|
||||||
"""Fetch url"""
|
"""Fetch url"""
|
||||||
|
|
||||||
logger.debug2("Fetch: checking for module directory '" + ud.moddir + "'")
|
logger.debug(2, "Fetch: checking for module directory '" + ud.moddir + "'")
|
||||||
|
|
||||||
lf = bb.utils.lockfile(ud.svnlock)
|
lf = bb.utils.lockfile(ud.svnlock)
|
||||||
|
|
||||||
@@ -135,7 +129,7 @@ class Svn(FetchMethod):
|
|||||||
runfetchcmd(ud.basecmd + " upgrade", d, workdir=ud.moddir)
|
runfetchcmd(ud.basecmd + " upgrade", d, workdir=ud.moddir)
|
||||||
except FetchError:
|
except FetchError:
|
||||||
pass
|
pass
|
||||||
logger.debug("Running %s", svncmd)
|
logger.debug(1, "Running %s", svncmd)
|
||||||
bb.fetch2.check_network_access(d, svncmd, ud.url)
|
bb.fetch2.check_network_access(d, svncmd, ud.url)
|
||||||
runfetchcmd(svncmd, d, workdir=ud.moddir)
|
runfetchcmd(svncmd, d, workdir=ud.moddir)
|
||||||
else:
|
else:
|
||||||
@@ -143,7 +137,7 @@ class Svn(FetchMethod):
|
|||||||
logger.info("Fetch " + ud.url)
|
logger.info("Fetch " + ud.url)
|
||||||
# check out sources there
|
# check out sources there
|
||||||
bb.utils.mkdirhier(ud.pkgdir)
|
bb.utils.mkdirhier(ud.pkgdir)
|
||||||
logger.debug("Running %s", svncmd)
|
logger.debug(1, "Running %s", svncmd)
|
||||||
bb.fetch2.check_network_access(d, svncmd, ud.url)
|
bb.fetch2.check_network_access(d, svncmd, ud.url)
|
||||||
runfetchcmd(svncmd, d, workdir=ud.pkgdir)
|
runfetchcmd(svncmd, d, workdir=ud.pkgdir)
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from bb.fetch2 import FetchMethod
|
|||||||
from bb.fetch2 import FetchError
|
from bb.fetch2 import FetchError
|
||||||
from bb.fetch2 import logger
|
from bb.fetch2 import logger
|
||||||
from bb.fetch2 import runfetchcmd
|
from bb.fetch2 import runfetchcmd
|
||||||
|
from bb.utils import export_proxies
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from bs4 import SoupStrainer
|
from bs4 import SoupStrainer
|
||||||
|
|
||||||
@@ -52,23 +53,11 @@ class WgetProgressHandler(bb.progress.LineFilterProgressHandler):
|
|||||||
|
|
||||||
class Wget(FetchMethod):
|
class Wget(FetchMethod):
|
||||||
"""Class to fetch urls via 'wget'"""
|
"""Class to fetch urls via 'wget'"""
|
||||||
|
|
||||||
# CDNs like CloudFlare may do a 'browser integrity test' which can fail
|
|
||||||
# with the standard wget/urllib User-Agent, so pretend to be a modern
|
|
||||||
# browser.
|
|
||||||
user_agent = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0"
|
|
||||||
|
|
||||||
def check_certs(self, d):
|
|
||||||
"""
|
|
||||||
Should certificates be checked?
|
|
||||||
"""
|
|
||||||
return (d.getVar("BB_CHECK_SSL_CERTS") or "1") != "0"
|
|
||||||
|
|
||||||
def supports(self, ud, d):
|
def supports(self, ud, d):
|
||||||
"""
|
"""
|
||||||
Check to see if a given url can be fetched with wget.
|
Check to see if a given url can be fetched with wget.
|
||||||
"""
|
"""
|
||||||
return ud.type in ['http', 'https', 'ftp', 'ftps']
|
return ud.type in ['http', 'https', 'ftp']
|
||||||
|
|
||||||
def recommends_checksum(self, urldata):
|
def recommends_checksum(self, urldata):
|
||||||
return True
|
return True
|
||||||
@@ -87,19 +76,13 @@ class Wget(FetchMethod):
|
|||||||
if not ud.localfile:
|
if not ud.localfile:
|
||||||
ud.localfile = d.expand(urllib.parse.unquote(ud.host + ud.path).replace("/", "."))
|
ud.localfile = d.expand(urllib.parse.unquote(ud.host + ud.path).replace("/", "."))
|
||||||
|
|
||||||
self.basecmd = d.getVar("FETCHCMD_wget") or "/usr/bin/env wget -t 2 -T 100"
|
self.basecmd = d.getVar("FETCHCMD_wget") or "/usr/bin/env wget -t 2 -T 30 --passive-ftp --no-check-certificate"
|
||||||
|
|
||||||
if ud.type == 'ftp' or ud.type == 'ftps':
|
|
||||||
self.basecmd += " --passive-ftp"
|
|
||||||
|
|
||||||
if not self.check_certs(d):
|
|
||||||
self.basecmd += " --no-check-certificate"
|
|
||||||
|
|
||||||
def _runwget(self, ud, d, command, quiet, workdir=None):
|
def _runwget(self, ud, d, command, quiet, workdir=None):
|
||||||
|
|
||||||
progresshandler = WgetProgressHandler(d)
|
progresshandler = WgetProgressHandler(d)
|
||||||
|
|
||||||
logger.debug2("Fetching %s using command '%s'" % (ud.url, command))
|
logger.debug(2, "Fetching %s using command '%s'" % (ud.url, command))
|
||||||
bb.fetch2.check_network_access(d, command, ud.url)
|
bb.fetch2.check_network_access(d, command, ud.url)
|
||||||
runfetchcmd(command + ' --progress=dot -v', d, quiet, log=progresshandler, workdir=workdir)
|
runfetchcmd(command + ' --progress=dot -v', d, quiet, log=progresshandler, workdir=workdir)
|
||||||
|
|
||||||
@@ -108,51 +91,32 @@ class Wget(FetchMethod):
|
|||||||
|
|
||||||
fetchcmd = self.basecmd
|
fetchcmd = self.basecmd
|
||||||
|
|
||||||
dldir = os.path.realpath(d.getVar("DL_DIR"))
|
if 'downloadfilename' in ud.parm:
|
||||||
localpath = os.path.join(dldir, ud.localfile) + ".tmp"
|
localpath = os.path.join(d.getVar("DL_DIR"), ud.localfile)
|
||||||
bb.utils.mkdirhier(os.path.dirname(localpath))
|
bb.utils.mkdirhier(os.path.dirname(localpath))
|
||||||
fetchcmd += " -O %s" % shlex.quote(localpath)
|
fetchcmd += " -O %s" % shlex.quote(localpath)
|
||||||
|
|
||||||
if ud.user and ud.pswd:
|
if ud.user and ud.pswd:
|
||||||
fetchcmd += " --auth-no-challenge"
|
fetchcmd += " --user=%s --password=%s --auth-no-challenge" % (ud.user, ud.pswd)
|
||||||
if ud.parm.get("redirectauth", "1") == "1":
|
|
||||||
# An undocumented feature of wget is that if the
|
|
||||||
# username/password are specified on the URI, wget will only
|
|
||||||
# send the Authorization header to the first host and not to
|
|
||||||
# any hosts that it is redirected to. With the increasing
|
|
||||||
# usage of temporary AWS URLs, this difference now matters as
|
|
||||||
# AWS will reject any request that has authentication both in
|
|
||||||
# the query parameters (from the redirect) and in the
|
|
||||||
# Authorization header.
|
|
||||||
fetchcmd += " --user=%s --password=%s" % (ud.user, ud.pswd)
|
|
||||||
|
|
||||||
uri = ud.url.split(";")[0]
|
uri = ud.url.split(";")[0]
|
||||||
if os.path.exists(ud.localpath):
|
if os.path.exists(ud.localpath):
|
||||||
# file exists, but we didnt complete it.. trying again..
|
# file exists, but we didnt complete it.. trying again..
|
||||||
fetchcmd += " -c -P " + dldir + " '" + uri + "'"
|
fetchcmd += d.expand(" -c -P ${DL_DIR} '%s'" % uri)
|
||||||
else:
|
else:
|
||||||
fetchcmd += " -P " + dldir + " '" + uri + "'"
|
fetchcmd += d.expand(" -P ${DL_DIR} '%s'" % uri)
|
||||||
|
|
||||||
self._runwget(ud, d, fetchcmd, False)
|
self._runwget(ud, d, fetchcmd, False)
|
||||||
|
|
||||||
# Sanity check since wget can pretend it succeed when it didn't
|
# Sanity check since wget can pretend it succeed when it didn't
|
||||||
# Also, this used to happen if sourceforge sent us to the mirror page
|
# Also, this used to happen if sourceforge sent us to the mirror page
|
||||||
if not os.path.exists(localpath):
|
if not os.path.exists(ud.localpath):
|
||||||
raise FetchError("The fetch command returned success for url %s but %s doesn't exist?!" % (uri, localpath), uri)
|
raise FetchError("The fetch command returned success for url %s but %s doesn't exist?!" % (uri, ud.localpath), uri)
|
||||||
|
|
||||||
if os.path.getsize(localpath) == 0:
|
if os.path.getsize(ud.localpath) == 0:
|
||||||
os.remove(localpath)
|
os.remove(ud.localpath)
|
||||||
raise FetchError("The fetch of %s resulted in a zero size file?! Deleting and failing since this isn't right." % (uri), uri)
|
raise FetchError("The fetch of %s resulted in a zero size file?! Deleting and failing since this isn't right." % (uri), uri)
|
||||||
|
|
||||||
# Try and verify any checksum now, meaning if it isn't correct, we don't remove the
|
|
||||||
# original file, which might be a race (imagine two recipes referencing the same
|
|
||||||
# source, one with an incorrect checksum)
|
|
||||||
bb.fetch2.verify_checksum(ud, d, localpath=localpath, fatal_nochecksum=False)
|
|
||||||
|
|
||||||
# Remove the ".tmp" and move the file into position atomically
|
|
||||||
# Our lock prevents multiple writers but mirroring code may grab incomplete files
|
|
||||||
os.rename(localpath, localpath[:-4])
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def checkstatus(self, fetch, ud, d, try_again=True):
|
def checkstatus(self, fetch, ud, d, try_again=True):
|
||||||
@@ -239,7 +203,7 @@ class Wget(FetchMethod):
|
|||||||
# We let the request fail and expect it to be
|
# We let the request fail and expect it to be
|
||||||
# tried once more ("try_again" in check_status()),
|
# tried once more ("try_again" in check_status()),
|
||||||
# with the dead connection removed from the cache.
|
# with the dead connection removed from the cache.
|
||||||
# If it still fails, we give up, which can happen for bad
|
# If it still fails, we give up, which can happend for bad
|
||||||
# HTTP proxy settings.
|
# HTTP proxy settings.
|
||||||
fetch.connection_cache.remove_connection(h.host, h.port)
|
fetch.connection_cache.remove_connection(h.host, h.port)
|
||||||
raise urllib.error.URLError(err)
|
raise urllib.error.URLError(err)
|
||||||
@@ -312,79 +276,56 @@ class Wget(FetchMethod):
|
|||||||
newreq = urllib.request.HTTPRedirectHandler.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 = req.get_method
|
newreq.get_method = req.get_method
|
||||||
return newreq
|
return newreq
|
||||||
|
exported_proxies = export_proxies(d)
|
||||||
|
|
||||||
# We need to update the environment here as both the proxy and HTTPS
|
handlers = [FixedHTTPRedirectHandler, HTTPMethodFallback]
|
||||||
# handlers need variables set. The proxy needs http_proxy and friends to
|
if exported_proxies:
|
||||||
# be set, and HTTPSHandler ends up calling into openssl to load the
|
handlers.append(urllib.request.ProxyHandler())
|
||||||
# certificates. In buildtools configurations this will be looking at the
|
handlers.append(CacheHTTPHandler())
|
||||||
# wrong place for certificates by default: we set SSL_CERT_FILE to the
|
# Since Python 2.7.9 ssl cert validation is enabled by default
|
||||||
# right location in the buildtools environment script but as BitBake
|
# see PEP-0476, this causes verification errors on some https servers
|
||||||
# prunes prunes the environment this is lost. When binaries are executed
|
# so disable by default.
|
||||||
# runfetchcmd ensures these values are in the environment, but this is
|
import ssl
|
||||||
# pure Python so we need to update the environment.
|
if hasattr(ssl, '_create_unverified_context'):
|
||||||
#
|
handlers.append(urllib.request.HTTPSHandler(context=ssl._create_unverified_context()))
|
||||||
# Avoid tramping the environment too much by using bb.utils.environment
|
opener = urllib.request.build_opener(*handlers)
|
||||||
# to scope the changes to the build_opener request, which is when the
|
|
||||||
# environment lookups happen.
|
|
||||||
newenv = bb.fetch2.get_fetcher_environment(d)
|
|
||||||
|
|
||||||
with bb.utils.environment(**newenv):
|
try:
|
||||||
import ssl
|
uri = ud.url.split(";")[0]
|
||||||
|
r = urllib.request.Request(uri)
|
||||||
|
r.get_method = lambda: "HEAD"
|
||||||
|
# Some servers (FusionForge, as used on Alioth) require that the
|
||||||
|
# optional Accept header is set.
|
||||||
|
r.add_header("Accept", "*/*")
|
||||||
|
r.add_header("User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.12) Gecko/20101027 Ubuntu/9.10 (karmic) Firefox/3.6.12")
|
||||||
|
def add_basic_auth(login_str, request):
|
||||||
|
'''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
|
||||||
|
r.add_header("Authorization", authheader)
|
||||||
|
|
||||||
if self.check_certs(d):
|
if ud.user and ud.pswd:
|
||||||
context = ssl.create_default_context()
|
add_basic_auth(ud.user + ':' + ud.pswd, r)
|
||||||
else:
|
|
||||||
context = ssl._create_unverified_context()
|
|
||||||
|
|
||||||
handlers = [FixedHTTPRedirectHandler,
|
|
||||||
HTTPMethodFallback,
|
|
||||||
urllib.request.ProxyHandler(),
|
|
||||||
CacheHTTPHandler(),
|
|
||||||
urllib.request.HTTPSHandler(context=context)]
|
|
||||||
opener = urllib.request.build_opener(*handlers)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parts = urllib.parse.urlparse(ud.url.split(";")[0])
|
import netrc
|
||||||
if parts.query:
|
n = netrc.netrc()
|
||||||
uri = "{}://{}{}?{}".format(parts.scheme, parts.netloc, parts.path, parts.query)
|
login, unused, password = n.authenticators(urllib.parse.urlparse(uri).hostname)
|
||||||
else:
|
add_basic_auth("%s:%s" % (login, password), r)
|
||||||
uri = "{}://{}{}".format(parts.scheme, parts.netloc, parts.path)
|
except (TypeError, ImportError, IOError, netrc.NetrcParseError):
|
||||||
r = urllib.request.Request(uri)
|
pass
|
||||||
r.get_method = lambda: "HEAD"
|
|
||||||
# Some servers (FusionForge, as used on Alioth) require that the
|
|
||||||
# optional Accept header is set.
|
|
||||||
r.add_header("Accept", "*/*")
|
|
||||||
r.add_header("User-Agent", self.user_agent)
|
|
||||||
def add_basic_auth(login_str, request):
|
|
||||||
'''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
|
|
||||||
r.add_header("Authorization", authheader)
|
|
||||||
|
|
||||||
if ud.user and ud.pswd:
|
|
||||||
add_basic_auth(ud.user + ':' + ud.pswd, r)
|
|
||||||
|
|
||||||
try:
|
|
||||||
import netrc
|
|
||||||
auth_data = netrc.netrc().authenticators(urllib.parse.urlparse(uri).hostname)
|
|
||||||
if auth_data:
|
|
||||||
login, _, password = auth_data
|
|
||||||
add_basic_auth("%s:%s" % (login, password), r)
|
|
||||||
except (FileNotFoundError, netrc.NetrcParseError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
with opener.open(r, timeout=100) as response:
|
|
||||||
pass
|
|
||||||
except (urllib.error.URLError, ConnectionResetError, TimeoutError) as e:
|
|
||||||
if try_again:
|
|
||||||
logger.debug2("checkstatus: trying again")
|
|
||||||
return self.checkstatus(fetch, ud, d, False)
|
|
||||||
else:
|
|
||||||
# debug for now to avoid spamming the logs in e.g. remote sstate searches
|
|
||||||
logger.debug2("checkstatus() urlopen failed for %s: %s" % (uri,e))
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
with opener.open(r) as response:
|
||||||
|
pass
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
if try_again:
|
||||||
|
logger.debug(2, "checkstatus: trying again")
|
||||||
|
return self.checkstatus(fetch, ud, d, False)
|
||||||
|
else:
|
||||||
|
# debug for now to avoid spamming the logs in e.g. remote sstate searches
|
||||||
|
logger.debug(2, "checkstatus() urlopen failed: %s" % e)
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _parse_path(self, regex, s):
|
def _parse_path(self, regex, s):
|
||||||
@@ -460,8 +401,9 @@ class Wget(FetchMethod):
|
|||||||
"""
|
"""
|
||||||
f = tempfile.NamedTemporaryFile()
|
f = tempfile.NamedTemporaryFile()
|
||||||
with tempfile.TemporaryDirectory(prefix="wget-index-") as workdir, tempfile.NamedTemporaryFile(dir=workdir, prefix="wget-listing-") as f:
|
with tempfile.TemporaryDirectory(prefix="wget-index-") as workdir, tempfile.NamedTemporaryFile(dir=workdir, prefix="wget-listing-") as f:
|
||||||
|
agent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.12) Gecko/20101027 Ubuntu/9.10 (karmic) Firefox/3.6.12"
|
||||||
fetchcmd = self.basecmd
|
fetchcmd = self.basecmd
|
||||||
fetchcmd += " -O " + f.name + " --user-agent='" + self.user_agent + "' '" + uri + "'"
|
fetchcmd += " -O " + f.name + " --user-agent='" + agent + "' '" + uri + "'"
|
||||||
try:
|
try:
|
||||||
self._runwget(ud, d, fetchcmd, True, workdir=workdir)
|
self._runwget(ud, d, fetchcmd, True, workdir=workdir)
|
||||||
fetchresult = f.read()
|
fetchresult = f.read()
|
||||||
@@ -517,7 +459,7 @@ class Wget(FetchMethod):
|
|||||||
version_dir = ['', '', '']
|
version_dir = ['', '', '']
|
||||||
version = ['', '', '']
|
version = ['', '', '']
|
||||||
|
|
||||||
dirver_regex = re.compile(r"(?P<pfx>\D*)(?P<ver>(\d+[\.\-_])*(\d+))")
|
dirver_regex = re.compile(r"(?P<pfx>\D*)(?P<ver>(\d+[\.\-_])+(\d+))")
|
||||||
s = dirver_regex.search(dirver)
|
s = dirver_regex.search(dirver)
|
||||||
if s:
|
if s:
|
||||||
version_dir[1] = s.group('ver')
|
version_dir[1] = s.group('ver')
|
||||||
@@ -593,7 +535,7 @@ class Wget(FetchMethod):
|
|||||||
|
|
||||||
# src.rpm extension was added only for rpm package. Can be removed if the rpm
|
# 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
|
# packaged will always be considered as having to be manually upgraded
|
||||||
psuffix_regex = r"(tar\.\w+|tgz|zip|xz|rpm|bz2|orig\.tar\.\w+|src\.tar\.\w+|src\.tgz|svnr\d+\.tar\.\w+|stable\.tar\.\w+|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
|
# match name, version and archive type of a package
|
||||||
package_regex_comp = re.compile(r"(?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$)"
|
||||||
@@ -644,10 +586,10 @@ class Wget(FetchMethod):
|
|||||||
# search for version matches on folders inside the path, like:
|
# 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
|
# "5.7" in http://download.gnome.org/sources/${PN}/5.7/${PN}-${PV}.tar.gz
|
||||||
dirver_regex = re.compile(r"(?P<dirver>[^/]*(\d+\.)*\d+([-_]r\d+)*)/")
|
dirver_regex = re.compile(r"(?P<dirver>[^/]*(\d+\.)*\d+([-_]r\d+)*)/")
|
||||||
m = dirver_regex.findall(path)
|
m = dirver_regex.search(path)
|
||||||
if m:
|
if m:
|
||||||
pn = d.getVar('PN')
|
pn = d.getVar('PN')
|
||||||
dirver = m[-1][0]
|
dirver = m.group('dirver')
|
||||||
|
|
||||||
dirver_pn_regex = re.compile(r"%s\d?" % (re.escape(pn)))
|
dirver_pn_regex = re.compile(r"%s\d?" % (re.escape(pn)))
|
||||||
if not dirver_pn_regex.search(dirver):
|
if not dirver_pn_regex.search(dirver):
|
||||||
|
|||||||
@@ -12,12 +12,11 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import argparse
|
import optparse
|
||||||
import warnings
|
import warnings
|
||||||
import fcntl
|
import fcntl
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import datetime
|
|
||||||
|
|
||||||
import bb
|
import bb
|
||||||
from bb import event
|
from bb import event
|
||||||
@@ -44,18 +43,18 @@ def present_options(optionlist):
|
|||||||
else:
|
else:
|
||||||
return optionlist[0]
|
return optionlist[0]
|
||||||
|
|
||||||
class BitbakeHelpFormatter(argparse.HelpFormatter):
|
class BitbakeHelpFormatter(optparse.IndentedHelpFormatter):
|
||||||
def _get_help_string(self, action):
|
def format_option(self, option):
|
||||||
# We need to do this here rather than in the text we supply to
|
# We need to do this here rather than in the text we supply to
|
||||||
# add_option() because we don't want to call list_extension_modules()
|
# add_option() because we don't want to call list_extension_modules()
|
||||||
# on every execution (since it imports all of the modules)
|
# on every execution (since it imports all of the modules)
|
||||||
# Note also that we modify option.help rather than the returned text
|
# Note also that we modify option.help rather than the returned text
|
||||||
# - this is so that we don't have to re-format the text ourselves
|
# - this is so that we don't have to re-format the text ourselves
|
||||||
if action.dest == 'ui':
|
if option.dest == 'ui':
|
||||||
valid_uis = list_extension_modules(bb.ui, 'main')
|
valid_uis = list_extension_modules(bb.ui, 'main')
|
||||||
return action.help.replace('@CHOICES@', present_options(valid_uis))
|
option.help = option.help.replace('@CHOICES@', present_options(valid_uis))
|
||||||
|
|
||||||
return action.help
|
return optparse.IndentedHelpFormatter.format_option(self, option)
|
||||||
|
|
||||||
def list_extension_modules(pkg, checkattr):
|
def list_extension_modules(pkg, checkattr):
|
||||||
"""
|
"""
|
||||||
@@ -113,209 +112,186 @@ def _showwarning(message, category, filename, lineno, file=None, line=None):
|
|||||||
warnlog.warning(s)
|
warnlog.warning(s)
|
||||||
|
|
||||||
warnings.showwarning = _showwarning
|
warnings.showwarning = _showwarning
|
||||||
|
warnings.filterwarnings("ignore")
|
||||||
def create_bitbake_parser():
|
warnings.filterwarnings("default", module="(<string>$|(oe|bb)\.)")
|
||||||
parser = argparse.ArgumentParser(
|
warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
|
||||||
description="""\
|
warnings.filterwarnings("ignore", category=ImportWarning)
|
||||||
It is assumed there is a conf/bblayers.conf available in cwd or in BBPATH which
|
warnings.filterwarnings("ignore", category=DeprecationWarning, module="<string>$")
|
||||||
will provide the layer, BBFILES and other configuration information.
|
warnings.filterwarnings("ignore", message="With-statements now directly support multiple context managers")
|
||||||
""",
|
|
||||||
formatter_class=BitbakeHelpFormatter,
|
|
||||||
allow_abbrev=False,
|
|
||||||
add_help=False, # help is manually added below in a specific argument group
|
|
||||||
)
|
|
||||||
|
|
||||||
general_group = parser.add_argument_group('General options')
|
|
||||||
task_group = parser.add_argument_group('Task control options')
|
|
||||||
exec_group = parser.add_argument_group('Execution control options')
|
|
||||||
logging_group = parser.add_argument_group('Logging/output control options')
|
|
||||||
server_group = parser.add_argument_group('Server options')
|
|
||||||
config_group = parser.add_argument_group('Configuration options')
|
|
||||||
|
|
||||||
general_group.add_argument("targets", nargs="*", metavar="recipename/target",
|
|
||||||
help="Execute the specified task (default is 'build') for these target "
|
|
||||||
"recipes (.bb files).")
|
|
||||||
|
|
||||||
general_group.add_argument("-s", "--show-versions", action="store_true",
|
|
||||||
help="Show current and preferred versions of all recipes.")
|
|
||||||
|
|
||||||
general_group.add_argument("-e", "--environment", action="store_true",
|
|
||||||
dest="show_environment",
|
|
||||||
help="Show the global or per-recipe environment complete with information"
|
|
||||||
" about where variables were set/changed.")
|
|
||||||
|
|
||||||
general_group.add_argument("-g", "--graphviz", action="store_true", dest="dot_graph",
|
|
||||||
help="Save dependency tree information for the specified "
|
|
||||||
"targets in the dot syntax.")
|
|
||||||
|
|
||||||
# @CHOICES@ is substituted out by BitbakeHelpFormatter above
|
|
||||||
general_group.add_argument("-u", "--ui",
|
|
||||||
default=os.environ.get('BITBAKE_UI', 'knotty'),
|
|
||||||
help="The user interface to use (@CHOICES@ - default %(default)s).")
|
|
||||||
|
|
||||||
general_group.add_argument("--version", action="store_true",
|
|
||||||
help="Show programs version and exit.")
|
|
||||||
|
|
||||||
general_group.add_argument('-h', '--help', action='help',
|
|
||||||
help='Show this help message and exit.')
|
|
||||||
|
|
||||||
|
|
||||||
task_group.add_argument("-f", "--force", action="store_true",
|
|
||||||
help="Force the specified targets/task to run (invalidating any "
|
|
||||||
"existing stamp file).")
|
|
||||||
|
|
||||||
task_group.add_argument("-c", "--cmd",
|
|
||||||
help="Specify the task to execute. The exact options available "
|
|
||||||
"depend on the metadata. Some examples might be 'compile'"
|
|
||||||
" or 'populate_sysroot' or 'listtasks' may give a list of "
|
|
||||||
"the tasks available.")
|
|
||||||
|
|
||||||
task_group.add_argument("-C", "--clear-stamp", dest="invalidate_stamp",
|
|
||||||
help="Invalidate the stamp for the specified task such as 'compile' "
|
|
||||||
"and then run the default task for the specified target(s).")
|
|
||||||
|
|
||||||
task_group.add_argument("--runall", action="append", default=[],
|
|
||||||
help="Run the specified task for any recipe in the taskgraph of the "
|
|
||||||
"specified target (even if it wouldn't otherwise have run).")
|
|
||||||
|
|
||||||
task_group.add_argument("--runonly", action="append",
|
|
||||||
help="Run only the specified task within the taskgraph of the "
|
|
||||||
"specified targets (and any task dependencies those tasks may have).")
|
|
||||||
|
|
||||||
task_group.add_argument("--no-setscene", action="store_true",
|
|
||||||
dest="nosetscene",
|
|
||||||
help="Do not run any setscene tasks. sstate will be ignored and "
|
|
||||||
"everything needed, built.")
|
|
||||||
|
|
||||||
task_group.add_argument("--skip-setscene", action="store_true",
|
|
||||||
dest="skipsetscene",
|
|
||||||
help="Skip setscene tasks if they would be executed. Tasks previously "
|
|
||||||
"restored from sstate will be kept, unlike --no-setscene.")
|
|
||||||
|
|
||||||
task_group.add_argument("--setscene-only", action="store_true",
|
|
||||||
dest="setsceneonly",
|
|
||||||
help="Only run setscene tasks, don't run any real tasks.")
|
|
||||||
|
|
||||||
|
|
||||||
exec_group.add_argument("-n", "--dry-run", action="store_true",
|
|
||||||
help="Don't execute, just go through the motions.")
|
|
||||||
|
|
||||||
exec_group.add_argument("-p", "--parse-only", action="store_true",
|
|
||||||
help="Quit after parsing the BB recipes.")
|
|
||||||
|
|
||||||
exec_group.add_argument("-k", "--continue", action="store_false", dest="halt",
|
|
||||||
help="Continue as much as possible after an error. While the target that "
|
|
||||||
"failed and anything depending on it cannot be built, as much as "
|
|
||||||
"possible will be built before stopping.")
|
|
||||||
|
|
||||||
exec_group.add_argument("-P", "--profile", action="store_true",
|
|
||||||
help="Profile the command and save reports.")
|
|
||||||
|
|
||||||
exec_group.add_argument("-S", "--dump-signatures", action="append",
|
|
||||||
default=[], metavar="SIGNATURE_HANDLER",
|
|
||||||
help="Dump out the signature construction information, with no task "
|
|
||||||
"execution. The SIGNATURE_HANDLER parameter is passed to the "
|
|
||||||
"handler. Two common values are none and printdiff but the handler "
|
|
||||||
"may define more/less. none means only dump the signature, printdiff"
|
|
||||||
" means recursively compare the dumped signature with the most recent"
|
|
||||||
" one in a local build or sstate cache (can be used to find out why tasks re-run"
|
|
||||||
" when that is not expected)")
|
|
||||||
|
|
||||||
exec_group.add_argument("--revisions-changed", action="store_true",
|
|
||||||
help="Set the exit code depending on whether upstream floating "
|
|
||||||
"revisions have changed or not.")
|
|
||||||
|
|
||||||
exec_group.add_argument("-b", "--buildfile",
|
|
||||||
help="Execute tasks from a specific .bb recipe directly. WARNING: Does "
|
|
||||||
"not handle any dependencies from other recipes.")
|
|
||||||
|
|
||||||
logging_group.add_argument("-D", "--debug", action="count", default=0,
|
|
||||||
help="Increase the debug level. You can specify this "
|
|
||||||
"more than once. -D sets the debug level to 1, "
|
|
||||||
"where only bb.debug(1, ...) messages are printed "
|
|
||||||
"to stdout; -DD sets the debug level to 2, where "
|
|
||||||
"both bb.debug(1, ...) and bb.debug(2, ...) "
|
|
||||||
"messages are printed; etc. Without -D, no debug "
|
|
||||||
"messages are printed. Note that -D only affects "
|
|
||||||
"output to stdout. All debug messages are written "
|
|
||||||
"to ${T}/log.do_taskname, regardless of the debug "
|
|
||||||
"level.")
|
|
||||||
|
|
||||||
logging_group.add_argument("-l", "--log-domains", action="append", dest="debug_domains",
|
|
||||||
default=[],
|
|
||||||
help="Show debug logging for the specified logging domains.")
|
|
||||||
|
|
||||||
logging_group.add_argument("-v", "--verbose", action="store_true",
|
|
||||||
help="Enable tracing of shell tasks (with 'set -x'). "
|
|
||||||
"Also print bb.note(...) messages to stdout (in "
|
|
||||||
"addition to writing them to ${T}/log.do_<task>).")
|
|
||||||
|
|
||||||
logging_group.add_argument("-q", "--quiet", action="count", default=0,
|
|
||||||
help="Output less log message data to the terminal. You can specify this "
|
|
||||||
"more than once.")
|
|
||||||
|
|
||||||
logging_group.add_argument("-w", "--write-log", dest="writeeventlog",
|
|
||||||
default=os.environ.get("BBEVENTLOG"),
|
|
||||||
help="Writes the event log of the build to a bitbake event json file. "
|
|
||||||
"Use '' (empty string) to assign the name automatically.")
|
|
||||||
|
|
||||||
|
|
||||||
server_group.add_argument("-B", "--bind", default=False,
|
|
||||||
help="The name/address for the bitbake xmlrpc server to bind to.")
|
|
||||||
|
|
||||||
server_group.add_argument("-T", "--idle-timeout", type=float, dest="server_timeout",
|
|
||||||
default=os.getenv("BB_SERVER_TIMEOUT"),
|
|
||||||
help="Set timeout to unload bitbake server due to inactivity, "
|
|
||||||
"set to -1 means no unload, "
|
|
||||||
"default: Environment variable BB_SERVER_TIMEOUT.")
|
|
||||||
|
|
||||||
server_group.add_argument("--remote-server",
|
|
||||||
default=os.environ.get("BBSERVER"),
|
|
||||||
help="Connect to the specified server.")
|
|
||||||
|
|
||||||
server_group.add_argument("-m", "--kill-server", action="store_true",
|
|
||||||
help="Terminate any running bitbake server.")
|
|
||||||
|
|
||||||
server_group.add_argument("--token", dest="xmlrpctoken",
|
|
||||||
default=os.environ.get("BBTOKEN"),
|
|
||||||
help="Specify the connection token to be used when connecting "
|
|
||||||
"to a remote server.")
|
|
||||||
|
|
||||||
server_group.add_argument("--observe-only", action="store_true",
|
|
||||||
help="Connect to a server as an observing-only client.")
|
|
||||||
|
|
||||||
server_group.add_argument("--status-only", action="store_true",
|
|
||||||
help="Check the status of the remote bitbake server.")
|
|
||||||
|
|
||||||
server_group.add_argument("--server-only", action="store_true",
|
|
||||||
help="Run bitbake without a UI, only starting a server "
|
|
||||||
"(cooker) process.")
|
|
||||||
|
|
||||||
|
|
||||||
config_group.add_argument("-r", "--read", action="append", dest="prefile", default=[],
|
|
||||||
help="Read the specified file before bitbake.conf.")
|
|
||||||
|
|
||||||
config_group.add_argument("-R", "--postread", action="append", dest="postfile", default=[],
|
|
||||||
help="Read the specified file after bitbake.conf.")
|
|
||||||
|
|
||||||
|
|
||||||
config_group.add_argument("-I", "--ignore-deps", action="append",
|
|
||||||
dest="extra_assume_provided", default=[],
|
|
||||||
help="Assume these dependencies don't exist and are already provided "
|
|
||||||
"(equivalent to ASSUME_PROVIDED). Useful to make dependency "
|
|
||||||
"graphs more appealing.")
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
|
|
||||||
class BitBakeConfigParameters(cookerdata.ConfigParameters):
|
class BitBakeConfigParameters(cookerdata.ConfigParameters):
|
||||||
def parseCommandLine(self, argv=sys.argv):
|
|
||||||
parser = create_bitbake_parser()
|
|
||||||
options = parser.parse_intermixed_args(argv[1:])
|
|
||||||
|
|
||||||
if options.version:
|
def parseCommandLine(self, argv=sys.argv):
|
||||||
print("BitBake Build Tool Core version %s" % bb.__version__)
|
parser = optparse.OptionParser(
|
||||||
sys.exit(0)
|
formatter=BitbakeHelpFormatter(),
|
||||||
|
version="BitBake Build Tool Core version %s" % bb.__version__,
|
||||||
|
usage="""%prog [options] [recipename/target recipe:do_task ...]
|
||||||
|
|
||||||
|
Executes the specified task (default is 'build') for a given set of target recipes (.bb files).
|
||||||
|
It is assumed there is a conf/bblayers.conf available in cwd or in BBPATH which
|
||||||
|
will provide the layer, BBFILES and other configuration information.""")
|
||||||
|
|
||||||
|
parser.add_option("-b", "--buildfile", action="store", dest="buildfile", default=None,
|
||||||
|
help="Execute tasks from a specific .bb recipe directly. WARNING: Does "
|
||||||
|
"not handle any dependencies from other recipes.")
|
||||||
|
|
||||||
|
parser.add_option("-k", "--continue", action="store_false", dest="abort", default=True,
|
||||||
|
help="Continue as much as possible after an error. While the target that "
|
||||||
|
"failed and anything depending on it cannot be built, as much as "
|
||||||
|
"possible will be built before stopping.")
|
||||||
|
|
||||||
|
parser.add_option("-f", "--force", action="store_true", dest="force", default=False,
|
||||||
|
help="Force the specified targets/task to run (invalidating any "
|
||||||
|
"existing stamp file).")
|
||||||
|
|
||||||
|
parser.add_option("-c", "--cmd", action="store", dest="cmd",
|
||||||
|
help="Specify the task to execute. The exact options available "
|
||||||
|
"depend on the metadata. Some examples might be 'compile'"
|
||||||
|
" or 'populate_sysroot' or 'listtasks' may give a list of "
|
||||||
|
"the tasks available.")
|
||||||
|
|
||||||
|
parser.add_option("-C", "--clear-stamp", action="store", dest="invalidate_stamp",
|
||||||
|
help="Invalidate the stamp for the specified task such as 'compile' "
|
||||||
|
"and then run the default task for the specified target(s).")
|
||||||
|
|
||||||
|
parser.add_option("-r", "--read", action="append", dest="prefile", default=[],
|
||||||
|
help="Read the specified file before bitbake.conf.")
|
||||||
|
|
||||||
|
parser.add_option("-R", "--postread", action="append", dest="postfile", default=[],
|
||||||
|
help="Read the specified file after bitbake.conf.")
|
||||||
|
|
||||||
|
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
|
||||||
|
help="Enable tracing of shell tasks (with 'set -x'). "
|
||||||
|
"Also print bb.note(...) messages to stdout (in "
|
||||||
|
"addition to writing them to ${T}/log.do_<task>).")
|
||||||
|
|
||||||
|
parser.add_option("-D", "--debug", action="count", dest="debug", default=0,
|
||||||
|
help="Increase the debug level. You can specify this "
|
||||||
|
"more than once. -D sets the debug level to 1, "
|
||||||
|
"where only bb.debug(1, ...) messages are printed "
|
||||||
|
"to stdout; -DD sets the debug level to 2, where "
|
||||||
|
"both bb.debug(1, ...) and bb.debug(2, ...) "
|
||||||
|
"messages are printed; etc. Without -D, no debug "
|
||||||
|
"messages are printed. Note that -D only affects "
|
||||||
|
"output to stdout. All debug messages are written "
|
||||||
|
"to ${T}/log.do_taskname, regardless of the debug "
|
||||||
|
"level.")
|
||||||
|
|
||||||
|
parser.add_option("-q", "--quiet", action="count", dest="quiet", default=0,
|
||||||
|
help="Output less log message data to the terminal. You can specify this more than once.")
|
||||||
|
|
||||||
|
parser.add_option("-n", "--dry-run", action="store_true", dest="dry_run", default=False,
|
||||||
|
help="Don't execute, just go through the motions.")
|
||||||
|
|
||||||
|
parser.add_option("-S", "--dump-signatures", action="append", dest="dump_signatures",
|
||||||
|
default=[], metavar="SIGNATURE_HANDLER",
|
||||||
|
help="Dump out the signature construction information, with no task "
|
||||||
|
"execution. The SIGNATURE_HANDLER parameter is passed to the "
|
||||||
|
"handler. Two common values are none and printdiff but the handler "
|
||||||
|
"may define more/less. none means only dump the signature, printdiff"
|
||||||
|
" means compare the dumped signature with the cached one.")
|
||||||
|
|
||||||
|
parser.add_option("-p", "--parse-only", action="store_true",
|
||||||
|
dest="parse_only", default=False,
|
||||||
|
help="Quit after parsing the BB recipes.")
|
||||||
|
|
||||||
|
parser.add_option("-s", "--show-versions", action="store_true",
|
||||||
|
dest="show_versions", default=False,
|
||||||
|
help="Show current and preferred versions of all recipes.")
|
||||||
|
|
||||||
|
parser.add_option("-e", "--environment", action="store_true",
|
||||||
|
dest="show_environment", default=False,
|
||||||
|
help="Show the global or per-recipe environment complete with information"
|
||||||
|
" about where variables were set/changed.")
|
||||||
|
|
||||||
|
parser.add_option("-g", "--graphviz", action="store_true", dest="dot_graph", default=False,
|
||||||
|
help="Save dependency tree information for the specified "
|
||||||
|
"targets in the dot syntax.")
|
||||||
|
|
||||||
|
parser.add_option("-I", "--ignore-deps", action="append",
|
||||||
|
dest="extra_assume_provided", default=[],
|
||||||
|
help="Assume these dependencies don't exist and are already provided "
|
||||||
|
"(equivalent to ASSUME_PROVIDED). Useful to make dependency "
|
||||||
|
"graphs more appealing")
|
||||||
|
|
||||||
|
parser.add_option("-l", "--log-domains", action="append", dest="debug_domains", default=[],
|
||||||
|
help="Show debug logging for the specified logging domains")
|
||||||
|
|
||||||
|
parser.add_option("-P", "--profile", action="store_true", dest="profile", default=False,
|
||||||
|
help="Profile the command and save reports.")
|
||||||
|
|
||||||
|
# @CHOICES@ is substituted out by BitbakeHelpFormatter above
|
||||||
|
parser.add_option("-u", "--ui", action="store", dest="ui",
|
||||||
|
default=os.environ.get('BITBAKE_UI', 'knotty'),
|
||||||
|
help="The user interface to use (@CHOICES@ - default %default).")
|
||||||
|
|
||||||
|
parser.add_option("", "--token", action="store", dest="xmlrpctoken",
|
||||||
|
default=os.environ.get("BBTOKEN"),
|
||||||
|
help="Specify the connection token to be used when connecting "
|
||||||
|
"to a remote server.")
|
||||||
|
|
||||||
|
parser.add_option("", "--revisions-changed", action="store_true",
|
||||||
|
dest="revisions_changed", default=False,
|
||||||
|
help="Set the exit code depending on whether upstream floating "
|
||||||
|
"revisions have changed or not.")
|
||||||
|
|
||||||
|
parser.add_option("", "--server-only", action="store_true",
|
||||||
|
dest="server_only", default=False,
|
||||||
|
help="Run bitbake without a UI, only starting a server "
|
||||||
|
"(cooker) process.")
|
||||||
|
|
||||||
|
parser.add_option("-B", "--bind", action="store", dest="bind", default=False,
|
||||||
|
help="The name/address for the bitbake xmlrpc server to bind to.")
|
||||||
|
|
||||||
|
parser.add_option("-T", "--idle-timeout", type=float, dest="server_timeout",
|
||||||
|
default=os.getenv("BB_SERVER_TIMEOUT"),
|
||||||
|
help="Set timeout to unload bitbake server due to inactivity, "
|
||||||
|
"set to -1 means no unload, "
|
||||||
|
"default: Environment variable BB_SERVER_TIMEOUT.")
|
||||||
|
|
||||||
|
parser.add_option("", "--no-setscene", action="store_true",
|
||||||
|
dest="nosetscene", default=False,
|
||||||
|
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.")
|
||||||
|
|
||||||
|
parser.add_option("", "--remote-server", action="store", dest="remote_server",
|
||||||
|
default=os.environ.get("BBSERVER"),
|
||||||
|
help="Connect to the specified server.")
|
||||||
|
|
||||||
|
parser.add_option("-m", "--kill-server", action="store_true",
|
||||||
|
dest="kill_server", default=False,
|
||||||
|
help="Terminate any running bitbake server.")
|
||||||
|
|
||||||
|
parser.add_option("", "--observe-only", action="store_true",
|
||||||
|
dest="observe_only", default=False,
|
||||||
|
help="Connect to a server as an observing-only client.")
|
||||||
|
|
||||||
|
parser.add_option("", "--status-only", action="store_true",
|
||||||
|
dest="status_only", default=False,
|
||||||
|
help="Check the status of the remote bitbake server.")
|
||||||
|
|
||||||
|
parser.add_option("-w", "--write-log", action="store", dest="writeeventlog",
|
||||||
|
default=os.environ.get("BBEVENTLOG"),
|
||||||
|
help="Writes the event log of the build to a bitbake event json file. "
|
||||||
|
"Use '' (empty string) to assign the name automatically.")
|
||||||
|
|
||||||
|
parser.add_option("", "--runall", action="append", dest="runall",
|
||||||
|
help="Run the specified task for any recipe in the taskgraph of the specified target (even if it wouldn't otherwise have run).")
|
||||||
|
|
||||||
|
parser.add_option("", "--runonly", action="append", dest="runonly",
|
||||||
|
help="Run only the specified task within the taskgraph of the specified targets (and any task dependencies those tasks may have).")
|
||||||
|
|
||||||
|
|
||||||
|
options, targets = parser.parse_args(argv)
|
||||||
|
|
||||||
if options.quiet and options.verbose:
|
if options.quiet and options.verbose:
|
||||||
parser.error("options --quiet and --verbose are mutually exclusive")
|
parser.error("options --quiet and --verbose are mutually exclusive")
|
||||||
@@ -347,7 +323,7 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters):
|
|||||||
else:
|
else:
|
||||||
options.xmlrpcinterface = (None, 0)
|
options.xmlrpcinterface = (None, 0)
|
||||||
|
|
||||||
return options, options.targets
|
return options, targets[1:]
|
||||||
|
|
||||||
|
|
||||||
def bitbake_main(configParams, configuration):
|
def bitbake_main(configParams, configuration):
|
||||||
@@ -412,9 +388,6 @@ def bitbake_main(configParams, configuration):
|
|||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def timestamp():
|
|
||||||
return datetime.datetime.now().strftime('%H:%M:%S.%f')
|
|
||||||
|
|
||||||
def setup_bitbake(configParams, extrafeatures=None):
|
def setup_bitbake(configParams, extrafeatures=None):
|
||||||
# Ensure logging messages get sent to the UI as events
|
# Ensure logging messages get sent to the UI as events
|
||||||
handler = bb.event.LogHandler()
|
handler = bb.event.LogHandler()
|
||||||
@@ -422,11 +395,6 @@ def setup_bitbake(configParams, extrafeatures=None):
|
|||||||
# In status only mode there are no logs and no UI
|
# In status only mode there are no logs and no UI
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
|
|
||||||
if configParams.dump_signatures:
|
|
||||||
if extrafeatures is None:
|
|
||||||
extrafeatures = []
|
|
||||||
extrafeatures.append(bb.cooker.CookerFeatures.RECIPE_SIGGEN_INFO)
|
|
||||||
|
|
||||||
if configParams.server_only:
|
if configParams.server_only:
|
||||||
featureset = []
|
featureset = []
|
||||||
ui_module = None
|
ui_module = None
|
||||||
@@ -454,7 +422,7 @@ def setup_bitbake(configParams, extrafeatures=None):
|
|||||||
retries = 8
|
retries = 8
|
||||||
while retries:
|
while retries:
|
||||||
try:
|
try:
|
||||||
topdir, lock, lockfile = lockBitbake()
|
topdir, lock = lockBitbake()
|
||||||
sockname = topdir + "/bitbake.sock"
|
sockname = topdir + "/bitbake.sock"
|
||||||
if lock:
|
if lock:
|
||||||
if configParams.status_only or configParams.kill_server:
|
if configParams.status_only or configParams.kill_server:
|
||||||
@@ -465,22 +433,18 @@ def setup_bitbake(configParams, extrafeatures=None):
|
|||||||
logger.info("Starting bitbake server...")
|
logger.info("Starting bitbake server...")
|
||||||
# Clear the event queue since we already displayed messages
|
# Clear the event queue since we already displayed messages
|
||||||
bb.event.ui_queue = []
|
bb.event.ui_queue = []
|
||||||
server = bb.server.process.BitBakeServer(lock, sockname, featureset, configParams.server_timeout, configParams.xmlrpcinterface, configParams.profile)
|
server = bb.server.process.BitBakeServer(lock, sockname, featureset, configParams.server_timeout, configParams.xmlrpcinterface)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.info("Reconnecting to bitbake server...")
|
logger.info("Reconnecting to bitbake server...")
|
||||||
if not os.path.exists(sockname):
|
if not os.path.exists(sockname):
|
||||||
logger.info("Previous bitbake instance shutting down?, waiting to retry... (%s)" % timestamp())
|
logger.info("Previous bitbake instance shutting down?, waiting to retry...")
|
||||||
procs = bb.server.process.get_lockfile_process_msg(lockfile)
|
|
||||||
if procs:
|
|
||||||
logger.info("Processes holding bitbake.lock (missing socket %s):\n%s" % (sockname, procs))
|
|
||||||
logger.info("Directory listing: %s" % (str(os.listdir(topdir))))
|
|
||||||
i = 0
|
i = 0
|
||||||
lock = None
|
lock = None
|
||||||
# Wait for 5s or until we can get the lock
|
# Wait for 5s or until we can get the lock
|
||||||
while not lock and i < 50:
|
while not lock and i < 50:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
_, lock, _ = lockBitbake()
|
_, lock = lockBitbake()
|
||||||
i += 1
|
i += 1
|
||||||
if lock:
|
if lock:
|
||||||
bb.utils.unlockfile(lock)
|
bb.utils.unlockfile(lock)
|
||||||
@@ -499,9 +463,9 @@ def setup_bitbake(configParams, extrafeatures=None):
|
|||||||
retries -= 1
|
retries -= 1
|
||||||
tryno = 8 - retries
|
tryno = 8 - retries
|
||||||
if isinstance(e, (bb.server.process.ProcessTimeout, BrokenPipeError, EOFError, SystemExit)):
|
if isinstance(e, (bb.server.process.ProcessTimeout, BrokenPipeError, EOFError, SystemExit)):
|
||||||
logger.info("Retrying server connection (#%d)... (%s)" % (tryno, timestamp()))
|
logger.info("Retrying server connection (#%d)..." % tryno)
|
||||||
else:
|
else:
|
||||||
logger.info("Retrying server connection (#%d)... (%s, %s)" % (tryno, traceback.format_exc(), timestamp()))
|
logger.info("Retrying server connection (#%d)... (%s)" % (tryno, traceback.format_exc()))
|
||||||
|
|
||||||
if not retries:
|
if not retries:
|
||||||
bb.fatal("Unable to connect to bitbake server, or start one (server startup failures would be in bitbake-cookerdaemon.log).")
|
bb.fatal("Unable to connect to bitbake server, or start one (server startup failures would be in bitbake-cookerdaemon.log).")
|
||||||
@@ -530,5 +494,5 @@ def lockBitbake():
|
|||||||
bb.error("Unable to find conf/bblayers.conf or conf/bitbake.conf. BBPATH 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
|
raise BBMainFatal
|
||||||
lockfile = topdir + "/bitbake.lock"
|
lockfile = topdir + "/bitbake.lock"
|
||||||
return topdir, bb.utils.lockfile(lockfile, False, False), lockfile
|
return topdir, bb.utils.lockfile(lockfile, False, False)
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ def getMountedDev(path):
|
|||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getDiskData(BBDirs):
|
def getDiskData(BBDirs, configuration):
|
||||||
|
|
||||||
"""Prepare disk data for disk space monitor"""
|
"""Prepare disk data for disk space monitor"""
|
||||||
|
|
||||||
@@ -76,12 +76,7 @@ def getDiskData(BBDirs):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
action = pathSpaceInodeRe.group(1)
|
action = pathSpaceInodeRe.group(1)
|
||||||
if action == "ABORT":
|
if action not in ("ABORT", "STOPTASKS", "WARN"):
|
||||||
# Emit a deprecation warning
|
|
||||||
logger.warnonce("The BB_DISKMON_DIRS \"ABORT\" action has been renamed to \"HALT\", update configuration")
|
|
||||||
action = "HALT"
|
|
||||||
|
|
||||||
if action not in ("HALT", "STOPTASKS", "WARN"):
|
|
||||||
printErr("Unknown disk space monitor action: %s" % action)
|
printErr("Unknown disk space monitor action: %s" % action)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -173,7 +168,7 @@ class diskMonitor:
|
|||||||
|
|
||||||
BBDirs = configuration.getVar("BB_DISKMON_DIRS") or None
|
BBDirs = configuration.getVar("BB_DISKMON_DIRS") or None
|
||||||
if BBDirs:
|
if BBDirs:
|
||||||
self.devDict = getDiskData(BBDirs)
|
self.devDict = getDiskData(BBDirs, configuration)
|
||||||
if self.devDict:
|
if self.devDict:
|
||||||
self.spaceInterval, self.inodeInterval = getInterval(configuration)
|
self.spaceInterval, self.inodeInterval = getInterval(configuration)
|
||||||
if self.spaceInterval and self.inodeInterval:
|
if self.spaceInterval and self.inodeInterval:
|
||||||
@@ -182,7 +177,7 @@ class diskMonitor:
|
|||||||
# use them to avoid printing too many warning messages
|
# use them to avoid printing too many warning messages
|
||||||
self.preFreeS = {}
|
self.preFreeS = {}
|
||||||
self.preFreeI = {}
|
self.preFreeI = {}
|
||||||
# This is for STOPTASKS and HALT, to avoid printing the message
|
# This is for STOPTASKS and ABORT, to avoid printing the message
|
||||||
# repeatedly while waiting for the tasks to finish
|
# repeatedly while waiting for the tasks to finish
|
||||||
self.checked = {}
|
self.checked = {}
|
||||||
for k in self.devDict:
|
for k in self.devDict:
|
||||||
@@ -224,8 +219,8 @@ class diskMonitor:
|
|||||||
self.checked[k] = True
|
self.checked[k] = True
|
||||||
rq.finish_runqueue(False)
|
rq.finish_runqueue(False)
|
||||||
bb.event.fire(bb.event.DiskFull(dev, 'disk', freeSpace, path), self.configuration)
|
bb.event.fire(bb.event.DiskFull(dev, 'disk', freeSpace, path), self.configuration)
|
||||||
elif action == "HALT" and not self.checked[k]:
|
elif action == "ABORT" and not self.checked[k]:
|
||||||
logger.error("Immediately halt since the disk space monitor action is \"HALT\"!")
|
logger.error("Immediately abort since the disk space monitor action is \"ABORT\"!")
|
||||||
self.checked[k] = True
|
self.checked[k] = True
|
||||||
rq.finish_runqueue(True)
|
rq.finish_runqueue(True)
|
||||||
bb.event.fire(bb.event.DiskFull(dev, 'disk', freeSpace, path), self.configuration)
|
bb.event.fire(bb.event.DiskFull(dev, 'disk', freeSpace, path), self.configuration)
|
||||||
@@ -234,10 +229,9 @@ class diskMonitor:
|
|||||||
freeInode = st.f_favail
|
freeInode = st.f_favail
|
||||||
|
|
||||||
if minInode and freeInode < minInode:
|
if minInode and freeInode < minInode:
|
||||||
# Some filesystems use dynamic inodes so can't run out.
|
# Some filesystems use dynamic inodes so can't run out
|
||||||
# This is reported by the inode count being 0 (btrfs) or the free
|
# (e.g. btrfs). This is reported by the inode count being 0.
|
||||||
# inode count being -1 (cephfs).
|
if st.f_files == 0:
|
||||||
if st.f_files == 0 or st.f_favail == -1:
|
|
||||||
self.devDict[k][2] = None
|
self.devDict[k][2] = None
|
||||||
continue
|
continue
|
||||||
# Always show warning, the self.checked would always be False if the action is WARN
|
# Always show warning, the self.checked would always be False if the action is WARN
|
||||||
@@ -251,8 +245,8 @@ class diskMonitor:
|
|||||||
self.checked[k] = True
|
self.checked[k] = True
|
||||||
rq.finish_runqueue(False)
|
rq.finish_runqueue(False)
|
||||||
bb.event.fire(bb.event.DiskFull(dev, 'inode', freeInode, path), self.configuration)
|
bb.event.fire(bb.event.DiskFull(dev, 'inode', freeInode, path), self.configuration)
|
||||||
elif action == "HALT" and not self.checked[k]:
|
elif action == "ABORT" and not self.checked[k]:
|
||||||
logger.error("Immediately halt since the disk space monitor action is \"HALT\"!")
|
logger.error("Immediately abort since the disk space monitor action is \"ABORT\"!")
|
||||||
self.checked[k] = True
|
self.checked[k] = True
|
||||||
rq.finish_runqueue(True)
|
rq.finish_runqueue(True)
|
||||||
bb.event.fire(bb.event.DiskFull(dev, 'inode', freeInode, path), self.configuration)
|
bb.event.fire(bb.event.DiskFull(dev, 'inode', freeInode, path), self.configuration)
|
||||||
|
|||||||
@@ -30,9 +30,7 @@ class BBLogFormatter(logging.Formatter):
|
|||||||
PLAIN = logging.INFO + 1
|
PLAIN = logging.INFO + 1
|
||||||
VERBNOTE = logging.INFO + 2
|
VERBNOTE = logging.INFO + 2
|
||||||
ERROR = logging.ERROR
|
ERROR = logging.ERROR
|
||||||
ERRORONCE = logging.ERROR - 1
|
|
||||||
WARNING = logging.WARNING
|
WARNING = logging.WARNING
|
||||||
WARNONCE = logging.WARNING - 1
|
|
||||||
CRITICAL = logging.CRITICAL
|
CRITICAL = logging.CRITICAL
|
||||||
|
|
||||||
levelnames = {
|
levelnames = {
|
||||||
@@ -44,9 +42,7 @@ class BBLogFormatter(logging.Formatter):
|
|||||||
PLAIN : '',
|
PLAIN : '',
|
||||||
VERBNOTE: 'NOTE',
|
VERBNOTE: 'NOTE',
|
||||||
WARNING : 'WARNING',
|
WARNING : 'WARNING',
|
||||||
WARNONCE : 'WARNING',
|
|
||||||
ERROR : 'ERROR',
|
ERROR : 'ERROR',
|
||||||
ERRORONCE : 'ERROR',
|
|
||||||
CRITICAL: 'ERROR',
|
CRITICAL: 'ERROR',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,9 +58,7 @@ class BBLogFormatter(logging.Formatter):
|
|||||||
PLAIN : BASECOLOR,
|
PLAIN : BASECOLOR,
|
||||||
VERBNOTE: BASECOLOR,
|
VERBNOTE: BASECOLOR,
|
||||||
WARNING : YELLOW,
|
WARNING : YELLOW,
|
||||||
WARNONCE : YELLOW,
|
|
||||||
ERROR : RED,
|
ERROR : RED,
|
||||||
ERRORONCE : RED,
|
|
||||||
CRITICAL: RED,
|
CRITICAL: RED,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +83,10 @@ class BBLogFormatter(logging.Formatter):
|
|||||||
msg = logging.Formatter.format(self, record)
|
msg = logging.Formatter.format(self, record)
|
||||||
if hasattr(record, 'bb_exc_formatted'):
|
if hasattr(record, 'bb_exc_formatted'):
|
||||||
msg += '\n' + ''.join(record.bb_exc_formatted)
|
msg += '\n' + ''.join(record.bb_exc_formatted)
|
||||||
|
elif hasattr(record, 'bb_exc_info'):
|
||||||
|
etype, value, tb = record.bb_exc_info
|
||||||
|
formatted = bb.exceptions.format_exception(etype, value, tb, limit=5)
|
||||||
|
msg += '\n' + ''.join(formatted)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def colorize(self, record):
|
def colorize(self, record):
|
||||||
@@ -123,22 +121,6 @@ class BBLogFilter(object):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class LogFilterShowOnce(logging.Filter):
|
|
||||||
def __init__(self):
|
|
||||||
self.seen_warnings = set()
|
|
||||||
self.seen_errors = set()
|
|
||||||
|
|
||||||
def filter(self, record):
|
|
||||||
if record.levelno == bb.msg.BBLogFormatter.WARNONCE:
|
|
||||||
if record.msg in self.seen_warnings:
|
|
||||||
return False
|
|
||||||
self.seen_warnings.add(record.msg)
|
|
||||||
if record.levelno == bb.msg.BBLogFormatter.ERRORONCE:
|
|
||||||
if record.msg in self.seen_errors:
|
|
||||||
return False
|
|
||||||
self.seen_errors.add(record.msg)
|
|
||||||
return True
|
|
||||||
|
|
||||||
class LogFilterGEQLevel(logging.Filter):
|
class LogFilterGEQLevel(logging.Filter):
|
||||||
def __init__(self, level):
|
def __init__(self, level):
|
||||||
self.strlevel = str(level)
|
self.strlevel = str(level)
|
||||||
@@ -224,9 +206,8 @@ def logger_create(name, output=sys.stderr, level=logging.INFO, preserve_handlers
|
|||||||
"""Standalone logger creation function"""
|
"""Standalone logger creation function"""
|
||||||
logger = logging.getLogger(name)
|
logger = logging.getLogger(name)
|
||||||
console = logging.StreamHandler(output)
|
console = logging.StreamHandler(output)
|
||||||
console.addFilter(bb.msg.LogFilterShowOnce())
|
|
||||||
format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
|
format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
|
||||||
if color == 'always' or (color == 'auto' and output.isatty() and os.environ.get('NO_COLOR', '') == ''):
|
if color == 'always' or (color == 'auto' and output.isatty()):
|
||||||
format.enable_color()
|
format.enable_color()
|
||||||
console.setFormatter(format)
|
console.setFormatter(format)
|
||||||
if preserve_handlers:
|
if preserve_handlers:
|
||||||
@@ -297,7 +278,7 @@ def setLoggingConfig(defaultconfig, userconfigfile=None):
|
|||||||
with open(os.path.normpath(userconfigfile), 'r') as f:
|
with open(os.path.normpath(userconfigfile), 'r') as f:
|
||||||
if userconfigfile.endswith('.yml') or userconfigfile.endswith('.yaml'):
|
if userconfigfile.endswith('.yml') or userconfigfile.endswith('.yaml'):
|
||||||
import yaml
|
import yaml
|
||||||
userconfig = yaml.safe_load(f)
|
userconfig = yaml.load(f)
|
||||||
elif userconfigfile.endswith('.json') or userconfigfile.endswith('.cfg'):
|
elif userconfigfile.endswith('.json') or userconfigfile.endswith('.cfg'):
|
||||||
import json
|
import json
|
||||||
userconfig = json.load(f)
|
userconfig = json.load(f)
|
||||||
@@ -312,17 +293,10 @@ def setLoggingConfig(defaultconfig, userconfigfile=None):
|
|||||||
|
|
||||||
# Convert all level parameters to integers in case users want to use the
|
# Convert all level parameters to integers in case users want to use the
|
||||||
# bitbake defined level names
|
# bitbake defined level names
|
||||||
for name, h in logconfig["handlers"].items():
|
for h in logconfig["handlers"].values():
|
||||||
if "level" in h:
|
if "level" in h:
|
||||||
h["level"] = bb.msg.stringToLevel(h["level"])
|
h["level"] = bb.msg.stringToLevel(h["level"])
|
||||||
|
|
||||||
# Every handler needs its own instance of the once filter.
|
|
||||||
once_filter_name = name + ".showonceFilter"
|
|
||||||
logconfig.setdefault("filters", {})[once_filter_name] = {
|
|
||||||
"()": "bb.msg.LogFilterShowOnce",
|
|
||||||
}
|
|
||||||
h.setdefault("filters", []).append(once_filter_name)
|
|
||||||
|
|
||||||
for l in logconfig["loggers"].values():
|
for l in logconfig["loggers"].values():
|
||||||
if "level" in l:
|
if "level" in l:
|
||||||
l["level"] = bb.msg.stringToLevel(l["level"])
|
l["level"] = bb.msg.stringToLevel(l["level"])
|
||||||
|
|||||||
@@ -49,32 +49,20 @@ class SkipPackage(SkipRecipe):
|
|||||||
__mtime_cache = {}
|
__mtime_cache = {}
|
||||||
def cached_mtime(f):
|
def cached_mtime(f):
|
||||||
if f not in __mtime_cache:
|
if f not in __mtime_cache:
|
||||||
res = os.stat(f)
|
__mtime_cache[f] = os.stat(f)[stat.ST_MTIME]
|
||||||
__mtime_cache[f] = (res.st_mtime_ns, res.st_size, res.st_ino)
|
|
||||||
return __mtime_cache[f]
|
return __mtime_cache[f]
|
||||||
|
|
||||||
def cached_mtime_noerror(f):
|
def cached_mtime_noerror(f):
|
||||||
if f not in __mtime_cache:
|
if f not in __mtime_cache:
|
||||||
try:
|
try:
|
||||||
res = os.stat(f)
|
__mtime_cache[f] = os.stat(f)[stat.ST_MTIME]
|
||||||
__mtime_cache[f] = (res.st_mtime_ns, res.st_size, res.st_ino)
|
|
||||||
except OSError:
|
except OSError:
|
||||||
return 0
|
return 0
|
||||||
return __mtime_cache[f]
|
return __mtime_cache[f]
|
||||||
|
|
||||||
def check_mtime(f, mtime):
|
|
||||||
try:
|
|
||||||
res = os.stat(f)
|
|
||||||
current_mtime = (res.st_mtime_ns, res.st_size, res.st_ino)
|
|
||||||
__mtime_cache[f] = current_mtime
|
|
||||||
except OSError:
|
|
||||||
current_mtime = 0
|
|
||||||
return current_mtime == mtime
|
|
||||||
|
|
||||||
def update_mtime(f):
|
def update_mtime(f):
|
||||||
try:
|
try:
|
||||||
res = os.stat(f)
|
__mtime_cache[f] = os.stat(f)[stat.ST_MTIME]
|
||||||
__mtime_cache[f] = (res.st_mtime_ns, res.st_size, res.st_ino)
|
|
||||||
except OSError:
|
except OSError:
|
||||||
if f in __mtime_cache:
|
if f in __mtime_cache:
|
||||||
del __mtime_cache[f]
|
del __mtime_cache[f]
|
||||||
@@ -83,7 +71,7 @@ def update_mtime(f):
|
|||||||
|
|
||||||
def update_cache(f):
|
def update_cache(f):
|
||||||
if f in __mtime_cache:
|
if f in __mtime_cache:
|
||||||
logger.debug("Updating mtime cache for %s" % f)
|
logger.debug(1, "Updating mtime cache for %s" % f)
|
||||||
update_mtime(f)
|
update_mtime(f)
|
||||||
|
|
||||||
def clear_cache():
|
def clear_cache():
|
||||||
@@ -111,12 +99,12 @@ def supports(fn, data):
|
|||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def handle(fn, data, include=0, baseconfig=False):
|
def handle(fn, data, include = 0):
|
||||||
"""Call the handler that is appropriate for this file"""
|
"""Call the handler that is appropriate for this file"""
|
||||||
for h in handlers:
|
for h in handlers:
|
||||||
if h['supports'](fn, data):
|
if h['supports'](fn, data):
|
||||||
with data.inchistory.include(fn):
|
with data.inchistory.include(fn):
|
||||||
return h['handle'](fn, data, include, baseconfig)
|
return h['handle'](fn, data, include)
|
||||||
raise ParseError("not a BitBake file", fn)
|
raise ParseError("not a BitBake file", fn)
|
||||||
|
|
||||||
def init(fn, data):
|
def init(fn, data):
|
||||||
@@ -125,8 +113,6 @@ def init(fn, data):
|
|||||||
return h['init'](data)
|
return h['init'](data)
|
||||||
|
|
||||||
def init_parser(d):
|
def init_parser(d):
|
||||||
if hasattr(bb.parse, "siggen"):
|
|
||||||
bb.parse.siggen.exit()
|
|
||||||
bb.parse.siggen = bb.siggen.init(d)
|
bb.parse.siggen = bb.siggen.init(d)
|
||||||
|
|
||||||
def resolve_file(fn, d):
|
def resolve_file(fn, d):
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
#
|
#
|
||||||
|
|
||||||
import sys
|
|
||||||
import bb
|
import bb
|
||||||
from bb import methodpool
|
from bb import methodpool
|
||||||
from bb.parse import logger
|
from bb.parse import logger
|
||||||
@@ -35,7 +34,7 @@ class IncludeNode(AstNode):
|
|||||||
Include the file and evaluate the statements
|
Include the file and evaluate the statements
|
||||||
"""
|
"""
|
||||||
s = data.expand(self.what_file)
|
s = data.expand(self.what_file)
|
||||||
logger.debug2("CONF %s:%s: including %s", self.filename, self.lineno, s)
|
logger.debug(2, "CONF %s:%s: including %s", self.filename, self.lineno, s)
|
||||||
|
|
||||||
# TODO: Cache those includes... maybe not here though
|
# TODO: Cache those includes... maybe not here though
|
||||||
if self.force:
|
if self.force:
|
||||||
@@ -131,10 +130,6 @@ class DataNode(AstNode):
|
|||||||
else:
|
else:
|
||||||
val = groupd["value"]
|
val = groupd["value"]
|
||||||
|
|
||||||
if ":append" in key or ":remove" in key or ":prepend" in key:
|
|
||||||
if op in ["append", "prepend", "postdot", "predot", "ques"]:
|
|
||||||
bb.warn(key + " " + groupd[op] + " is not a recommended operator combination, please replace it.")
|
|
||||||
|
|
||||||
flag = None
|
flag = None
|
||||||
if 'flag' in groupd and groupd['flag'] is not None:
|
if 'flag' in groupd and groupd['flag'] is not None:
|
||||||
flag = groupd['flag']
|
flag = groupd['flag']
|
||||||
@@ -150,7 +145,7 @@ class DataNode(AstNode):
|
|||||||
data.setVar(key, val, parsing=True, **loginfo)
|
data.setVar(key, val, parsing=True, **loginfo)
|
||||||
|
|
||||||
class MethodNode(AstNode):
|
class MethodNode(AstNode):
|
||||||
tr_tbl = str.maketrans('/.+-@%&~', '________')
|
tr_tbl = str.maketrans('/.+-@%&', '_______')
|
||||||
|
|
||||||
def __init__(self, filename, lineno, func_name, body, python, fakeroot):
|
def __init__(self, filename, lineno, func_name, body, python, fakeroot):
|
||||||
AstNode.__init__(self, filename, lineno)
|
AstNode.__init__(self, filename, lineno)
|
||||||
@@ -211,12 +206,10 @@ class ExportFuncsNode(AstNode):
|
|||||||
|
|
||||||
def eval(self, data):
|
def eval(self, data):
|
||||||
|
|
||||||
sentinel = " # Export function set\n"
|
|
||||||
for func in self.n:
|
for func in self.n:
|
||||||
calledfunc = self.classname + "_" + func
|
calledfunc = self.classname + "_" + func
|
||||||
|
|
||||||
basevar = data.getVar(func, False)
|
if data.getVar(func, False) and not data.getVarFlag(func, 'export_func', False):
|
||||||
if basevar and sentinel not in basevar:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if data.getVar(func, False):
|
if data.getVar(func, False):
|
||||||
@@ -226,18 +219,19 @@ class ExportFuncsNode(AstNode):
|
|||||||
for flag in [ "func", "python" ]:
|
for flag in [ "func", "python" ]:
|
||||||
if data.getVarFlag(calledfunc, flag, False):
|
if data.getVarFlag(calledfunc, flag, False):
|
||||||
data.setVarFlag(func, flag, data.getVarFlag(calledfunc, flag, False))
|
data.setVarFlag(func, flag, data.getVarFlag(calledfunc, flag, False))
|
||||||
for flag in ["dirs", "cleandirs", "fakeroot"]:
|
for flag in [ "dirs" ]:
|
||||||
if data.getVarFlag(func, flag, False):
|
if data.getVarFlag(func, flag, False):
|
||||||
data.setVarFlag(calledfunc, flag, data.getVarFlag(func, flag, False))
|
data.setVarFlag(calledfunc, flag, data.getVarFlag(func, flag, False))
|
||||||
data.setVarFlag(func, "filename", "autogenerated")
|
data.setVarFlag(func, "filename", "autogenerated")
|
||||||
data.setVarFlag(func, "lineno", 1)
|
data.setVarFlag(func, "lineno", 1)
|
||||||
|
|
||||||
if data.getVarFlag(calledfunc, "python", False):
|
if data.getVarFlag(calledfunc, "python", False):
|
||||||
data.setVar(func, sentinel + " bb.build.exec_func('" + calledfunc + "', d)\n", parsing=True)
|
data.setVar(func, " bb.build.exec_func('" + calledfunc + "', d)\n", parsing=True)
|
||||||
else:
|
else:
|
||||||
if "-" in self.classname:
|
if "-" in self.classname:
|
||||||
bb.fatal("The classname %s contains a dash character and is calling an sh function %s using EXPORT_FUNCTIONS. Since a dash is illegal in sh function names, this cannot work, please rename the class or don't use EXPORT_FUNCTIONS." % (self.classname, calledfunc))
|
bb.fatal("The classname %s contains a dash character and is calling an sh function %s using EXPORT_FUNCTIONS. Since a dash is illegal in sh function names, this cannot work, please rename the class or don't use EXPORT_FUNCTIONS." % (self.classname, calledfunc))
|
||||||
data.setVar(func, sentinel + " " + calledfunc + "\n", parsing=True)
|
data.setVar(func, " " + calledfunc + "\n", parsing=True)
|
||||||
|
data.setVarFlag(func, 'export_func', '1')
|
||||||
|
|
||||||
class AddTaskNode(AstNode):
|
class AddTaskNode(AstNode):
|
||||||
def __init__(self, filename, lineno, func, before, after):
|
def __init__(self, filename, lineno, func, before, after):
|
||||||
@@ -271,41 +265,6 @@ class BBHandlerNode(AstNode):
|
|||||||
data.setVarFlag(h, "handler", 1)
|
data.setVarFlag(h, "handler", 1)
|
||||||
data.setVar('__BBHANDLERS', bbhands)
|
data.setVar('__BBHANDLERS', bbhands)
|
||||||
|
|
||||||
class PyLibNode(AstNode):
|
|
||||||
def __init__(self, filename, lineno, libdir, namespace):
|
|
||||||
AstNode.__init__(self, filename, lineno)
|
|
||||||
self.libdir = libdir
|
|
||||||
self.namespace = namespace
|
|
||||||
|
|
||||||
def eval(self, data):
|
|
||||||
global_mods = (data.getVar("BB_GLOBAL_PYMODULES") or "").split()
|
|
||||||
for m in global_mods:
|
|
||||||
if m not in bb.utils._context:
|
|
||||||
bb.utils._context[m] = __import__(m)
|
|
||||||
|
|
||||||
libdir = data.expand(self.libdir)
|
|
||||||
if libdir not in sys.path:
|
|
||||||
sys.path.append(libdir)
|
|
||||||
try:
|
|
||||||
bb.utils._context[self.namespace] = __import__(self.namespace)
|
|
||||||
toimport = getattr(bb.utils._context[self.namespace], "BBIMPORTS", [])
|
|
||||||
for i in toimport:
|
|
||||||
bb.utils._context[self.namespace] = __import__(self.namespace + "." + i)
|
|
||||||
mod = getattr(bb.utils._context[self.namespace], i)
|
|
||||||
fn = getattr(mod, "__file__")
|
|
||||||
funcs = {}
|
|
||||||
for f in dir(mod):
|
|
||||||
if f.startswith("_"):
|
|
||||||
continue
|
|
||||||
fcall = getattr(mod, f)
|
|
||||||
if not callable(fcall):
|
|
||||||
continue
|
|
||||||
funcs[f] = fcall
|
|
||||||
bb.codeparser.add_module_functions(fn, funcs, "%s.%s" % (self.namespace, i))
|
|
||||||
|
|
||||||
except AttributeError as e:
|
|
||||||
bb.error("Error importing OE modules: %s" % str(e))
|
|
||||||
|
|
||||||
class InheritNode(AstNode):
|
class InheritNode(AstNode):
|
||||||
def __init__(self, filename, lineno, classes):
|
def __init__(self, filename, lineno, classes):
|
||||||
AstNode.__init__(self, filename, lineno)
|
AstNode.__init__(self, filename, lineno)
|
||||||
@@ -314,16 +273,6 @@ class InheritNode(AstNode):
|
|||||||
def eval(self, data):
|
def eval(self, data):
|
||||||
bb.parse.BBHandler.inherit(self.classes, self.filename, self.lineno, data)
|
bb.parse.BBHandler.inherit(self.classes, self.filename, self.lineno, data)
|
||||||
|
|
||||||
class InheritDeferredNode(AstNode):
|
|
||||||
def __init__(self, filename, lineno, classes):
|
|
||||||
AstNode.__init__(self, filename, lineno)
|
|
||||||
self.inherit = (classes, filename, lineno)
|
|
||||||
|
|
||||||
def eval(self, data):
|
|
||||||
inherits = data.getVar('__BBDEFINHERITS', False) or []
|
|
||||||
inherits.append(self.inherit)
|
|
||||||
data.setVar('__BBDEFINHERITS', inherits)
|
|
||||||
|
|
||||||
def handleInclude(statements, filename, lineno, m, force):
|
def handleInclude(statements, filename, lineno, m, force):
|
||||||
statements.append(IncludeNode(filename, lineno, m.group(1), force))
|
statements.append(IncludeNode(filename, lineno, m.group(1), force))
|
||||||
|
|
||||||
@@ -367,17 +316,10 @@ def handleDelTask(statements, filename, lineno, m):
|
|||||||
def handleBBHandlers(statements, filename, lineno, m):
|
def handleBBHandlers(statements, filename, lineno, m):
|
||||||
statements.append(BBHandlerNode(filename, lineno, m.group(1)))
|
statements.append(BBHandlerNode(filename, lineno, m.group(1)))
|
||||||
|
|
||||||
def handlePyLib(statements, filename, lineno, m):
|
|
||||||
statements.append(PyLibNode(filename, lineno, m.group(1), m.group(2)))
|
|
||||||
|
|
||||||
def handleInherit(statements, filename, lineno, m):
|
def handleInherit(statements, filename, lineno, m):
|
||||||
classes = m.group(1)
|
classes = m.group(1)
|
||||||
statements.append(InheritNode(filename, lineno, classes))
|
statements.append(InheritNode(filename, lineno, classes))
|
||||||
|
|
||||||
def handleInheritDeferred(statements, filename, lineno, m):
|
|
||||||
classes = m.group(1)
|
|
||||||
statements.append(InheritDeferredNode(filename, lineno, classes))
|
|
||||||
|
|
||||||
def runAnonFuncs(d):
|
def runAnonFuncs(d):
|
||||||
code = []
|
code = []
|
||||||
for funcname in d.getVar("__BBANONFUNCS", False) or []:
|
for funcname in d.getVar("__BBANONFUNCS", False) or []:
|
||||||
@@ -387,25 +329,13 @@ def runAnonFuncs(d):
|
|||||||
def finalize(fn, d, variant = None):
|
def finalize(fn, d, variant = None):
|
||||||
saved_handlers = bb.event.get_handlers().copy()
|
saved_handlers = bb.event.get_handlers().copy()
|
||||||
try:
|
try:
|
||||||
# Found renamed variables. Exit immediately
|
|
||||||
if d.getVar("_FAILPARSINGERRORHANDLED", False) == True:
|
|
||||||
raise bb.BBHandledException()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
inherits = d.getVar('__BBDEFINHERITS', False) or []
|
|
||||||
if not inherits:
|
|
||||||
break
|
|
||||||
inherit, filename, lineno = inherits.pop(0)
|
|
||||||
d.setVar('__BBDEFINHERITS', inherits)
|
|
||||||
bb.parse.BBHandler.inherit(inherit, filename, lineno, d, deferred=True)
|
|
||||||
|
|
||||||
for var in d.getVar('__BBHANDLERS', False) or []:
|
for var in d.getVar('__BBHANDLERS', False) or []:
|
||||||
# try to add the handler
|
# try to add the handler
|
||||||
handlerfn = d.getVarFlag(var, "filename", False)
|
handlerfn = d.getVarFlag(var, "filename", False)
|
||||||
if not handlerfn:
|
if not handlerfn:
|
||||||
bb.fatal("Undefined event handler function '%s'" % var)
|
bb.fatal("Undefined event handler function '%s'" % var)
|
||||||
handlerln = int(d.getVarFlag(var, "lineno", False))
|
handlerln = int(d.getVarFlag(var, "lineno", False))
|
||||||
bb.event.register(var, d.getVar(var, False), (d.getVarFlag(var, "eventmask") or "").split(), handlerfn, handlerln, data=d)
|
bb.event.register(var, d.getVar(var, False), (d.getVarFlag(var, "eventmask") or "").split(), handlerfn, handlerln)
|
||||||
|
|
||||||
bb.event.fire(bb.event.RecipePreFinalise(fn), d)
|
bb.event.fire(bb.event.RecipePreFinalise(fn), d)
|
||||||
|
|
||||||
@@ -423,9 +353,6 @@ def finalize(fn, d, variant = None):
|
|||||||
|
|
||||||
d.setVar('BBINCLUDED', bb.parse.get_file_depends(d))
|
d.setVar('BBINCLUDED', bb.parse.get_file_depends(d))
|
||||||
|
|
||||||
if d.getVar('__BBAUTOREV_SEEN') and d.getVar('__BBSRCREV_SEEN') and not d.getVar("__BBAUTOREV_ACTED_UPON"):
|
|
||||||
bb.fatal("AUTOREV/SRCPV set too late for the fetcher to work properly, please set the variables earlier in parsing. Erroring instead of later obtuse build failures.")
|
|
||||||
|
|
||||||
bb.event.fire(bb.event.RecipeParsed(fn), d)
|
bb.event.fire(bb.event.RecipeParsed(fn), d)
|
||||||
finally:
|
finally:
|
||||||
bb.event.set_handlers(saved_handlers)
|
bb.event.set_handlers(saved_handlers)
|
||||||
@@ -449,7 +376,7 @@ def _create_variants(datastores, names, function, onlyfinalise):
|
|||||||
def multi_finalize(fn, d):
|
def multi_finalize(fn, d):
|
||||||
appends = (d.getVar("__BBAPPEND") or "").split()
|
appends = (d.getVar("__BBAPPEND") or "").split()
|
||||||
for append in appends:
|
for append in appends:
|
||||||
logger.debug("Appending .bbappend file %s to %s", append, fn)
|
logger.debug(1, "Appending .bbappend file %s to %s", append, fn)
|
||||||
bb.parse.BBHandler.handle(append, d, True)
|
bb.parse.BBHandler.handle(append, d, True)
|
||||||
|
|
||||||
onlyfinalise = d.getVar("__ONLYFINALISE", False)
|
onlyfinalise = d.getVar("__ONLYFINALISE", False)
|
||||||
@@ -487,9 +414,7 @@ def multi_finalize(fn, d):
|
|||||||
d.setVar("BBEXTENDVARIANT", variantmap[name])
|
d.setVar("BBEXTENDVARIANT", variantmap[name])
|
||||||
else:
|
else:
|
||||||
d.setVar("PN", "%s-%s" % (pn, name))
|
d.setVar("PN", "%s-%s" % (pn, name))
|
||||||
inherits = d.getVar('__BBDEFINHERITS', False) or []
|
bb.parse.BBHandler.inherit(extendedmap[name], fn, 0, d)
|
||||||
inherits.append((extendedmap[name], fn, 0))
|
|
||||||
d.setVar('__BBDEFINHERITS', inherits)
|
|
||||||
|
|
||||||
safe_d.setVar("BBCLASSEXTEND", extended)
|
safe_d.setVar("BBCLASSEXTEND", extended)
|
||||||
_create_variants(datastores, extendedmap.keys(), extendfunc, onlyfinalise)
|
_create_variants(datastores, extendedmap.keys(), extendfunc, onlyfinalise)
|
||||||
|
|||||||
@@ -13,15 +13,17 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import re, bb, os
|
import re, bb, os
|
||||||
import bb.build, bb.utils, bb.data_smart
|
import bb.build, bb.utils
|
||||||
|
|
||||||
from . import ConfHandler
|
from . import ConfHandler
|
||||||
from .. import resolve_file, ast, logger, ParseError
|
from .. import resolve_file, ast, logger, ParseError
|
||||||
from .ConfHandler import include, init
|
from .ConfHandler import include, init
|
||||||
|
|
||||||
__func_start_regexp__ = re.compile(r"(((?P<py>python(?=(\s|\()))|(?P<fr>fakeroot(?=\s)))\s*)*(?P<func>[\w\.\-\+\{\}\$:]+)?\s*\(\s*\)\s*{$" )
|
# 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+(.+)" )
|
__inherit_regexp__ = re.compile(r"inherit\s+(.+)" )
|
||||||
__inherit_def_regexp__ = re.compile(r"inherit_defer\s+(.+)" )
|
|
||||||
__export_func_regexp__ = re.compile(r"EXPORT_FUNCTIONS\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))|(.*)))))*")
|
__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+(.+)")
|
__deltask_regexp__ = re.compile(r"deltask\s+(.+)")
|
||||||
@@ -34,7 +36,6 @@ __infunc__ = []
|
|||||||
__inpython__ = False
|
__inpython__ = False
|
||||||
__body__ = []
|
__body__ = []
|
||||||
__classname__ = ""
|
__classname__ = ""
|
||||||
__residue__ = []
|
|
||||||
|
|
||||||
cached_statements = {}
|
cached_statements = {}
|
||||||
|
|
||||||
@@ -42,46 +43,31 @@ def supports(fn, d):
|
|||||||
"""Return True if fn has a supported extension"""
|
"""Return True if fn has a supported extension"""
|
||||||
return os.path.splitext(fn)[-1] in [".bb", ".bbclass", ".inc"]
|
return os.path.splitext(fn)[-1] in [".bb", ".bbclass", ".inc"]
|
||||||
|
|
||||||
def inherit(files, fn, lineno, d, deferred=False):
|
def inherit(files, fn, lineno, d):
|
||||||
__inherit_cache = d.getVar('__inherit_cache', False) or []
|
__inherit_cache = d.getVar('__inherit_cache', False) or []
|
||||||
#if "${" in files and not deferred:
|
|
||||||
# bb.warn("%s:%s has non deferred conditional inherit" % (fn, lineno))
|
|
||||||
files = d.expand(files).split()
|
files = d.expand(files).split()
|
||||||
for file in files:
|
for file in files:
|
||||||
classtype = d.getVar("__bbclasstype", False)
|
if not os.path.isabs(file) and not file.endswith(".bbclass"):
|
||||||
origfile = file
|
file = os.path.join('classes', '%s.bbclass' % file)
|
||||||
for t in ["classes-" + classtype, "classes"]:
|
|
||||||
file = origfile
|
|
||||||
if not os.path.isabs(file) and not file.endswith(".bbclass"):
|
|
||||||
file = os.path.join(t, '%s.bbclass' % file)
|
|
||||||
|
|
||||||
if not os.path.isabs(file):
|
if not os.path.isabs(file):
|
||||||
bbpath = d.getVar("BBPATH")
|
bbpath = d.getVar("BBPATH")
|
||||||
abs_fn, attempts = bb.utils.which(bbpath, file, history=True)
|
abs_fn, attempts = bb.utils.which(bbpath, file, history=True)
|
||||||
for af in attempts:
|
for af in attempts:
|
||||||
if af != abs_fn:
|
if af != abs_fn:
|
||||||
bb.parse.mark_dependency(d, af)
|
bb.parse.mark_dependency(d, af)
|
||||||
if abs_fn:
|
if abs_fn:
|
||||||
file = abs_fn
|
file = abs_fn
|
||||||
|
|
||||||
if os.path.exists(file):
|
|
||||||
break
|
|
||||||
|
|
||||||
if not os.path.exists(file):
|
|
||||||
raise ParseError("Could not inherit file %s" % (file), fn, lineno)
|
|
||||||
|
|
||||||
if not file in __inherit_cache:
|
if not file in __inherit_cache:
|
||||||
logger.debug("Inheriting %s (from %s:%d)" % (file, fn, lineno))
|
logger.debug(1, "Inheriting %s (from %s:%d)" % (file, fn, lineno))
|
||||||
__inherit_cache.append( file )
|
__inherit_cache.append( file )
|
||||||
d.setVar('__inherit_cache', __inherit_cache)
|
d.setVar('__inherit_cache', __inherit_cache)
|
||||||
try:
|
include(fn, file, lineno, d, "inherit")
|
||||||
bb.parse.handle(file, d, True)
|
|
||||||
except (IOError, OSError) as exc:
|
|
||||||
raise ParseError("Could not inherit file %s: %s" % (fn, exc.strerror), fn, lineno)
|
|
||||||
__inherit_cache = d.getVar('__inherit_cache', False) or []
|
__inherit_cache = d.getVar('__inherit_cache', False) or []
|
||||||
|
|
||||||
def get_statements(filename, absolute_filename, base_name):
|
def get_statements(filename, absolute_filename, base_name):
|
||||||
global cached_statements, __residue__, __body__
|
global cached_statements
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return cached_statements[absolute_filename]
|
return cached_statements[absolute_filename]
|
||||||
@@ -101,17 +87,12 @@ def get_statements(filename, absolute_filename, base_name):
|
|||||||
# add a blank line to close out any python definition
|
# add a blank line to close out any python definition
|
||||||
feeder(lineno, "", filename, base_name, statements, eof=True)
|
feeder(lineno, "", filename, base_name, statements, eof=True)
|
||||||
|
|
||||||
if __residue__:
|
|
||||||
raise ParseError("Unparsed lines %s: %s" % (filename, str(__residue__)), filename, lineno)
|
|
||||||
if __body__:
|
|
||||||
raise ParseError("Unparsed lines from unclosed function %s: %s" % (filename, str(__body__)), filename, lineno)
|
|
||||||
|
|
||||||
if filename.endswith(".bbclass") or filename.endswith(".inc"):
|
if filename.endswith(".bbclass") or filename.endswith(".inc"):
|
||||||
cached_statements[absolute_filename] = statements
|
cached_statements[absolute_filename] = statements
|
||||||
return statements
|
return statements
|
||||||
|
|
||||||
def handle(fn, d, include, baseconfig=False):
|
def handle(fn, d, include):
|
||||||
global __infunc__, __body__, __residue__, __classname__
|
global __func_start_regexp__, __inherit_regexp__, __export_func_regexp__, __addtask_regexp__, __addhandler_regexp__, __infunc__, __body__, __residue__, __classname__
|
||||||
__body__ = []
|
__body__ = []
|
||||||
__infunc__ = []
|
__infunc__ = []
|
||||||
__classname__ = ""
|
__classname__ = ""
|
||||||
@@ -163,7 +144,7 @@ def handle(fn, d, include, baseconfig=False):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
def feeder(lineno, s, fn, root, statements, eof=False):
|
def feeder(lineno, s, fn, root, statements, eof=False):
|
||||||
global __inpython__, __infunc__, __body__, __residue__, __classname__
|
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:
|
# Check tabs in python functions:
|
||||||
# - def py_funcname(): covered by __inpython__
|
# - def py_funcname(): covered by __inpython__
|
||||||
@@ -200,10 +181,10 @@ def feeder(lineno, s, fn, root, statements, eof=False):
|
|||||||
|
|
||||||
if s and s[0] == '#':
|
if s and s[0] == '#':
|
||||||
if len(__residue__) != 0 and __residue__[0][0] != "#":
|
if len(__residue__) != 0 and __residue__[0][0] != "#":
|
||||||
bb.fatal("There is a comment on line %s of file %s:\n'''\n%s\n'''\nwhich is in the middle of a multiline expression. This syntax is invalid, please correct it." % (lineno, fn, s))
|
bb.fatal("There is a comment on line %s of file %s (%s) which is in the middle of a multiline expression.\nBitbake used to ignore these but no longer does so, please fix your metadata as errors are likely as a result of this change." % (lineno, fn, s))
|
||||||
|
|
||||||
if len(__residue__) != 0 and __residue__[0][0] == "#" and (not s or s[0] != "#"):
|
if len(__residue__) != 0 and __residue__[0][0] == "#" and (not s or s[0] != "#"):
|
||||||
bb.fatal("There is a confusing multiline partially commented expression on line %s of file %s:\n%s\nPlease clarify whether this is all a comment or should be parsed." % (lineno - len(__residue__), fn, "\n".join(__residue__)))
|
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))
|
||||||
|
|
||||||
if s and s[-1] == '\\':
|
if s and s[-1] == '\\':
|
||||||
__residue__.append(s[:-1])
|
__residue__.append(s[:-1])
|
||||||
@@ -252,10 +233,6 @@ def feeder(lineno, s, fn, root, statements, eof=False):
|
|||||||
if taskexpression.count(word) > 1:
|
if taskexpression.count(word) > 1:
|
||||||
logger.warning("addtask contained multiple '%s' keywords, only one is supported" % word)
|
logger.warning("addtask contained multiple '%s' keywords, only one is supported" % word)
|
||||||
|
|
||||||
# Check and warn for having task with exprssion as part of task name
|
|
||||||
for te in taskexpression:
|
|
||||||
if any( ( "%s_" % keyword ) in te for keyword in bb.data_smart.__setvar_keyword__ ):
|
|
||||||
raise ParseError("Task name '%s' contains a keyword which is not recommended/supported.\nPlease rename the task not to include the keyword.\n%s" % (te, ("\n".join(map(str, bb.data_smart.__setvar_keyword__)))), fn)
|
|
||||||
ast.handleAddTask(statements, fn, lineno, m)
|
ast.handleAddTask(statements, fn, lineno, m)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -274,12 +251,7 @@ def feeder(lineno, s, fn, root, statements, eof=False):
|
|||||||
ast.handleInherit(statements, fn, lineno, m)
|
ast.handleInherit(statements, fn, lineno, m)
|
||||||
return
|
return
|
||||||
|
|
||||||
m = __inherit_def_regexp__.match(s)
|
return ConfHandler.feeder(lineno, s, fn, statements)
|
||||||
if m:
|
|
||||||
ast.handleInheritDeferred(statements, fn, lineno, m)
|
|
||||||
return
|
|
||||||
|
|
||||||
return ConfHandler.feeder(lineno, s, fn, statements, conffile=False)
|
|
||||||
|
|
||||||
# Add us to the handlers list
|
# Add us to the handlers list
|
||||||
from .. import handlers
|
from .. import handlers
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ from bb.parse import ParseError, resolve_file, ast, logger, handle
|
|||||||
__config_regexp__ = re.compile( r"""
|
__config_regexp__ = re.compile( r"""
|
||||||
^
|
^
|
||||||
(?P<exp>export\s+)?
|
(?P<exp>export\s+)?
|
||||||
(?P<var>[a-zA-Z0-9\-_+.${}/~:]+?)
|
(?P<var>[a-zA-Z0-9\-_+.${}/~]+?)
|
||||||
(\[(?P<flag>[a-zA-Z0-9\-_+.][a-zA-Z0-9\-_+.@]*)\])?
|
(\[(?P<flag>[a-zA-Z0-9\-_+.]+)\])?
|
||||||
|
|
||||||
\s* (
|
\s* (
|
||||||
(?P<colon>:=) |
|
(?P<colon>:=) |
|
||||||
@@ -45,11 +45,13 @@ __include_regexp__ = re.compile( r"include\s+(.+)" )
|
|||||||
__require_regexp__ = re.compile( r"require\s+(.+)" )
|
__require_regexp__ = re.compile( r"require\s+(.+)" )
|
||||||
__export_regexp__ = re.compile( r"export\s+([a-zA-Z0-9\-_+.${}/~]+)$" )
|
__export_regexp__ = re.compile( r"export\s+([a-zA-Z0-9\-_+.${}/~]+)$" )
|
||||||
__unset_regexp__ = re.compile( r"unset\s+([a-zA-Z0-9\-_+.${}/~]+)$" )
|
__unset_regexp__ = re.compile( r"unset\s+([a-zA-Z0-9\-_+.${}/~]+)$" )
|
||||||
__unset_flag_regexp__ = re.compile( r"unset\s+([a-zA-Z0-9\-_+.${}/~]+)\[([a-zA-Z0-9\-_+.][a-zA-Z0-9\-_+.@]+)\]$" )
|
__unset_flag_regexp__ = re.compile( r"unset\s+([a-zA-Z0-9\-_+.${}/~]+)\[([a-zA-Z0-9\-_+.]+)\]$" )
|
||||||
__addpylib_regexp__ = re.compile(r"addpylib\s+(.+)\s+(.+)" )
|
|
||||||
|
|
||||||
def init(data):
|
def init(data):
|
||||||
return
|
topdir = data.getVar('TOPDIR', False)
|
||||||
|
if not topdir:
|
||||||
|
data.setVar('TOPDIR', os.getcwd())
|
||||||
|
|
||||||
|
|
||||||
def supports(fn, d):
|
def supports(fn, d):
|
||||||
return fn[-5:] == ".conf"
|
return fn[-5:] == ".conf"
|
||||||
@@ -93,7 +95,7 @@ def include_single_file(parentfn, fn, lineno, data, error_out):
|
|||||||
if exc.errno == errno.ENOENT:
|
if exc.errno == errno.ENOENT:
|
||||||
if error_out:
|
if error_out:
|
||||||
raise ParseError("Could not %s file %s" % (error_out, fn), parentfn, lineno)
|
raise ParseError("Could not %s file %s" % (error_out, fn), parentfn, lineno)
|
||||||
logger.debug2("CONF file '%s' not found", fn)
|
logger.debug(2, "CONF file '%s' not found", fn)
|
||||||
else:
|
else:
|
||||||
if error_out:
|
if error_out:
|
||||||
raise ParseError("Could not %s file %s: %s" % (error_out, fn, exc.strerror), parentfn, lineno)
|
raise ParseError("Could not %s file %s: %s" % (error_out, fn, exc.strerror), parentfn, lineno)
|
||||||
@@ -108,7 +110,7 @@ def include_single_file(parentfn, fn, lineno, data, error_out):
|
|||||||
# parsing. This turns out to be a hard problem to solve any other way.
|
# parsing. This turns out to be a hard problem to solve any other way.
|
||||||
confFilters = []
|
confFilters = []
|
||||||
|
|
||||||
def handle(fn, data, include, baseconfig=False):
|
def handle(fn, data, include):
|
||||||
init(data)
|
init(data)
|
||||||
|
|
||||||
if include == 0:
|
if include == 0:
|
||||||
@@ -126,26 +128,21 @@ def handle(fn, data, include, baseconfig=False):
|
|||||||
s = f.readline()
|
s = f.readline()
|
||||||
if not s:
|
if not s:
|
||||||
break
|
break
|
||||||
origlineno = lineno
|
|
||||||
origline = s
|
|
||||||
w = s.strip()
|
w = s.strip()
|
||||||
# skip empty lines
|
# skip empty lines
|
||||||
if not w:
|
if not w:
|
||||||
continue
|
continue
|
||||||
s = s.rstrip()
|
s = s.rstrip()
|
||||||
while s[-1] == '\\':
|
while s[-1] == '\\':
|
||||||
line = f.readline()
|
s2 = f.readline().rstrip()
|
||||||
origline += line
|
|
||||||
s2 = line.rstrip()
|
|
||||||
lineno = lineno + 1
|
lineno = lineno + 1
|
||||||
if (not s2 or s2 and s2[0] != "#") and s[0] == "#" :
|
if (not s2 or s2 and s2[0] != "#") and s[0] == "#" :
|
||||||
bb.fatal("There is a confusing multiline, partially commented expression starting on line %s of file %s:\n%s\nPlease clarify whether this is all a comment or should be parsed." % (origlineno, fn, origline))
|
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))
|
||||||
|
|
||||||
s = s[:-1] + s2
|
s = s[:-1] + s2
|
||||||
# skip comments
|
# skip comments
|
||||||
if s[0] == '#':
|
if s[0] == '#':
|
||||||
continue
|
continue
|
||||||
feeder(lineno, s, abs_fn, statements, baseconfig=baseconfig)
|
feeder(lineno, s, abs_fn, statements)
|
||||||
|
|
||||||
# DONE WITH PARSING... time to evaluate
|
# DONE WITH PARSING... time to evaluate
|
||||||
data.setVar('FILE', abs_fn)
|
data.setVar('FILE', abs_fn)
|
||||||
@@ -153,14 +150,14 @@ def handle(fn, data, include, baseconfig=False):
|
|||||||
if oldfile:
|
if oldfile:
|
||||||
data.setVar('FILE', oldfile)
|
data.setVar('FILE', oldfile)
|
||||||
|
|
||||||
|
f.close()
|
||||||
|
|
||||||
for f in confFilters:
|
for f in confFilters:
|
||||||
f(fn, data)
|
f(fn, data)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
# baseconfig is set for the bblayers/layer.conf cookerdata config parsing
|
def feeder(lineno, s, fn, statements):
|
||||||
# The function is also used by BBHandler, conffile would be False
|
|
||||||
def feeder(lineno, s, fn, statements, baseconfig=False, conffile=True):
|
|
||||||
m = __config_regexp__.match(s)
|
m = __config_regexp__.match(s)
|
||||||
if m:
|
if m:
|
||||||
groupd = m.groupdict()
|
groupd = m.groupdict()
|
||||||
@@ -192,11 +189,6 @@ def feeder(lineno, s, fn, statements, baseconfig=False, conffile=True):
|
|||||||
ast.handleUnsetFlag(statements, fn, lineno, m)
|
ast.handleUnsetFlag(statements, fn, lineno, m)
|
||||||
return
|
return
|
||||||
|
|
||||||
m = __addpylib_regexp__.match(s)
|
|
||||||
if baseconfig and conffile and m:
|
|
||||||
ast.handlePyLib(statements, fn, lineno, m)
|
|
||||||
return
|
|
||||||
|
|
||||||
raise ParseError("unparsed line: '%s'" % s, fn, lineno);
|
raise ParseError("unparsed line: '%s'" % s, fn, lineno);
|
||||||
|
|
||||||
# Add us to the handlers list
|
# Add us to the handlers list
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ currently, providing a key/value store accessed by 'domain'.
|
|||||||
#
|
#
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import collections.abc
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import Mapping
|
import warnings
|
||||||
|
from collections import Mapping
|
||||||
|
|
||||||
sqlversion = sqlite3.sqlite_version_info
|
sqlversion = sqlite3.sqlite_version_info
|
||||||
if sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3):
|
if sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3):
|
||||||
@@ -29,7 +29,7 @@ if sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3):
|
|||||||
logger = logging.getLogger("BitBake.PersistData")
|
logger = logging.getLogger("BitBake.PersistData")
|
||||||
|
|
||||||
@functools.total_ordering
|
@functools.total_ordering
|
||||||
class SQLTable(collections.abc.MutableMapping):
|
class SQLTable(collections.MutableMapping):
|
||||||
class _Decorators(object):
|
class _Decorators(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def retry(*, reconnect=True):
|
def retry(*, reconnect=True):
|
||||||
@@ -63,7 +63,7 @@ class SQLTable(collections.abc.MutableMapping):
|
|||||||
"""
|
"""
|
||||||
Decorator that starts a database transaction and creates a database
|
Decorator that starts a database transaction and creates a database
|
||||||
cursor for performing queries. If no exception is thrown, the
|
cursor for performing queries. If no exception is thrown, the
|
||||||
database results are committed. If an exception occurs, the database
|
database results are commited. If an exception occurs, the database
|
||||||
is rolled back. In all cases, the cursor is closed after the
|
is rolled back. In all cases, the cursor is closed after the
|
||||||
function ends.
|
function ends.
|
||||||
|
|
||||||
@@ -154,7 +154,6 @@ class SQLTable(collections.abc.MutableMapping):
|
|||||||
|
|
||||||
def __exit__(self, *excinfo):
|
def __exit__(self, *excinfo):
|
||||||
self.connection.__exit__(*excinfo)
|
self.connection.__exit__(*excinfo)
|
||||||
self.connection.close()
|
|
||||||
|
|
||||||
@_Decorators.retry()
|
@_Decorators.retry()
|
||||||
@_Decorators.transaction
|
@_Decorators.transaction
|
||||||
@@ -209,7 +208,7 @@ class SQLTable(collections.abc.MutableMapping):
|
|||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
if not isinstance(other, Mapping):
|
if not isinstance(other, Mapping):
|
||||||
raise NotImplementedError()
|
raise NotImplemented
|
||||||
|
|
||||||
return len(self) < len(other)
|
return len(self) < len(other)
|
||||||
|
|
||||||
@@ -239,6 +238,55 @@ class SQLTable(collections.abc.MutableMapping):
|
|||||||
def has_key(self, key):
|
def has_key(self, key):
|
||||||
return key in self
|
return key in self
|
||||||
|
|
||||||
|
|
||||||
|
class PersistData(object):
|
||||||
|
"""Deprecated representation of the bitbake persistent data store"""
|
||||||
|
def __init__(self, d):
|
||||||
|
warnings.warn("Use of PersistData is deprecated. Please use "
|
||||||
|
"persist(domain, d) instead.",
|
||||||
|
category=DeprecationWarning,
|
||||||
|
stacklevel=2)
|
||||||
|
|
||||||
|
self.data = persist(d)
|
||||||
|
logger.debug(1, "Using '%s' as the persistent data cache",
|
||||||
|
self.data.filename)
|
||||||
|
|
||||||
|
def addDomain(self, domain):
|
||||||
|
"""
|
||||||
|
Add a domain (pending deprecation)
|
||||||
|
"""
|
||||||
|
return self.data[domain]
|
||||||
|
|
||||||
|
def delDomain(self, domain):
|
||||||
|
"""
|
||||||
|
Removes a domain and all the data it contains
|
||||||
|
"""
|
||||||
|
del self.data[domain]
|
||||||
|
|
||||||
|
def getKeyValues(self, domain):
|
||||||
|
"""
|
||||||
|
Return a list of key + value pairs for a domain
|
||||||
|
"""
|
||||||
|
return list(self.data[domain].items())
|
||||||
|
|
||||||
|
def getValue(self, domain, key):
|
||||||
|
"""
|
||||||
|
Return the value of a key for a domain
|
||||||
|
"""
|
||||||
|
return self.data[domain][key]
|
||||||
|
|
||||||
|
def setValue(self, domain, key, value):
|
||||||
|
"""
|
||||||
|
Sets the value of a key for a domain
|
||||||
|
"""
|
||||||
|
self.data[domain][key] = value
|
||||||
|
|
||||||
|
def delValue(self, domain, key):
|
||||||
|
"""
|
||||||
|
Deletes a key/value pair
|
||||||
|
"""
|
||||||
|
del self.data[domain][key]
|
||||||
|
|
||||||
def persist(domain, d):
|
def persist(domain, d):
|
||||||
"""Convenience factory for SQLTable objects based upon metadata"""
|
"""Convenience factory for SQLTable objects based upon metadata"""
|
||||||
import bb.utils
|
import bb.utils
|
||||||
@@ -250,23 +298,4 @@ def persist(domain, d):
|
|||||||
|
|
||||||
bb.utils.mkdirhier(cachedir)
|
bb.utils.mkdirhier(cachedir)
|
||||||
cachefile = os.path.join(cachedir, "bb_persist_data.sqlite3")
|
cachefile = os.path.join(cachedir, "bb_persist_data.sqlite3")
|
||||||
|
return SQLTable(cachefile, domain)
|
||||||
try:
|
|
||||||
return SQLTable(cachefile, domain)
|
|
||||||
except sqlite3.OperationalError:
|
|
||||||
# Sqlite fails to open database when its path is too long.
|
|
||||||
# After testing, 504 is the biggest path length that can be opened by
|
|
||||||
# sqlite.
|
|
||||||
# Note: This code is called before sanity.bbclass and its path length
|
|
||||||
# check
|
|
||||||
max_len = 504
|
|
||||||
if len(cachefile) > max_len:
|
|
||||||
logger.critical("The path of the cache file is too long "
|
|
||||||
"({0} chars > {1}) to be opened by sqlite! "
|
|
||||||
"Your cache file is \"{2}\"".format(
|
|
||||||
len(cachefile),
|
|
||||||
max_len,
|
|
||||||
cachefile))
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#
|
#
|
||||||
# Copyright BitBake Contributors
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
#
|
#
|
||||||
|
|
||||||
@@ -62,7 +60,7 @@ class Popen(subprocess.Popen):
|
|||||||
"close_fds": True,
|
"close_fds": True,
|
||||||
"preexec_fn": subprocess_setup,
|
"preexec_fn": subprocess_setup,
|
||||||
"stdout": subprocess.PIPE,
|
"stdout": subprocess.PIPE,
|
||||||
"stderr": subprocess.PIPE,
|
"stderr": subprocess.STDOUT,
|
||||||
"stdin": subprocess.PIPE,
|
"stdin": subprocess.PIPE,
|
||||||
"shell": False,
|
"shell": False,
|
||||||
}
|
}
|
||||||
@@ -144,7 +142,7 @@ def _logged_communicate(pipe, log, input, extrafiles):
|
|||||||
while pipe.poll() is None:
|
while pipe.poll() is None:
|
||||||
read_all_pipes(log, rin, outdata, errdata)
|
read_all_pipes(log, rin, outdata, errdata)
|
||||||
|
|
||||||
# Process closed, drain all pipes...
|
# Pocess closed, drain all pipes...
|
||||||
read_all_pipes(log, rin, outdata, errdata)
|
read_all_pipes(log, rin, outdata, errdata)
|
||||||
finally:
|
finally:
|
||||||
log.flush()
|
log.flush()
|
||||||
@@ -183,8 +181,5 @@ def run(cmd, input=None, log=None, extrafiles=None, **options):
|
|||||||
stderr = stderr.decode("utf-8")
|
stderr = stderr.decode("utf-8")
|
||||||
|
|
||||||
if pipe.returncode != 0:
|
if pipe.returncode != 0:
|
||||||
if log:
|
|
||||||
# Don't duplicate the output in the exception if logging it
|
|
||||||
raise ExecutionError(cmd, pipe.returncode, None, None)
|
|
||||||
raise ExecutionError(cmd, pipe.returncode, stdout, stderr)
|
raise ExecutionError(cmd, pipe.returncode, stdout, stderr)
|
||||||
return stdout, stderr
|
return stdout, stderr
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user