summaryrefslogtreecommitdiffstats
path: root/bitbake/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/bin')
-rwxr-xr-xbitbake/bin/bitbake7
-rwxr-xr-xbitbake/bin/bitbake-diffsigs67
-rwxr-xr-xbitbake/bin/bitbake-getvar60
-rwxr-xr-xbitbake/bin/bitbake-hashclient246
-rwxr-xr-xbitbake/bin/bitbake-hashserv147
-rwxr-xr-xbitbake/bin/bitbake-layers6
-rwxr-xr-xbitbake/bin/bitbake-prserv94
-rwxr-xr-xbitbake/bin/bitbake-selftest3
-rwxr-xr-xbitbake/bin/bitbake-server17
-rwxr-xr-xbitbake/bin/bitbake-worker149
-rwxr-xr-xbitbake/bin/git-make-shallow38
-rwxr-xr-xbitbake/bin/toaster18
-rwxr-xr-xbitbake/bin/toaster-eventreplay82
13 files changed, 689 insertions, 245 deletions
diff --git a/bitbake/bin/bitbake b/bitbake/bin/bitbake
index bc762bfc15..382983e087 100755
--- a/bitbake/bin/bitbake
+++ b/bitbake/bin/bitbake
@@ -12,6 +12,8 @@
12 12
13import os 13import os
14import sys 14import sys
15import warnings
16warnings.simplefilter("default")
15 17
16sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 18sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)),
17 'lib')) 19 'lib'))
@@ -23,10 +25,9 @@ except RuntimeError as exc:
23from bb import cookerdata 25from bb import cookerdata
24from bb.main import bitbake_main, BitBakeConfigParameters, BBMainException 26from bb.main import bitbake_main, BitBakeConfigParameters, BBMainException
25 27
26if sys.getfilesystemencoding() != "utf-8": 28bb.utils.check_system_locale()
27 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.")
28 29
29__version__ = "1.49.2" 30__version__ = "2.9.0"
30 31
31if __name__ == "__main__": 32if __name__ == "__main__":
32 if __version__ != bb.__version__: 33 if __version__ != bb.__version__:
diff --git a/bitbake/bin/bitbake-diffsigs b/bitbake/bin/bitbake-diffsigs
index 19420a2df6..8202c78623 100755
--- a/bitbake/bin/bitbake-diffsigs
+++ b/bitbake/bin/bitbake-diffsigs
@@ -11,6 +11,8 @@
11import os 11import os
12import sys 12import sys
13import warnings 13import warnings
14
15warnings.simplefilter("default")
14import argparse 16import argparse
15import logging 17import logging
16import pickle 18import pickle
@@ -26,6 +28,7 @@ logger = bb.msg.logger_create(myname)
26 28
27is_dump = myname == 'bitbake-dumpsig' 29is_dump = myname == 'bitbake-dumpsig'
28 30
31
29def find_siginfo(tinfoil, pn, taskname, sigs=None): 32def find_siginfo(tinfoil, pn, taskname, sigs=None):
30 result = None 33 result = None
31 tinfoil.set_event_mask(['bb.event.FindSigInfoResult', 34 tinfoil.set_event_mask(['bb.event.FindSigInfoResult',
@@ -51,6 +54,7 @@ def find_siginfo(tinfoil, pn, taskname, sigs=None):
51 sys.exit(2) 54 sys.exit(2)
52 return result 55 return result
53 56
57
54def find_siginfo_task(bbhandler, pn, taskname, sig1=None, sig2=None): 58def find_siginfo_task(bbhandler, pn, taskname, sig1=None, sig2=None):
55 """ Find the most recent signature files for the specified PN/task """ 59 """ Find the most recent signature files for the specified PN/task """
56 60
@@ -59,22 +63,25 @@ def find_siginfo_task(bbhandler, pn, taskname, sig1=None, sig2=None):
59 63
60 if sig1 and sig2: 64 if sig1 and sig2:
61 sigfiles = find_siginfo(bbhandler, pn, taskname, [sig1, sig2]) 65 sigfiles = find_siginfo(bbhandler, pn, taskname, [sig1, sig2])
62 if len(sigfiles) == 0: 66 if not sigfiles:
63 logger.error('No sigdata files found matching %s %s matching either %s or %s' % (pn, taskname, sig1, sig2)) 67 logger.error('No sigdata files found matching %s %s matching either %s or %s' % (pn, taskname, sig1, sig2))
64 sys.exit(1) 68 sys.exit(1)
65 elif not sig1 in sigfiles: 69 elif sig1 not in sigfiles:
66 logger.error('No sigdata files found matching %s %s with signature %s' % (pn, taskname, sig1)) 70 logger.error('No sigdata files found matching %s %s with signature %s' % (pn, taskname, sig1))
67 sys.exit(1) 71 sys.exit(1)
68 elif not sig2 in sigfiles: 72 elif sig2 not in sigfiles:
69 logger.error('No sigdata files found matching %s %s with signature %s' % (pn, taskname, sig2)) 73 logger.error('No sigdata files found matching %s %s with signature %s' % (pn, taskname, sig2))
70 sys.exit(1) 74 sys.exit(1)
71 latestfiles = [sigfiles[sig1], sigfiles[sig2]]
72 else: 75 else:
73 filedates = find_siginfo(bbhandler, pn, taskname) 76 sigfiles = find_siginfo(bbhandler, pn, taskname)
74 latestfiles = sorted(filedates.keys(), key=lambda f: filedates[f])[-2:] 77 latestsigs = sorted(sigfiles.keys(), key=lambda h: sigfiles[h]['time'])[-2:]
75 if not latestfiles: 78 if not latestsigs:
76 logger.error('No sigdata files found matching %s %s' % (pn, taskname)) 79 logger.error('No sigdata files found matching %s %s' % (pn, taskname))
77 sys.exit(1) 80 sys.exit(1)
81 sig1 = latestsigs[0]
82 sig2 = latestsigs[1]
83
84 latestfiles = [sigfiles[sig1]['path'], sigfiles[sig2]['path']]
78 85
79 return latestfiles 86 return latestfiles
80 87
@@ -85,14 +92,14 @@ def recursecb(key, hash1, hash2):
85 hashfiles = find_siginfo(tinfoil, key, None, hashes) 92 hashfiles = find_siginfo(tinfoil, key, None, hashes)
86 93
87 recout = [] 94 recout = []
88 if len(hashfiles) == 0: 95 if not hashfiles:
89 recout.append("Unable to find matching sigdata for %s with hashes %s or %s" % (key, hash1, hash2)) 96 recout.append("Unable to find matching sigdata for %s with hashes %s or %s" % (key, hash1, hash2))
90 elif not hash1 in hashfiles: 97 elif hash1 not in hashfiles:
91 recout.append("Unable to find matching sigdata for %s with hash %s" % (key, hash1)) 98 recout.append("Unable to find matching sigdata for %s with hash %s" % (key, hash1))
92 elif not hash2 in hashfiles: 99 elif hash2 not in hashfiles:
93 recout.append("Unable to find matching sigdata for %s with hash %s" % (key, hash2)) 100 recout.append("Unable to find matching sigdata for %s with hash %s" % (key, hash2))
94 else: 101 else:
95 out2 = bb.siggen.compare_sigfiles(hashfiles[hash1], hashfiles[hash2], recursecb, color=color) 102 out2 = bb.siggen.compare_sigfiles(hashfiles[hash1]['path'], hashfiles[hash2]['path'], recursecb, color=color)
96 for change in out2: 103 for change in out2:
97 for line in change.splitlines(): 104 for line in change.splitlines():
98 recout.append(' ' + line) 105 recout.append(' ' + line)
@@ -109,36 +116,36 @@ parser.add_argument('-D', '--debug',
109 116
110if is_dump: 117if is_dump:
111 parser.add_argument("-t", "--task", 118 parser.add_argument("-t", "--task",
112 help="find the signature data file for the last run of the specified task", 119 help="find the signature data file for the last run of the specified task",
113 action="store", dest="taskargs", nargs=2, metavar=('recipename', 'taskname')) 120 action="store", dest="taskargs", nargs=2, metavar=('recipename', 'taskname'))
114 121
115 parser.add_argument("sigdatafile1", 122 parser.add_argument("sigdatafile1",
116 help="Signature file to dump. Not used when using -t/--task.", 123 help="Signature file to dump. Not used when using -t/--task.",
117 action="store", nargs='?', metavar="sigdatafile") 124 action="store", nargs='?', metavar="sigdatafile")
118else: 125else:
119 parser.add_argument('-c', '--color', 126 parser.add_argument('-c', '--color',
120 help='Colorize the output (where %(metavar)s is %(choices)s)', 127 help='Colorize the output (where %(metavar)s is %(choices)s)',
121 choices=['auto', 'always', 'never'], default='auto', metavar='color') 128 choices=['auto', 'always', 'never'], default='auto', metavar='color')
122 129
123 parser.add_argument('-d', '--dump', 130 parser.add_argument('-d', '--dump',
124 help='Dump the last signature data instead of comparing (equivalent to using bitbake-dumpsig)', 131 help='Dump the last signature data instead of comparing (equivalent to using bitbake-dumpsig)',
125 action='store_true') 132 action='store_true')
126 133
127 parser.add_argument("-t", "--task", 134 parser.add_argument("-t", "--task",
128 help="find the signature data files for the last two runs of the specified task and compare them", 135 help="find the signature data files for the last two runs of the specified task and compare them",
129 action="store", dest="taskargs", nargs=2, metavar=('recipename', 'taskname')) 136 action="store", dest="taskargs", nargs=2, metavar=('recipename', 'taskname'))
130 137
131 parser.add_argument("-s", "--signature", 138 parser.add_argument("-s", "--signature",
132 help="With -t/--task, specify the signatures to look for instead of taking the last two", 139 help="With -t/--task, specify the signatures to look for instead of taking the last two",
133 action="store", dest="sigargs", nargs=2, metavar=('fromsig', 'tosig')) 140 action="store", dest="sigargs", nargs=2, metavar=('fromsig', 'tosig'))
134 141
135 parser.add_argument("sigdatafile1", 142 parser.add_argument("sigdatafile1",
136 help="First signature file to compare (or signature file to dump, if second not specified). Not used when using -t/--task.", 143 help="First signature file to compare (or signature file to dump, if second not specified). Not used when using -t/--task.",
137 action="store", nargs='?') 144 action="store", nargs='?')
138 145
139 parser.add_argument("sigdatafile2", 146 parser.add_argument("sigdatafile2",
140 help="Second signature file to compare", 147 help="Second signature file to compare",
141 action="store", nargs='?') 148 action="store", nargs='?')
142 149
143options = parser.parse_args() 150options = parser.parse_args()
144if is_dump: 151if is_dump:
@@ -156,7 +163,8 @@ if options.taskargs:
156 with bb.tinfoil.Tinfoil() as tinfoil: 163 with bb.tinfoil.Tinfoil() as tinfoil:
157 tinfoil.prepare(config_only=True) 164 tinfoil.prepare(config_only=True)
158 if not options.dump and options.sigargs: 165 if not options.dump and options.sigargs:
159 files = find_siginfo_task(tinfoil, options.taskargs[0], options.taskargs[1], options.sigargs[0], options.sigargs[1]) 166 files = find_siginfo_task(tinfoil, options.taskargs[0], options.taskargs[1], options.sigargs[0],
167 options.sigargs[1])
160 else: 168 else:
161 files = find_siginfo_task(tinfoil, options.taskargs[0], options.taskargs[1]) 169 files = find_siginfo_task(tinfoil, options.taskargs[0], options.taskargs[1])
162 170
@@ -165,7 +173,8 @@ if options.taskargs:
165 output = bb.siggen.dump_sigfile(files[-1]) 173 output = bb.siggen.dump_sigfile(files[-1])
166 else: 174 else:
167 if len(files) < 2: 175 if len(files) < 2:
168 logger.error('Only one matching sigdata file found for the specified task (%s %s)' % (options.taskargs[0], options.taskargs[1])) 176 logger.error('Only one matching sigdata file found for the specified task (%s %s)' % (
177 options.taskargs[0], options.taskargs[1]))
169 sys.exit(1) 178 sys.exit(1)
170 179
171 # Recurse into signature comparison 180 # Recurse into signature comparison
diff --git a/bitbake/bin/bitbake-getvar b/bitbake/bin/bitbake-getvar
new file mode 100755
index 0000000000..8901f99ae2
--- /dev/null
+++ b/bitbake/bin/bitbake-getvar
@@ -0,0 +1,60 @@
1#! /usr/bin/env python3
2#
3# Copyright (C) 2021 Richard Purdie
4#
5# SPDX-License-Identifier: GPL-2.0-only
6#
7
8import argparse
9import io
10import os
11import sys
12import warnings
13warnings.simplefilter("default")
14
15bindir = os.path.dirname(__file__)
16topdir = os.path.dirname(bindir)
17sys.path[0:0] = [os.path.join(topdir, 'lib')]
18
19import bb.tinfoil
20
21if __name__ == "__main__":
22 parser = argparse.ArgumentParser(description="Bitbake Query Variable")
23 parser.add_argument("variable", help="variable name to query")
24 parser.add_argument("-r", "--recipe", help="Recipe name to query", default=None, required=False)
25 parser.add_argument('-u', '--unexpand', help='Do not expand the value (with --value)', action="store_true")
26 parser.add_argument('-f', '--flag', help='Specify a variable flag to query (with --value)', default=None)
27 parser.add_argument('--value', help='Only report the value, no history and no variable name', action="store_true")
28 parser.add_argument('-q', '--quiet', help='Silence bitbake server logging', action="store_true")
29 parser.add_argument('--ignore-undefined', help='Suppress any errors related to undefined variables', action="store_true")
30 args = parser.parse_args()
31
32 if not args.value:
33 if args.unexpand:
34 sys.exit("--unexpand only makes sense with --value")
35
36 if args.flag:
37 sys.exit("--flag only makes sense with --value")
38
39 quiet = args.quiet or args.value
40 with bb.tinfoil.Tinfoil(tracking=True, setup_logging=not quiet) as tinfoil:
41 if args.recipe:
42 tinfoil.prepare(quiet=3 if quiet else 2)
43 d = tinfoil.parse_recipe(args.recipe)
44 else:
45 tinfoil.prepare(quiet=2, config_only=True)
46 d = tinfoil.config_data
47
48 value = None
49 if args.flag:
50 value = d.getVarFlag(args.variable, args.flag, expand=not args.unexpand)
51 if value is None and not args.ignore_undefined:
52 sys.exit(f"The flag '{args.flag}' is not defined for variable '{args.variable}'")
53 else:
54 value = d.getVar(args.variable, expand=not args.unexpand)
55 if value is None and not args.ignore_undefined:
56 sys.exit(f"The variable '{args.variable}' is not defined")
57 if args.value:
58 print(str(value if value is not None else ""))
59 else:
60 bb.data.emit_var(args.variable, d=d, all=True)
diff --git a/bitbake/bin/bitbake-hashclient b/bitbake/bin/bitbake-hashclient
index a89290217b..610787ed2b 100755
--- a/bitbake/bin/bitbake-hashclient
+++ b/bitbake/bin/bitbake-hashclient
@@ -13,6 +13,10 @@ import pprint
13import sys 13import sys
14import threading 14import threading
15import time 15import time
16import warnings
17import netrc
18import json
19warnings.simplefilter("default")
16 20
17try: 21try:
18 import tqdm 22 import tqdm
@@ -34,18 +38,42 @@ except ImportError:
34sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) 38sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib'))
35 39
36import hashserv 40import hashserv
41import bb.asyncrpc
37 42
38DEFAULT_ADDRESS = 'unix://./hashserve.sock' 43DEFAULT_ADDRESS = 'unix://./hashserve.sock'
39METHOD = 'stress.test.method' 44METHOD = 'stress.test.method'
40 45
46def print_user(u):
47 print(f"Username: {u['username']}")
48 if "permissions" in u:
49 print("Permissions: " + " ".join(u["permissions"]))
50 if "token" in u:
51 print(f"Token: {u['token']}")
52
41 53
42def main(): 54def main():
55 def handle_get(args, client):
56 result = client.get_taskhash(args.method, args.taskhash, all_properties=True)
57 if not result:
58 return 0
59
60 print(json.dumps(result, sort_keys=True, indent=4))
61 return 0
62
63 def handle_get_outhash(args, client):
64 result = client.get_outhash(args.method, args.outhash, args.taskhash)
65 if not result:
66 return 0
67
68 print(json.dumps(result, sort_keys=True, indent=4))
69 return 0
70
43 def handle_stats(args, client): 71 def handle_stats(args, client):
44 if args.reset: 72 if args.reset:
45 s = client.reset_stats() 73 s = client.reset_stats()
46 else: 74 else:
47 s = client.get_stats() 75 s = client.get_stats()
48 pprint.pprint(s) 76 print(json.dumps(s, sort_keys=True, indent=4))
49 return 0 77 return 0
50 78
51 def handle_stress(args, client): 79 def handle_stress(args, client):
@@ -54,25 +82,24 @@ def main():
54 nonlocal missed_hashes 82 nonlocal missed_hashes
55 nonlocal max_time 83 nonlocal max_time
56 84
57 client = hashserv.create_client(args.address) 85 with hashserv.create_client(args.address) as client:
58 86 for i in range(args.requests):
59 for i in range(args.requests): 87 taskhash = hashlib.sha256()
60 taskhash = hashlib.sha256() 88 taskhash.update(args.taskhash_seed.encode('utf-8'))
61 taskhash.update(args.taskhash_seed.encode('utf-8')) 89 taskhash.update(str(i).encode('utf-8'))
62 taskhash.update(str(i).encode('utf-8'))
63 90
64 start_time = time.perf_counter() 91 start_time = time.perf_counter()
65 l = client.get_unihash(METHOD, taskhash.hexdigest()) 92 l = client.get_unihash(METHOD, taskhash.hexdigest())
66 elapsed = time.perf_counter() - start_time 93 elapsed = time.perf_counter() - start_time
67 94
68 with lock: 95 with lock:
69 if l: 96 if l:
70 found_hashes += 1 97 found_hashes += 1
71 else: 98 else:
72 missed_hashes += 1 99 missed_hashes += 1
73 100
74 max_time = max(elapsed, max_time) 101 max_time = max(elapsed, max_time)
75 pbar.update() 102 pbar.update()
76 103
77 max_time = 0 104 max_time = 0
78 found_hashes = 0 105 found_hashes = 0
@@ -111,12 +138,114 @@ def main():
111 with lock: 138 with lock:
112 pbar.update() 139 pbar.update()
113 140
141 def handle_remove(args, client):
142 where = {k: v for k, v in args.where}
143 if where:
144 result = client.remove(where)
145 print("Removed %d row(s)" % (result["count"]))
146 else:
147 print("No query specified")
148
149 def handle_clean_unused(args, client):
150 result = client.clean_unused(args.max_age)
151 print("Removed %d rows" % (result["count"]))
152 return 0
153
154 def handle_refresh_token(args, client):
155 r = client.refresh_token(args.username)
156 print_user(r)
157
158 def handle_set_user_permissions(args, client):
159 r = client.set_user_perms(args.username, args.permissions)
160 print_user(r)
161
162 def handle_get_user(args, client):
163 r = client.get_user(args.username)
164 print_user(r)
165
166 def handle_get_all_users(args, client):
167 users = client.get_all_users()
168 print("{username:20}| {permissions}".format(username="Username", permissions="Permissions"))
169 print(("-" * 20) + "+" + ("-" * 20))
170 for u in users:
171 print("{username:20}| {permissions}".format(username=u["username"], permissions=" ".join(u["permissions"])))
172
173 def handle_new_user(args, client):
174 r = client.new_user(args.username, args.permissions)
175 print_user(r)
176
177 def handle_delete_user(args, client):
178 r = client.delete_user(args.username)
179 print_user(r)
180
181 def handle_get_db_usage(args, client):
182 usage = client.get_db_usage()
183 print(usage)
184 tables = sorted(usage.keys())
185 print("{name:20}| {rows:20}".format(name="Table name", rows="Rows"))
186 print(("-" * 20) + "+" + ("-" * 20))
187 for t in tables:
188 print("{name:20}| {rows:<20}".format(name=t, rows=usage[t]["rows"]))
189 print()
190
191 total_rows = sum(t["rows"] for t in usage.values())
192 print(f"Total rows: {total_rows}")
193
194 def handle_get_db_query_columns(args, client):
195 columns = client.get_db_query_columns()
196 print("\n".join(sorted(columns)))
197
198 def handle_gc_status(args, client):
199 result = client.gc_status()
200 if not result["mark"]:
201 print("No Garbage collection in progress")
202 return 0
203
204 print("Current Mark: %s" % result["mark"])
205 print("Total hashes to keep: %d" % result["keep"])
206 print("Total hashes to remove: %s" % result["remove"])
207 return 0
208
209 def handle_gc_mark(args, client):
210 where = {k: v for k, v in args.where}
211 result = client.gc_mark(args.mark, where)
212 print("New hashes marked: %d" % result["count"])
213 return 0
214
215 def handle_gc_sweep(args, client):
216 result = client.gc_sweep(args.mark)
217 print("Removed %d rows" % result["count"])
218 return 0
219
220 def handle_unihash_exists(args, client):
221 result = client.unihash_exists(args.unihash)
222 if args.quiet:
223 return 0 if result else 1
224
225 print("true" if result else "false")
226 return 0
227
114 parser = argparse.ArgumentParser(description='Hash Equivalence Client') 228 parser = argparse.ArgumentParser(description='Hash Equivalence Client')
115 parser.add_argument('--address', default=DEFAULT_ADDRESS, help='Server address (default "%(default)s")') 229 parser.add_argument('--address', default=DEFAULT_ADDRESS, help='Server address (default "%(default)s")')
116 parser.add_argument('--log', default='WARNING', help='Set logging level') 230 parser.add_argument('--log', default='WARNING', help='Set logging level')
231 parser.add_argument('--login', '-l', metavar="USERNAME", help="Authenticate as USERNAME")
232 parser.add_argument('--password', '-p', metavar="TOKEN", help="Authenticate using token TOKEN")
233 parser.add_argument('--become', '-b', metavar="USERNAME", help="Impersonate user USERNAME (if allowed) when performing actions")
234 parser.add_argument('--no-netrc', '-n', action="store_false", dest="netrc", help="Do not use .netrc")
117 235
118 subparsers = parser.add_subparsers() 236 subparsers = parser.add_subparsers()
119 237
238 get_parser = subparsers.add_parser('get', help="Get the unihash for a taskhash")
239 get_parser.add_argument("method", help="Method to query")
240 get_parser.add_argument("taskhash", help="Task hash to query")
241 get_parser.set_defaults(func=handle_get)
242
243 get_outhash_parser = subparsers.add_parser('get-outhash', help="Get output hash information")
244 get_outhash_parser.add_argument("method", help="Method to query")
245 get_outhash_parser.add_argument("outhash", help="Output hash to query")
246 get_outhash_parser.add_argument("taskhash", help="Task hash to query")
247 get_outhash_parser.set_defaults(func=handle_get_outhash)
248
120 stats_parser = subparsers.add_parser('stats', help='Show server stats') 249 stats_parser = subparsers.add_parser('stats', help='Show server stats')
121 stats_parser.add_argument('--reset', action='store_true', 250 stats_parser.add_argument('--reset', action='store_true',
122 help='Reset server stats') 251 help='Reset server stats')
@@ -135,6 +264,64 @@ def main():
135 help='Include string in outhash') 264 help='Include string in outhash')
136 stress_parser.set_defaults(func=handle_stress) 265 stress_parser.set_defaults(func=handle_stress)
137 266
267 remove_parser = subparsers.add_parser('remove', help="Remove hash entries")
268 remove_parser.add_argument("--where", "-w", metavar="KEY VALUE", nargs=2, action="append", default=[],
269 help="Remove entries from table where KEY == VALUE")
270 remove_parser.set_defaults(func=handle_remove)
271
272 clean_unused_parser = subparsers.add_parser('clean-unused', help="Remove unused database entries")
273 clean_unused_parser.add_argument("max_age", metavar="SECONDS", type=int, help="Remove unused entries older than SECONDS old")
274 clean_unused_parser.set_defaults(func=handle_clean_unused)
275
276 refresh_token_parser = subparsers.add_parser('refresh-token', help="Refresh auth token")
277 refresh_token_parser.add_argument("--username", "-u", help="Refresh the token for another user (if authorized)")
278 refresh_token_parser.set_defaults(func=handle_refresh_token)
279
280 set_user_perms_parser = subparsers.add_parser('set-user-perms', help="Set new permissions for user")
281 set_user_perms_parser.add_argument("--username", "-u", help="Username", required=True)
282 set_user_perms_parser.add_argument("permissions", metavar="PERM", nargs="*", default=[], help="New permissions")
283 set_user_perms_parser.set_defaults(func=handle_set_user_permissions)
284
285 get_user_parser = subparsers.add_parser('get-user', help="Get user")
286 get_user_parser.add_argument("--username", "-u", help="Username")
287 get_user_parser.set_defaults(func=handle_get_user)
288
289 get_all_users_parser = subparsers.add_parser('get-all-users', help="List all users")
290 get_all_users_parser.set_defaults(func=handle_get_all_users)
291
292 new_user_parser = subparsers.add_parser('new-user', help="Create new user")
293 new_user_parser.add_argument("--username", "-u", help="Username", required=True)
294 new_user_parser.add_argument("permissions", metavar="PERM", nargs="*", default=[], help="New permissions")
295 new_user_parser.set_defaults(func=handle_new_user)
296
297 delete_user_parser = subparsers.add_parser('delete-user', help="Delete user")
298 delete_user_parser.add_argument("--username", "-u", help="Username", required=True)
299 delete_user_parser.set_defaults(func=handle_delete_user)
300
301 db_usage_parser = subparsers.add_parser('get-db-usage', help="Database Usage")
302 db_usage_parser.set_defaults(func=handle_get_db_usage)
303
304 db_query_columns_parser = subparsers.add_parser('get-db-query-columns', help="Show columns that can be used in database queries")
305 db_query_columns_parser.set_defaults(func=handle_get_db_query_columns)
306
307 gc_status_parser = subparsers.add_parser("gc-status", help="Show garbage collection status")
308 gc_status_parser.set_defaults(func=handle_gc_status)
309
310 gc_mark_parser = subparsers.add_parser('gc-mark', help="Mark hashes to be kept for garbage collection")
311 gc_mark_parser.add_argument("mark", help="Mark for this garbage collection operation")
312 gc_mark_parser.add_argument("--where", "-w", metavar="KEY VALUE", nargs=2, action="append", default=[],
313 help="Keep entries in table where KEY == VALUE")
314 gc_mark_parser.set_defaults(func=handle_gc_mark)
315
316 gc_sweep_parser = subparsers.add_parser('gc-sweep', help="Perform garbage collection and delete any entries that are not marked")
317 gc_sweep_parser.add_argument("mark", help="Mark for this garbage collection operation")
318 gc_sweep_parser.set_defaults(func=handle_gc_sweep)
319
320 unihash_exists_parser = subparsers.add_parser('unihash-exists', help="Check if a unihash is known to the server")
321 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")
322 unihash_exists_parser.add_argument("unihash", help="Unihash to check")
323 unihash_exists_parser.set_defaults(func=handle_unihash_exists)
324
138 args = parser.parse_args() 325 args = parser.parse_args()
139 326
140 logger = logging.getLogger('hashserv') 327 logger = logging.getLogger('hashserv')
@@ -148,11 +335,30 @@ def main():
148 console.setLevel(level) 335 console.setLevel(level)
149 logger.addHandler(console) 336 logger.addHandler(console)
150 337
338 login = args.login
339 password = args.password
340
341 if login is None and args.netrc:
342 try:
343 n = netrc.netrc()
344 auth = n.authenticators(args.address)
345 if auth is not None:
346 login, _, password = auth
347 except FileNotFoundError:
348 pass
349 except netrc.NetrcParseError as e:
350 sys.stderr.write(f"Error parsing {e.filename}:{e.lineno}: {e.msg}\n")
351
151 func = getattr(args, 'func', None) 352 func = getattr(args, 'func', None)
152 if func: 353 if func:
153 client = hashserv.create_client(args.address) 354 try:
154 355 with hashserv.create_client(args.address, login, password) as client:
155 return func(args, client) 356 if args.become:
357 client.become_user(args.become)
358 return func(args, client)
359 except bb.asyncrpc.InvokeError as e:
360 print(f"ERROR: {e}")
361 return 1
156 362
157 return 0 363 return 0
158 364
diff --git a/bitbake/bin/bitbake-hashserv b/bitbake/bin/bitbake-hashserv
index 153f65a378..4bfb7abfbc 100755
--- a/bitbake/bin/bitbake-hashserv
+++ b/bitbake/bin/bitbake-hashserv
@@ -10,55 +10,162 @@ import sys
10import logging 10import logging
11import argparse 11import argparse
12import sqlite3 12import sqlite3
13import warnings
13 14
14sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) 15warnings.simplefilter("default")
16
17sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib"))
15 18
16import hashserv 19import hashserv
20from hashserv.server import DEFAULT_ANON_PERMS
17 21
18VERSION = "1.0.0" 22VERSION = "1.0.0"
19 23
20DEFAULT_BIND = 'unix://./hashserve.sock' 24DEFAULT_BIND = "unix://./hashserve.sock"
21 25
22 26
23def main(): 27def main():
24 parser = argparse.ArgumentParser(description='Hash Equivalence Reference Server. Version=%s' % VERSION, 28 parser = argparse.ArgumentParser(
25 epilog='''The bind address is the path to a unix domain socket if it is 29 description="Hash Equivalence Reference Server. Version=%s" % VERSION,
26 prefixed with "unix://". Otherwise, it is an IP address 30 formatter_class=argparse.RawTextHelpFormatter,
27 and port in form ADDRESS:PORT. To bind to all addresses, leave 31 epilog="""
28 the ADDRESS empty, e.g. "--bind :8686". To bind to a specific 32The bind address may take one of the following formats:
29 IPv6 address, enclose the address in "[]", e.g. 33 unix://PATH - Bind to unix domain socket at PATH
30 "--bind [::1]:8686"''' 34 ws://ADDRESS:PORT - Bind to websocket on ADDRESS:PORT
31 ) 35 ADDRESS:PORT - Bind to raw TCP socket on ADDRESS:PORT
32 36
33 parser.add_argument('-b', '--bind', default=DEFAULT_BIND, help='Bind address (default "%(default)s")') 37To bind to all addresses, leave the ADDRESS empty, e.g. "--bind :8686" or
34 parser.add_argument('-d', '--database', default='./hashserv.db', help='Database file (default "%(default)s")') 38"--bind ws://:8686". To bind to a specific IPv6 address, enclose the address in
35 parser.add_argument('-l', '--log', default='WARNING', help='Set logging level') 39"[]", e.g. "--bind [::1]:8686" or "--bind ws://[::1]:8686"
36 parser.add_argument('-u', '--upstream', help='Upstream hashserv to pull hashes from') 40
37 parser.add_argument('-r', '--read-only', action='store_true', help='Disallow write operations from clients') 41Note that the default Anonymous permissions are designed to not break existing
42server instances when upgrading, but are not particularly secure defaults. If
43you want to use authentication, it is recommended that you use "--anon-perms
44@read" to only give anonymous users read access, or "--anon-perms @none" to
45give un-authenticated users no access at all.
46
47Setting "--anon-perms @all" or "--anon-perms @user-admin" is not allowed, since
48this would allow anonymous users to manage all users accounts, which is a bad
49idea.
50
51If you are using user authentication, you should run your server in websockets
52mode with an SSL terminating load balancer in front of it (as this server does
53not implement SSL). Otherwise all usernames and passwords will be transmitted
54in the clear. When configured this way, clients can connect using a secure
55websocket, as in "wss://SERVER:PORT"
56
57The following permissions are supported by the server:
58
59 @none - No permissions
60 @read - The ability to read equivalent hashes from the server
61 @report - The ability to report equivalent hashes to the server
62 @db-admin - Manage the hash database(s). This includes cleaning the
63 database, removing hashes, etc.
64 @user-admin - The ability to manage user accounts. This includes, creating
65 users, deleting users, resetting login tokens, and assigning
66 permissions.
67 @all - All possible permissions, including any that may be added
68 in the future
69 """,
70 )
71
72 parser.add_argument(
73 "-b",
74 "--bind",
75 default=os.environ.get("HASHSERVER_BIND", DEFAULT_BIND),
76 help='Bind address (default $HASHSERVER_BIND, "%(default)s")',
77 )
78 parser.add_argument(
79 "-d",
80 "--database",
81 default=os.environ.get("HASHSERVER_DB", "./hashserv.db"),
82 help='Database file (default $HASHSERVER_DB, "%(default)s")',
83 )
84 parser.add_argument(
85 "-l",
86 "--log",
87 default=os.environ.get("HASHSERVER_LOG_LEVEL", "WARNING"),
88 help='Set logging level (default $HASHSERVER_LOG_LEVEL, "%(default)s")',
89 )
90 parser.add_argument(
91 "-u",
92 "--upstream",
93 default=os.environ.get("HASHSERVER_UPSTREAM", None),
94 help="Upstream hashserv to pull hashes from ($HASHSERVER_UPSTREAM)",
95 )
96 parser.add_argument(
97 "-r",
98 "--read-only",
99 action="store_true",
100 help="Disallow write operations from clients ($HASHSERVER_READ_ONLY)",
101 )
102 parser.add_argument(
103 "--db-username",
104 default=os.environ.get("HASHSERVER_DB_USERNAME", None),
105 help="Database username ($HASHSERVER_DB_USERNAME)",
106 )
107 parser.add_argument(
108 "--db-password",
109 default=os.environ.get("HASHSERVER_DB_PASSWORD", None),
110 help="Database password ($HASHSERVER_DB_PASSWORD)",
111 )
112 parser.add_argument(
113 "--anon-perms",
114 metavar="PERM[,PERM[,...]]",
115 default=os.environ.get("HASHSERVER_ANON_PERMS", ",".join(DEFAULT_ANON_PERMS)),
116 help='Permissions to give anonymous users (default $HASHSERVER_ANON_PERMS, "%(default)s")',
117 )
118 parser.add_argument(
119 "--admin-user",
120 default=os.environ.get("HASHSERVER_ADMIN_USER", None),
121 help="Create default admin user with name ADMIN_USER ($HASHSERVER_ADMIN_USER)",
122 )
123 parser.add_argument(
124 "--admin-password",
125 default=os.environ.get("HASHSERVER_ADMIN_PASSWORD", None),
126 help="Create default admin user with password ADMIN_PASSWORD ($HASHSERVER_ADMIN_PASSWORD)",
127 )
38 128
39 args = parser.parse_args() 129 args = parser.parse_args()
40 130
41 logger = logging.getLogger('hashserv') 131 logger = logging.getLogger("hashserv")
42 132
43 level = getattr(logging, args.log.upper(), None) 133 level = getattr(logging, args.log.upper(), None)
44 if not isinstance(level, int): 134 if not isinstance(level, int):
45 raise ValueError('Invalid log level: %s' % args.log) 135 raise ValueError("Invalid log level: %s (Try ERROR/WARNING/INFO/DEBUG)" % args.log)
46 136
47 logger.setLevel(level) 137 logger.setLevel(level)
48 console = logging.StreamHandler() 138 console = logging.StreamHandler()
49 console.setLevel(level) 139 console.setLevel(level)
50 logger.addHandler(console) 140 logger.addHandler(console)
51 141
52 server = hashserv.create_server(args.bind, args.database, upstream=args.upstream, read_only=args.read_only) 142 read_only = (os.environ.get("HASHSERVER_READ_ONLY", "0") == "1") or args.read_only
143 if "," in args.anon_perms:
144 anon_perms = args.anon_perms.split(",")
145 else:
146 anon_perms = args.anon_perms.split()
147
148 server = hashserv.create_server(
149 args.bind,
150 args.database,
151 upstream=args.upstream,
152 read_only=read_only,
153 db_username=args.db_username,
154 db_password=args.db_password,
155 anon_perms=anon_perms,
156 admin_username=args.admin_user,
157 admin_password=args.admin_password,
158 )
53 server.serve_forever() 159 server.serve_forever()
54 return 0 160 return 0
55 161
56 162
57if __name__ == '__main__': 163if __name__ == "__main__":
58 try: 164 try:
59 ret = main() 165 ret = main()
60 except Exception: 166 except Exception:
61 ret = 1 167 ret = 1
62 import traceback 168 import traceback
169
63 traceback.print_exc() 170 traceback.print_exc()
64 sys.exit(ret) 171 sys.exit(ret)
diff --git a/bitbake/bin/bitbake-layers b/bitbake/bin/bitbake-layers
index ff085d6744..d4b1d1aaf2 100755
--- a/bitbake/bin/bitbake-layers
+++ b/bitbake/bin/bitbake-layers
@@ -14,6 +14,8 @@ import logging
14import os 14import os
15import sys 15import sys
16import argparse 16import argparse
17import warnings
18warnings.simplefilter("default")
17 19
18bindir = os.path.dirname(__file__) 20bindir = os.path.dirname(__file__)
19topdir = os.path.dirname(bindir) 21topdir = os.path.dirname(bindir)
@@ -66,11 +68,11 @@ def main():
66 68
67 registered = False 69 registered = False
68 for plugin in plugins: 70 for plugin in plugins:
71 if hasattr(plugin, 'tinfoil_init'):
72 plugin.tinfoil_init(tinfoil)
69 if hasattr(plugin, 'register_commands'): 73 if hasattr(plugin, 'register_commands'):
70 registered = True 74 registered = True
71 plugin.register_commands(subparsers) 75 plugin.register_commands(subparsers)
72 if hasattr(plugin, 'tinfoil_init'):
73 plugin.tinfoil_init(tinfoil)
74 76
75 if not registered: 77 if not registered:
76 logger.error("No commands registered - missing plugins?") 78 logger.error("No commands registered - missing plugins?")
diff --git a/bitbake/bin/bitbake-prserv b/bitbake/bin/bitbake-prserv
index 1e9b6cbc1b..ad0a069401 100755
--- a/bitbake/bin/bitbake-prserv
+++ b/bitbake/bin/bitbake-prserv
@@ -1,49 +1,83 @@
1#!/usr/bin/env python3 1#!/usr/bin/env python3
2# 2#
3# Copyright BitBake Contributors
4#
3# SPDX-License-Identifier: GPL-2.0-only 5# SPDX-License-Identifier: GPL-2.0-only
4# 6#
5 7
6import os 8import os
7import sys,logging 9import sys,logging
8import optparse 10import argparse
11import warnings
12warnings.simplefilter("default")
9 13
10sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)),'lib')) 14sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib"))
11 15
12import prserv 16import prserv
13import prserv.serv 17import prserv.serv
14 18
15__version__="1.0.0" 19VERSION = "1.1.0"
16 20
17PRHOST_DEFAULT='0.0.0.0' 21PRHOST_DEFAULT="0.0.0.0"
18PRPORT_DEFAULT=8585 22PRPORT_DEFAULT=8585
19 23
20def main(): 24def main():
21 parser = optparse.OptionParser( 25 parser = argparse.ArgumentParser(
22 version="Bitbake PR Service Core version %s, %%prog version %s" % (prserv.__version__, __version__), 26 description="BitBake PR Server. Version=%s" % VERSION,
23 usage = "%prog < --start | --stop > [options]") 27 formatter_class=argparse.RawTextHelpFormatter)
24 28
25 parser.add_option("-f", "--file", help="database filename(default: prserv.sqlite3)", action="store", 29 parser.add_argument(
26 dest="dbfile", type="string", default="prserv.sqlite3") 30 "-f",
27 parser.add_option("-l", "--log", help="log filename(default: prserv.log)", action="store", 31 "--file",
28 dest="logfile", type="string", default="prserv.log") 32 default="prserv.sqlite3",
29 parser.add_option("--loglevel", help="logging level, i.e. CRITICAL, ERROR, WARNING, INFO, DEBUG", 33 help="database filename (default: prserv.sqlite3)",
30 action = "store", type="string", dest="loglevel", default = "INFO") 34 )
31 parser.add_option("--start", help="start daemon", 35 parser.add_argument(
32 action="store_true", dest="start") 36 "-l",
33 parser.add_option("--stop", help="stop daemon", 37 "--log",
34 action="store_true", dest="stop") 38 default="prserv.log",
35 parser.add_option("--host", help="ip address to bind", action="store", 39 help="log filename(default: prserv.log)",
36 dest="host", type="string", default=PRHOST_DEFAULT) 40 )
37 parser.add_option("--port", help="port number(default: 8585)", action="store", 41 parser.add_argument(
38 dest="port", type="int", default=PRPORT_DEFAULT) 42 "--loglevel",
39 43 default="INFO",
40 options, args = parser.parse_args(sys.argv) 44 help="logging level, i.e. CRITICAL, ERROR, WARNING, INFO, DEBUG",
41 prserv.init_logger(os.path.abspath(options.logfile),options.loglevel) 45 )
42 46 parser.add_argument(
43 if options.start: 47 "--start",
44 ret=prserv.serv.start_daemon(options.dbfile, options.host, options.port,os.path.abspath(options.logfile)) 48 action="store_true",
45 elif options.stop: 49 help="start daemon",
46 ret=prserv.serv.stop_daemon(options.host, options.port) 50 )
51 parser.add_argument(
52 "--stop",
53 action="store_true",
54 help="stop daemon",
55 )
56 parser.add_argument(
57 "--host",
58 help="ip address to bind",
59 default=PRHOST_DEFAULT,
60 )
61 parser.add_argument(
62 "--port",
63 type=int,
64 default=PRPORT_DEFAULT,
65 help="port number (default: 8585)",
66 )
67 parser.add_argument(
68 "-r",
69 "--read-only",
70 action="store_true",
71 help="open database in read-only mode",
72 )
73
74 args = parser.parse_args()
75 prserv.init_logger(os.path.abspath(args.log), args.loglevel)
76
77 if args.start:
78 ret=prserv.serv.start_daemon(args.file, args.host, args.port, os.path.abspath(args.log), args.read_only)
79 elif args.stop:
80 ret=prserv.serv.stop_daemon(args.host, args.port)
47 else: 81 else:
48 ret=parser.print_help() 82 ret=parser.print_help()
49 return ret 83 return ret
diff --git a/bitbake/bin/bitbake-selftest b/bitbake/bin/bitbake-selftest
index 6c0737416b..f25f23b1ae 100755
--- a/bitbake/bin/bitbake-selftest
+++ b/bitbake/bin/bitbake-selftest
@@ -7,6 +7,8 @@
7 7
8import os 8import os
9import sys, logging 9import sys, logging
10import warnings
11warnings.simplefilter("default")
10sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) 12sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib'))
11 13
12import unittest 14import unittest
@@ -29,6 +31,7 @@ tests = ["bb.tests.codeparser",
29 "bb.tests.runqueue", 31 "bb.tests.runqueue",
30 "bb.tests.siggen", 32 "bb.tests.siggen",
31 "bb.tests.utils", 33 "bb.tests.utils",
34 "bb.tests.compression",
32 "hashserv.tests", 35 "hashserv.tests",
33 "layerindexlib.tests.layerindexobj", 36 "layerindexlib.tests.layerindexobj",
34 "layerindexlib.tests.restapi", 37 "layerindexlib.tests.restapi",
diff --git a/bitbake/bin/bitbake-server b/bitbake/bin/bitbake-server
index ffbc7894ef..454a3919aa 100755
--- a/bitbake/bin/bitbake-server
+++ b/bitbake/bin/bitbake-server
@@ -8,14 +8,16 @@
8import os 8import os
9import sys 9import sys
10import warnings 10import warnings
11warnings.simplefilter("default")
11import logging 12import logging
12sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) 13sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib'))
13 14
14if sys.getfilesystemencoding() != "utf-8": 15import bb
15 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.") 16
17bb.utils.check_system_locale()
16 18
17# Users shouldn't be running this code directly 19# Users shouldn't be running this code directly
18if len(sys.argv) != 10 or not sys.argv[1].startswith("decafbad"): 20if len(sys.argv) != 11 or not sys.argv[1].startswith("decafbad"):
19 print("bitbake-server is meant for internal execution by bitbake itself, please don't use it standalone.") 21 print("bitbake-server is meant for internal execution by bitbake itself, please don't use it standalone.")
20 sys.exit(1) 22 sys.exit(1)
21 23
@@ -26,12 +28,11 @@ readypipeinfd = int(sys.argv[3])
26logfile = sys.argv[4] 28logfile = sys.argv[4]
27lockname = sys.argv[5] 29lockname = sys.argv[5]
28sockname = sys.argv[6] 30sockname = sys.argv[6]
29timeout = sys.argv[7] 31timeout = float(sys.argv[7])
30xmlrpcinterface = (sys.argv[8], int(sys.argv[9])) 32profile = bool(int(sys.argv[8]))
33xmlrpcinterface = (sys.argv[9], int(sys.argv[10]))
31if xmlrpcinterface[0] == "None": 34if xmlrpcinterface[0] == "None":
32 xmlrpcinterface = (None, xmlrpcinterface[1]) 35 xmlrpcinterface = (None, xmlrpcinterface[1])
33if timeout == "None":
34 timeout = None
35 36
36# Replace standard fds with our own 37# Replace standard fds with our own
37with open('/dev/null', 'r') as si: 38with open('/dev/null', 'r') as si:
@@ -50,5 +51,5 @@ logger = logging.getLogger("BitBake")
50handler = bb.event.LogHandler() 51handler = bb.event.LogHandler()
51logger.addHandler(handler) 52logger.addHandler(handler)
52 53
53bb.server.process.execServer(lockfd, readypipeinfd, lockname, sockname, timeout, xmlrpcinterface) 54bb.server.process.execServer(lockfd, readypipeinfd, lockname, sockname, timeout, xmlrpcinterface, profile)
54 55
diff --git a/bitbake/bin/bitbake-worker b/bitbake/bin/bitbake-worker
index 7765b9368b..e8073f2ac3 100755
--- a/bitbake/bin/bitbake-worker
+++ b/bitbake/bin/bitbake-worker
@@ -1,11 +1,14 @@
1#!/usr/bin/env python3 1#!/usr/bin/env python3
2# 2#
3# Copyright BitBake Contributors
4#
3# SPDX-License-Identifier: GPL-2.0-only 5# SPDX-License-Identifier: GPL-2.0-only
4# 6#
5 7
6import os 8import os
7import sys 9import sys
8import warnings 10import warnings
11warnings.simplefilter("default")
9sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) 12sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib'))
10from bb import fetch2 13from bb import fetch2
11import logging 14import logging
@@ -16,11 +19,12 @@ import signal
16import pickle 19import pickle
17import traceback 20import traceback
18import queue 21import queue
22import shlex
23import subprocess
19from multiprocessing import Lock 24from multiprocessing import Lock
20from threading import Thread 25from threading import Thread
21 26
22if sys.getfilesystemencoding() != "utf-8": 27bb.utils.check_system_locale()
23 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.")
24 28
25# Users shouldn't be running this code directly 29# Users shouldn't be running this code directly
26if len(sys.argv) != 2 or not sys.argv[1].startswith("decafbad"): 30if len(sys.argv) != 2 or not sys.argv[1].startswith("decafbad"):
@@ -87,19 +91,19 @@ def worker_fire_prepickled(event):
87worker_thread_exit = False 91worker_thread_exit = False
88 92
89def worker_flush(worker_queue): 93def worker_flush(worker_queue):
90 worker_queue_int = b"" 94 worker_queue_int = bytearray()
91 global worker_pipe, worker_thread_exit 95 global worker_pipe, worker_thread_exit
92 96
93 while True: 97 while True:
94 try: 98 try:
95 worker_queue_int = worker_queue_int + worker_queue.get(True, 1) 99 worker_queue_int.extend(worker_queue.get(True, 1))
96 except queue.Empty: 100 except queue.Empty:
97 pass 101 pass
98 while (worker_queue_int or not worker_queue.empty()): 102 while (worker_queue_int or not worker_queue.empty()):
99 try: 103 try:
100 (_, ready, _) = select.select([], [worker_pipe], [], 1) 104 (_, ready, _) = select.select([], [worker_pipe], [], 1)
101 if not worker_queue.empty(): 105 if not worker_queue.empty():
102 worker_queue_int = worker_queue_int + worker_queue.get() 106 worker_queue_int.extend(worker_queue.get())
103 written = os.write(worker_pipe, worker_queue_int) 107 written = os.write(worker_pipe, worker_queue_int)
104 worker_queue_int = worker_queue_int[written:] 108 worker_queue_int = worker_queue_int[written:]
105 except (IOError, OSError) as e: 109 except (IOError, OSError) as e:
@@ -117,11 +121,10 @@ def worker_child_fire(event, d):
117 121
118 data = b"<event>" + pickle.dumps(event) + b"</event>" 122 data = b"<event>" + pickle.dumps(event) + b"</event>"
119 try: 123 try:
120 worker_pipe_lock.acquire() 124 with bb.utils.lock_timeout(worker_pipe_lock):
121 while(len(data)): 125 while(len(data)):
122 written = worker_pipe.write(data) 126 written = worker_pipe.write(data)
123 data = data[written:] 127 data = data[written:]
124 worker_pipe_lock.release()
125 except IOError: 128 except IOError:
126 sigterm_handler(None, None) 129 sigterm_handler(None, None)
127 raise 130 raise
@@ -140,15 +143,29 @@ def sigterm_handler(signum, frame):
140 os.killpg(0, signal.SIGTERM) 143 os.killpg(0, signal.SIGTERM)
141 sys.exit() 144 sys.exit()
142 145
143def fork_off_task(cfg, data, databuilder, workerdata, fn, task, taskname, taskhash, unihash, appends, taskdepdata, extraconfigdata, quieterrors=False, dry_run_exec=False): 146def fork_off_task(cfg, data, databuilder, workerdata, extraconfigdata, runtask):
147
148 fn = runtask['fn']
149 task = runtask['task']
150 taskname = runtask['taskname']
151 taskhash = runtask['taskhash']
152 unihash = runtask['unihash']
153 appends = runtask['appends']
154 layername = runtask['layername']
155 taskdepdata = runtask['taskdepdata']
156 quieterrors = runtask['quieterrors']
144 # We need to setup the environment BEFORE the fork, since 157 # We need to setup the environment BEFORE the fork, since
145 # a fork() or exec*() activates PSEUDO... 158 # a fork() or exec*() activates PSEUDO...
146 159
147 envbackup = {} 160 envbackup = {}
161 fakeroot = False
148 fakeenv = {} 162 fakeenv = {}
149 umask = None 163 umask = None
150 164
151 taskdep = workerdata["taskdeps"][fn] 165 uid = os.getuid()
166 gid = os.getgid()
167
168 taskdep = runtask['taskdep']
152 if 'umask' in taskdep and taskname in taskdep['umask']: 169 if 'umask' in taskdep and taskname in taskdep['umask']:
153 umask = taskdep['umask'][taskname] 170 umask = taskdep['umask'][taskname]
154 elif workerdata["umask"]: 171 elif workerdata["umask"]:
@@ -160,24 +177,25 @@ def fork_off_task(cfg, data, databuilder, workerdata, fn, task, taskname, taskha
160 except TypeError: 177 except TypeError:
161 pass 178 pass
162 179
163 dry_run = cfg.dry_run or dry_run_exec 180 dry_run = cfg.dry_run or runtask['dry_run']
164 181
165 # We can't use the fakeroot environment in a dry run as it possibly hasn't been built 182 # We can't use the fakeroot environment in a dry run as it possibly hasn't been built
166 if 'fakeroot' in taskdep and taskname in taskdep['fakeroot'] and not dry_run: 183 if 'fakeroot' in taskdep and taskname in taskdep['fakeroot'] and not dry_run:
167 envvars = (workerdata["fakerootenv"][fn] or "").split() 184 fakeroot = True
168 for key, value in (var.split('=') for var in envvars): 185 envvars = (runtask['fakerootenv'] or "").split()
186 for key, value in (var.split('=',1) for var in envvars):
169 envbackup[key] = os.environ.get(key) 187 envbackup[key] = os.environ.get(key)
170 os.environ[key] = value 188 os.environ[key] = value
171 fakeenv[key] = value 189 fakeenv[key] = value
172 190
173 fakedirs = (workerdata["fakerootdirs"][fn] or "").split() 191 fakedirs = (runtask['fakerootdirs'] or "").split()
174 for p in fakedirs: 192 for p in fakedirs:
175 bb.utils.mkdirhier(p) 193 bb.utils.mkdirhier(p)
176 logger.debug2('Running %s:%s under fakeroot, fakedirs: %s' % 194 logger.debug2('Running %s:%s under fakeroot, fakedirs: %s' %
177 (fn, taskname, ', '.join(fakedirs))) 195 (fn, taskname, ', '.join(fakedirs)))
178 else: 196 else:
179 envvars = (workerdata["fakerootnoenv"][fn] or "").split() 197 envvars = (runtask['fakerootnoenv'] or "").split()
180 for key, value in (var.split('=') for var in envvars): 198 for key, value in (var.split('=',1) for var in envvars):
181 envbackup[key] = os.environ.get(key) 199 envbackup[key] = os.environ.get(key)
182 os.environ[key] = value 200 os.environ[key] = value
183 fakeenv[key] = value 201 fakeenv[key] = value
@@ -219,19 +237,21 @@ def fork_off_task(cfg, data, databuilder, workerdata, fn, task, taskname, taskha
219 # Let SIGHUP exit as SIGTERM 237 # Let SIGHUP exit as SIGTERM
220 signal.signal(signal.SIGHUP, sigterm_handler) 238 signal.signal(signal.SIGHUP, sigterm_handler)
221 239
222 # No stdin 240 # No stdin & stdout
223 newsi = os.open(os.devnull, os.O_RDWR) 241 # stdout is used as a status report channel and must not be used by child processes.
224 os.dup2(newsi, sys.stdin.fileno()) 242 dumbio = os.open(os.devnull, os.O_RDWR)
243 os.dup2(dumbio, sys.stdin.fileno())
244 os.dup2(dumbio, sys.stdout.fileno())
225 245
226 if umask: 246 if umask is not None:
227 os.umask(umask) 247 os.umask(umask)
228 248
229 try: 249 try:
230 bb_cache = bb.cache.NoCache(databuilder)
231 (realfn, virtual, mc) = bb.cache.virtualfn2realfn(fn) 250 (realfn, virtual, mc) = bb.cache.virtualfn2realfn(fn)
232 the_data = databuilder.mcdata[mc] 251 the_data = databuilder.mcdata[mc]
233 the_data.setVar("BB_WORKERCONTEXT", "1") 252 the_data.setVar("BB_WORKERCONTEXT", "1")
234 the_data.setVar("BB_TASKDEPDATA", taskdepdata) 253 the_data.setVar("BB_TASKDEPDATA", taskdepdata)
254 the_data.setVar('BB_CURRENTTASK', taskname.replace("do_", ""))
235 if cfg.limited_deps: 255 if cfg.limited_deps:
236 the_data.setVar("BB_LIMITEDDEPS", "1") 256 the_data.setVar("BB_LIMITEDDEPS", "1")
237 the_data.setVar("BUILDNAME", workerdata["buildname"]) 257 the_data.setVar("BUILDNAME", workerdata["buildname"])
@@ -245,12 +265,20 @@ def fork_off_task(cfg, data, databuilder, workerdata, fn, task, taskname, taskha
245 bb.parse.siggen.set_taskhashes(workerdata["newhashes"]) 265 bb.parse.siggen.set_taskhashes(workerdata["newhashes"])
246 ret = 0 266 ret = 0
247 267
248 the_data = bb_cache.loadDataFull(fn, appends) 268 the_data = databuilder.parseRecipe(fn, appends, layername)
249 the_data.setVar('BB_TASKHASH', taskhash) 269 the_data.setVar('BB_TASKHASH', taskhash)
250 the_data.setVar('BB_UNIHASH', unihash) 270 the_data.setVar('BB_UNIHASH', unihash)
271 bb.parse.siggen.setup_datacache_from_datastore(fn, the_data)
251 272
252 bb.utils.set_process_name("%s:%s" % (the_data.getVar("PN"), taskname.replace("do_", ""))) 273 bb.utils.set_process_name("%s:%s" % (the_data.getVar("PN"), taskname.replace("do_", "")))
253 274
275 if not bb.utils.to_boolean(the_data.getVarFlag(taskname, 'network')):
276 if bb.utils.is_local_uid(uid):
277 logger.debug("Attempting to disable network for %s" % taskname)
278 bb.utils.disable_network(uid, gid)
279 else:
280 logger.debug("Skipping disable network for %s since %s is not a local uid." % (taskname, uid))
281
254 # exported_vars() returns a generator which *cannot* be passed to os.environ.update() 282 # exported_vars() returns a generator which *cannot* be passed to os.environ.update()
255 # successfully. We also need to unset anything from the environment which shouldn't be there 283 # successfully. We also need to unset anything from the environment which shouldn't be there
256 exports = bb.data.exported_vars(the_data) 284 exports = bb.data.exported_vars(the_data)
@@ -279,10 +307,20 @@ def fork_off_task(cfg, data, databuilder, workerdata, fn, task, taskname, taskha
279 if not quieterrors: 307 if not quieterrors:
280 logger.critical(traceback.format_exc()) 308 logger.critical(traceback.format_exc())
281 os._exit(1) 309 os._exit(1)
310
311 sys.stdout.flush()
312 sys.stderr.flush()
313
282 try: 314 try:
283 if dry_run: 315 if dry_run:
284 return 0 316 return 0
285 return bb.build.exec_task(fn, taskname, the_data, cfg.profile) 317 try:
318 ret = bb.build.exec_task(fn, taskname, the_data, cfg.profile)
319 finally:
320 if fakeroot:
321 fakerootcmd = shlex.split(the_data.getVar("FAKEROOTCMD"))
322 subprocess.run(fakerootcmd + ['-S'], check=True, stdout=subprocess.PIPE)
323 return ret
286 except: 324 except:
287 os._exit(1) 325 os._exit(1)
288 if not profiling: 326 if not profiling:
@@ -314,12 +352,12 @@ class runQueueWorkerPipe():
314 if pipeout: 352 if pipeout:
315 pipeout.close() 353 pipeout.close()
316 bb.utils.nonblockingfd(self.input) 354 bb.utils.nonblockingfd(self.input)
317 self.queue = b"" 355 self.queue = bytearray()
318 356
319 def read(self): 357 def read(self):
320 start = len(self.queue) 358 start = len(self.queue)
321 try: 359 try:
322 self.queue = self.queue + (self.input.read(102400) or b"") 360 self.queue.extend(self.input.read(102400) or b"")
323 except (OSError, IOError) as e: 361 except (OSError, IOError) as e:
324 if e.errno != errno.EAGAIN: 362 if e.errno != errno.EAGAIN:
325 raise 363 raise
@@ -347,7 +385,7 @@ class BitbakeWorker(object):
347 def __init__(self, din): 385 def __init__(self, din):
348 self.input = din 386 self.input = din
349 bb.utils.nonblockingfd(self.input) 387 bb.utils.nonblockingfd(self.input)
350 self.queue = b"" 388 self.queue = bytearray()
351 self.cookercfg = None 389 self.cookercfg = None
352 self.databuilder = None 390 self.databuilder = None
353 self.data = None 391 self.data = None
@@ -381,7 +419,7 @@ class BitbakeWorker(object):
381 if len(r) == 0: 419 if len(r) == 0:
382 # EOF on pipe, server must have terminated 420 # EOF on pipe, server must have terminated
383 self.sigterm_exception(signal.SIGTERM, None) 421 self.sigterm_exception(signal.SIGTERM, None)
384 self.queue = self.queue + r 422 self.queue.extend(r)
385 except (OSError, IOError): 423 except (OSError, IOError):
386 pass 424 pass
387 if len(self.queue): 425 if len(self.queue):
@@ -401,19 +439,35 @@ class BitbakeWorker(object):
401 while self.process_waitpid(): 439 while self.process_waitpid():
402 continue 440 continue
403 441
404
405 def handle_item(self, item, func): 442 def handle_item(self, item, func):
406 if self.queue.startswith(b"<" + item + b">"): 443 opening_tag = b"<" + item + b">"
407 index = self.queue.find(b"</" + item + b">") 444 if not self.queue.startswith(opening_tag):
408 while index != -1: 445 return
409 func(self.queue[(len(item) + 2):index]) 446
410 self.queue = self.queue[(index + len(item) + 3):] 447 tag_len = len(opening_tag)
411 index = self.queue.find(b"</" + item + b">") 448 if len(self.queue) < tag_len + 4:
449 # we need to receive more data
450 return
451 header = self.queue[tag_len:tag_len + 4]
452 payload_len = int.from_bytes(header, 'big')
453 # closing tag has length (tag_len + 1)
454 if len(self.queue) < tag_len * 2 + 1 + payload_len:
455 # we need to receive more data
456 return
457
458 index = self.queue.find(b"</" + item + b">")
459 if index != -1:
460 try:
461 func(self.queue[(tag_len + 4):index])
462 except pickle.UnpicklingError:
463 workerlog_write("Unable to unpickle data: %s\n" % ":".join("{:02x}".format(c) for c in self.queue))
464 raise
465 self.queue = self.queue[(index + len(b"</") + len(item) + len(b">")):]
412 466
413 def handle_cookercfg(self, data): 467 def handle_cookercfg(self, data):
414 self.cookercfg = pickle.loads(data) 468 self.cookercfg = pickle.loads(data)
415 self.databuilder = bb.cookerdata.CookerDataBuilder(self.cookercfg, worker=True) 469 self.databuilder = bb.cookerdata.CookerDataBuilder(self.cookercfg, worker=True)
416 self.databuilder.parseBaseConfiguration() 470 self.databuilder.parseBaseConfiguration(worker=True)
417 self.data = self.databuilder.data 471 self.data = self.databuilder.data
418 472
419 def handle_extraconfigdata(self, data): 473 def handle_extraconfigdata(self, data):
@@ -428,6 +482,7 @@ class BitbakeWorker(object):
428 for mc in self.databuilder.mcdata: 482 for mc in self.databuilder.mcdata:
429 self.databuilder.mcdata[mc].setVar("PRSERV_HOST", self.workerdata["prhost"]) 483 self.databuilder.mcdata[mc].setVar("PRSERV_HOST", self.workerdata["prhost"])
430 self.databuilder.mcdata[mc].setVar("BB_HASHSERVE", self.workerdata["hashservaddr"]) 484 self.databuilder.mcdata[mc].setVar("BB_HASHSERVE", self.workerdata["hashservaddr"])
485 self.databuilder.mcdata[mc].setVar("__bbclasstype", "recipe")
431 486
432 def handle_newtaskhashes(self, data): 487 def handle_newtaskhashes(self, data):
433 self.workerdata["newhashes"] = pickle.loads(data) 488 self.workerdata["newhashes"] = pickle.loads(data)
@@ -445,11 +500,15 @@ class BitbakeWorker(object):
445 sys.exit(0) 500 sys.exit(0)
446 501
447 def handle_runtask(self, data): 502 def handle_runtask(self, data):
448 fn, task, taskname, taskhash, unihash, quieterrors, appends, taskdepdata, dry_run_exec = pickle.loads(data) 503 runtask = pickle.loads(data)
449 workerlog_write("Handling runtask %s %s %s\n" % (task, fn, taskname))
450 504
451 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) 505 fn = runtask['fn']
506 task = runtask['task']
507 taskname = runtask['taskname']
452 508
509 workerlog_write("Handling runtask %s %s %s\n" % (task, fn, taskname))
510
511 pid, pipein, pipeout = fork_off_task(self.cookercfg, self.data, self.databuilder, self.workerdata, self.extraconfigdata, runtask)
453 self.build_pids[pid] = task 512 self.build_pids[pid] = task
454 self.build_pipes[pid] = runQueueWorkerPipe(pipein, pipeout) 513 self.build_pipes[pid] = runQueueWorkerPipe(pipein, pipeout)
455 514
@@ -513,9 +572,11 @@ except BaseException as e:
513 import traceback 572 import traceback
514 sys.stderr.write(traceback.format_exc()) 573 sys.stderr.write(traceback.format_exc())
515 sys.stderr.write(str(e)) 574 sys.stderr.write(str(e))
575finally:
576 worker_thread_exit = True
577 worker_thread.join()
516 578
517worker_thread_exit = True 579workerlog_write("exiting")
518worker_thread.join() 580if not normalexit:
519 581 sys.exit(1)
520workerlog_write("exitting")
521sys.exit(0) 582sys.exit(0)
diff --git a/bitbake/bin/git-make-shallow b/bitbake/bin/git-make-shallow
index 57069f7edf..9de557c10e 100755
--- a/bitbake/bin/git-make-shallow
+++ b/bitbake/bin/git-make-shallow
@@ -1,5 +1,7 @@
1#!/usr/bin/env python3 1#!/usr/bin/env python3
2# 2#
3# Copyright BitBake Contributors
4#
3# SPDX-License-Identifier: GPL-2.0-only 5# SPDX-License-Identifier: GPL-2.0-only
4# 6#
5 7
@@ -16,19 +18,23 @@ import itertools
16import os 18import os
17import subprocess 19import subprocess
18import sys 20import sys
21import warnings
22warnings.simplefilter("default")
19 23
20version = 1.0 24version = 1.0
21 25
22 26
27git_cmd = ['git', '-c', 'safe.bareRepository=all']
28
23def main(): 29def main():
24 if sys.version_info < (3, 4, 0): 30 if sys.version_info < (3, 4, 0):
25 sys.exit('Python 3.4 or greater is required') 31 sys.exit('Python 3.4 or greater is required')
26 32
27 git_dir = check_output(['git', 'rev-parse', '--git-dir']).rstrip() 33 git_dir = check_output(git_cmd + ['rev-parse', '--git-dir']).rstrip()
28 shallow_file = os.path.join(git_dir, 'shallow') 34 shallow_file = os.path.join(git_dir, 'shallow')
29 if os.path.exists(shallow_file): 35 if os.path.exists(shallow_file):
30 try: 36 try:
31 check_output(['git', 'fetch', '--unshallow']) 37 check_output(git_cmd + ['fetch', '--unshallow'])
32 except subprocess.CalledProcessError: 38 except subprocess.CalledProcessError:
33 try: 39 try:
34 os.unlink(shallow_file) 40 os.unlink(shallow_file)
@@ -37,21 +43,21 @@ def main():
37 raise 43 raise
38 44
39 args = process_args() 45 args = process_args()
40 revs = check_output(['git', 'rev-list'] + args.revisions).splitlines() 46 revs = check_output(git_cmd + ['rev-list'] + args.revisions).splitlines()
41 47
42 make_shallow(shallow_file, args.revisions, args.refs) 48 make_shallow(shallow_file, args.revisions, args.refs)
43 49
44 ref_revs = check_output(['git', 'rev-list'] + args.refs).splitlines() 50 ref_revs = check_output(git_cmd + ['rev-list'] + args.refs).splitlines()
45 remaining_history = set(revs) & set(ref_revs) 51 remaining_history = set(revs) & set(ref_revs)
46 for rev in remaining_history: 52 for rev in remaining_history:
47 if check_output(['git', 'rev-parse', '{}^@'.format(rev)]): 53 if check_output(git_cmd + ['rev-parse', '{}^@'.format(rev)]):
48 sys.exit('Error: %s was not made shallow' % rev) 54 sys.exit('Error: %s was not made shallow' % rev)
49 55
50 filter_refs(args.refs) 56 filter_refs(args.refs)
51 57
52 if args.shrink: 58 if args.shrink:
53 shrink_repo(git_dir) 59 shrink_repo(git_dir)
54 subprocess.check_call(['git', 'fsck', '--unreachable']) 60 subprocess.check_call(git_cmd + ['fsck', '--unreachable'])
55 61
56 62
57def process_args(): 63def process_args():
@@ -68,12 +74,12 @@ def process_args():
68 args = parser.parse_args() 74 args = parser.parse_args()
69 75
70 if args.refs: 76 if args.refs:
71 args.refs = check_output(['git', 'rev-parse', '--symbolic-full-name'] + args.refs).splitlines() 77 args.refs = check_output(git_cmd + ['rev-parse', '--symbolic-full-name'] + args.refs).splitlines()
72 else: 78 else:
73 args.refs = get_all_refs(lambda r, t, tt: t == 'commit' or tt == 'commit') 79 args.refs = get_all_refs(lambda r, t, tt: t == 'commit' or tt == 'commit')
74 80
75 args.refs = list(filter(lambda r: not r.endswith('/HEAD'), args.refs)) 81 args.refs = list(filter(lambda r: not r.endswith('/HEAD'), args.refs))
76 args.revisions = check_output(['git', 'rev-parse'] + ['%s^{}' % i for i in args.revisions]).splitlines() 82 args.revisions = check_output(git_cmd + ['rev-parse'] + ['%s^{}' % i for i in args.revisions]).splitlines()
77 return args 83 return args
78 84
79 85
@@ -91,7 +97,7 @@ def make_shallow(shallow_file, revisions, refs):
91 97
92def get_all_refs(ref_filter=None): 98def get_all_refs(ref_filter=None):
93 """Return all the existing refs in this repository, optionally filtering the refs.""" 99 """Return all the existing refs in this repository, optionally filtering the refs."""
94 ref_output = check_output(['git', 'for-each-ref', '--format=%(refname)\t%(objecttype)\t%(*objecttype)']) 100 ref_output = check_output(git_cmd + ['for-each-ref', '--format=%(refname)\t%(objecttype)\t%(*objecttype)'])
95 ref_split = [tuple(iter_extend(l.rsplit('\t'), 3)) for l in ref_output.splitlines()] 101 ref_split = [tuple(iter_extend(l.rsplit('\t'), 3)) for l in ref_output.splitlines()]
96 if ref_filter: 102 if ref_filter:
97 ref_split = (e for e in ref_split if ref_filter(*e)) 103 ref_split = (e for e in ref_split if ref_filter(*e))
@@ -109,7 +115,7 @@ def filter_refs(refs):
109 all_refs = get_all_refs() 115 all_refs = get_all_refs()
110 to_remove = set(all_refs) - set(refs) 116 to_remove = set(all_refs) - set(refs)
111 if to_remove: 117 if to_remove:
112 check_output(['xargs', '-0', '-n', '1', 'git', 'update-ref', '-d', '--no-deref'], 118 check_output(['xargs', '-0', '-n', '1'] + git_cmd + ['update-ref', '-d', '--no-deref'],
113 input=''.join(l + '\0' for l in to_remove)) 119 input=''.join(l + '\0' for l in to_remove))
114 120
115 121
@@ -122,7 +128,7 @@ def follow_history_intersections(revisions, refs):
122 if rev in seen: 128 if rev in seen:
123 continue 129 continue
124 130
125 parents = check_output(['git', 'rev-parse', '%s^@' % rev]).splitlines() 131 parents = check_output(git_cmd + ['rev-parse', '%s^@' % rev]).splitlines()
126 132
127 yield rev 133 yield rev
128 seen.add(rev) 134 seen.add(rev)
@@ -130,12 +136,12 @@ def follow_history_intersections(revisions, refs):
130 if not parents: 136 if not parents:
131 continue 137 continue
132 138
133 check_refs = check_output(['git', 'merge-base', '--independent'] + sorted(refs)).splitlines() 139 check_refs = check_output(git_cmd + ['merge-base', '--independent'] + sorted(refs)).splitlines()
134 for parent in parents: 140 for parent in parents:
135 for ref in check_refs: 141 for ref in check_refs:
136 print("Checking %s vs %s" % (parent, ref)) 142 print("Checking %s vs %s" % (parent, ref))
137 try: 143 try:
138 merge_base = check_output(['git', 'merge-base', parent, ref]).rstrip() 144 merge_base = check_output(git_cmd + ['merge-base', parent, ref]).rstrip()
139 except subprocess.CalledProcessError: 145 except subprocess.CalledProcessError:
140 continue 146 continue
141 else: 147 else:
@@ -155,14 +161,14 @@ def iter_except(func, exception, start=None):
155 161
156def shrink_repo(git_dir): 162def shrink_repo(git_dir):
157 """Shrink the newly shallow repository, removing the unreachable objects.""" 163 """Shrink the newly shallow repository, removing the unreachable objects."""
158 subprocess.check_call(['git', 'reflog', 'expire', '--expire-unreachable=now', '--all']) 164 subprocess.check_call(git_cmd + ['reflog', 'expire', '--expire-unreachable=now', '--all'])
159 subprocess.check_call(['git', 'repack', '-ad']) 165 subprocess.check_call(git_cmd + ['repack', '-ad'])
160 try: 166 try:
161 os.unlink(os.path.join(git_dir, 'objects', 'info', 'alternates')) 167 os.unlink(os.path.join(git_dir, 'objects', 'info', 'alternates'))
162 except OSError as exc: 168 except OSError as exc:
163 if exc.errno != errno.ENOENT: 169 if exc.errno != errno.ENOENT:
164 raise 170 raise
165 subprocess.check_call(['git', 'prune', '--expire', 'now']) 171 subprocess.check_call(git_cmd + ['prune', '--expire', 'now'])
166 172
167 173
168if __name__ == '__main__': 174if __name__ == '__main__':
diff --git a/bitbake/bin/toaster b/bitbake/bin/toaster
index 6b90ee187e..f002c8c159 100755
--- a/bitbake/bin/toaster
+++ b/bitbake/bin/toaster
@@ -33,7 +33,7 @@ databaseCheck()
33 $MANAGE migrate --noinput || retval=1 33 $MANAGE migrate --noinput || retval=1
34 34
35 if [ $retval -eq 1 ]; then 35 if [ $retval -eq 1 ]; then
36 echo "Failed migrations, aborting system start" 1>&2 36 echo "Failed migrations, halting system start" 1>&2
37 return $retval 37 return $retval
38 fi 38 fi
39 # Make sure that checksettings can pick up any value for TEMPLATECONF 39 # Make sure that checksettings can pick up any value for TEMPLATECONF
@@ -41,7 +41,7 @@ databaseCheck()
41 $MANAGE checksettings --traceback || retval=1 41 $MANAGE checksettings --traceback || retval=1
42 42
43 if [ $retval -eq 1 ]; then 43 if [ $retval -eq 1 ]; then
44 printf "\nError while checking settings; aborting\n" 44 printf "\nError while checking settings; exiting\n"
45 return $retval 45 return $retval
46 fi 46 fi
47 47
@@ -84,7 +84,7 @@ webserverStartAll()
84 echo "Starting webserver..." 84 echo "Starting webserver..."
85 85
86 $MANAGE runserver --noreload "$ADDR_PORT" \ 86 $MANAGE runserver --noreload "$ADDR_PORT" \
87 </dev/null >>${BUILDDIR}/toaster_web.log 2>&1 \ 87 </dev/null >>${TOASTER_LOGS_DIR}/web.log 2>&1 \
88 & echo $! >${BUILDDIR}/.toastermain.pid 88 & echo $! >${BUILDDIR}/.toastermain.pid
89 89
90 sleep 1 90 sleep 1
@@ -181,6 +181,14 @@ WEBSERVER=1
181export TOASTER_BUILDSERVER=1 181export TOASTER_BUILDSERVER=1
182ADDR_PORT="localhost:8000" 182ADDR_PORT="localhost:8000"
183TOASTERDIR=`dirname $BUILDDIR` 183TOASTERDIR=`dirname $BUILDDIR`
184# ${BUILDDIR}/toaster_logs/ became the default location for toaster logs
185# This is needed for implemented django-log-viewer: https://pypi.org/project/django-log-viewer/
186# If the directory does not exist, create it.
187TOASTER_LOGS_DIR="${BUILDDIR}/toaster_logs/"
188if [ ! -d $TOASTER_LOGS_DIR ]
189then
190 mkdir $TOASTER_LOGS_DIR
191fi
184unset CMD 192unset CMD
185for param in $*; do 193for param in $*; do
186 case $param in 194 case $param in
@@ -248,7 +256,7 @@ fi
248# 3) the sqlite db if that is being used. 256# 3) the sqlite db if that is being used.
249# 4) pid's we need to clean up on exit/shutdown 257# 4) pid's we need to clean up on exit/shutdown
250export TOASTER_DIR=$TOASTERDIR 258export TOASTER_DIR=$TOASTERDIR
251export BB_ENV_EXTRAWHITE="$BB_ENV_EXTRAWHITE TOASTER_DIR" 259export BB_ENV_PASSTHROUGH_ADDITIONS="$BB_ENV_PASSTHROUGH_ADDITIONS TOASTER_DIR"
252 260
253# Determine the action. If specified by arguments, fine, if not, toggle it 261# Determine the action. If specified by arguments, fine, if not, toggle it
254if [ "$CMD" = "start" ] ; then 262if [ "$CMD" = "start" ] ; then
@@ -299,7 +307,7 @@ case $CMD in
299 export BITBAKE_UI='toasterui' 307 export BITBAKE_UI='toasterui'
300 if [ $TOASTER_BUILDSERVER -eq 1 ] ; then 308 if [ $TOASTER_BUILDSERVER -eq 1 ] ; then
301 $MANAGE runbuilds \ 309 $MANAGE runbuilds \
302 </dev/null >>${BUILDDIR}/toaster_runbuilds.log 2>&1 \ 310 </dev/null >>${TOASTER_LOGS_DIR}/toaster_runbuilds.log 2>&1 \
303 & echo $! >${BUILDDIR}/.runbuilds.pid 311 & echo $! >${BUILDDIR}/.runbuilds.pid
304 else 312 else
305 echo "Toaster build server not started." 313 echo "Toaster build server not started."
diff --git a/bitbake/bin/toaster-eventreplay b/bitbake/bin/toaster-eventreplay
index 8fa4ab7116..74a319320e 100755
--- a/bitbake/bin/toaster-eventreplay
+++ b/bitbake/bin/toaster-eventreplay
@@ -19,6 +19,8 @@ import sys
19import json 19import json
20import pickle 20import pickle
21import codecs 21import codecs
22import warnings
23warnings.simplefilter("default")
22 24
23from collections import namedtuple 25from collections import namedtuple
24 26
@@ -28,79 +30,23 @@ sys.path.insert(0, join(dirname(dirname(abspath(__file__))), 'lib'))
28 30
29import bb.cooker 31import bb.cooker
30from bb.ui import toasterui 32from bb.ui import toasterui
31 33from bb.ui import eventreplay
32class EventPlayer:
33 """Emulate a connection to a bitbake server."""
34
35 def __init__(self, eventfile, variables):
36 self.eventfile = eventfile
37 self.variables = variables
38 self.eventmask = []
39
40 def waitEvent(self, _timeout):
41 """Read event from the file."""
42 line = self.eventfile.readline().strip()
43 if not line:
44 return
45 try:
46 event_str = json.loads(line)['vars'].encode('utf-8')
47 event = pickle.loads(codecs.decode(event_str, 'base64'))
48 event_name = "%s.%s" % (event.__module__, event.__class__.__name__)
49 if event_name not in self.eventmask:
50 return
51 return event
52 except ValueError as err:
53 print("Failed loading ", line)
54 raise err
55
56 def runCommand(self, command_line):
57 """Emulate running a command on the server."""
58 name = command_line[0]
59
60 if name == "getVariable":
61 var_name = command_line[1]
62 variable = self.variables.get(var_name)
63 if variable:
64 return variable['v'], None
65 return None, "Missing variable %s" % var_name
66
67 elif name == "getAllKeysWithFlags":
68 dump = {}
69 flaglist = command_line[1]
70 for key, val in self.variables.items():
71 try:
72 if not key.startswith("__"):
73 dump[key] = {
74 'v': val['v'],
75 'history' : val['history'],
76 }
77 for flag in flaglist:
78 dump[key][flag] = val[flag]
79 except Exception as err:
80 print(err)
81 return (dump, None)
82
83 elif name == 'setEventMask':
84 self.eventmask = command_line[-1]
85 return True, None
86
87 else:
88 raise Exception("Command %s not implemented" % command_line[0])
89
90 def getEventHandle(self):
91 """
92 This method is called by toasterui.
93 The return value is passed to self.runCommand but not used there.
94 """
95 pass
96 34
97def main(argv): 35def main(argv):
98 with open(argv[-1]) as eventfile: 36 with open(argv[-1]) as eventfile:
99 # load variables from the first line 37 # load variables from the first line
100 variables = json.loads(eventfile.readline().strip())['allvariables'] 38 variables = None
101 39 while line := eventfile.readline().strip():
40 try:
41 variables = json.loads(line)['allvariables']
42 break
43 except (KeyError, json.JSONDecodeError):
44 continue
45 if not variables:
46 sys.exit("Cannot find allvariables entry in event log file %s" % argv[-1])
47 eventfile.seek(0)
102 params = namedtuple('ConfigParams', ['observe_only'])(True) 48 params = namedtuple('ConfigParams', ['observe_only'])(True)
103 player = EventPlayer(eventfile, variables) 49 player = eventreplay.EventPlayer(eventfile, variables)
104 50
105 return toasterui.main(player, player, params) 51 return toasterui.main(player, player, params)
106 52