diff options
Diffstat (limited to 'bitbake/bin')
-rwxr-xr-x | bitbake/bin/bitbake | 7 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-diffsigs | 67 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-getvar | 60 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-hashclient | 246 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-hashserv | 147 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-layers | 6 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-prserv | 94 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-selftest | 3 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-server | 17 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-worker | 149 | ||||
-rwxr-xr-x | bitbake/bin/git-make-shallow | 38 | ||||
-rwxr-xr-x | bitbake/bin/toaster | 18 | ||||
-rwxr-xr-x | bitbake/bin/toaster-eventreplay | 82 |
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 | ||
13 | import os | 13 | import os |
14 | import sys | 14 | import sys |
15 | import warnings | ||
16 | warnings.simplefilter("default") | ||
15 | 17 | ||
16 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), | 18 | sys.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: | |||
23 | from bb import cookerdata | 25 | from bb import cookerdata |
24 | from bb.main import bitbake_main, BitBakeConfigParameters, BBMainException | 26 | from bb.main import bitbake_main, BitBakeConfigParameters, BBMainException |
25 | 27 | ||
26 | if sys.getfilesystemencoding() != "utf-8": | 28 | bb.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 | ||
31 | if __name__ == "__main__": | 32 | if __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 @@ | |||
11 | import os | 11 | import os |
12 | import sys | 12 | import sys |
13 | import warnings | 13 | import warnings |
14 | |||
15 | warnings.simplefilter("default") | ||
14 | import argparse | 16 | import argparse |
15 | import logging | 17 | import logging |
16 | import pickle | 18 | import pickle |
@@ -26,6 +28,7 @@ logger = bb.msg.logger_create(myname) | |||
26 | 28 | ||
27 | is_dump = myname == 'bitbake-dumpsig' | 29 | is_dump = myname == 'bitbake-dumpsig' |
28 | 30 | ||
31 | |||
29 | def find_siginfo(tinfoil, pn, taskname, sigs=None): | 32 | def 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 | |||
54 | def find_siginfo_task(bbhandler, pn, taskname, sig1=None, sig2=None): | 58 | def 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 | ||
110 | if is_dump: | 117 | if 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") |
118 | else: | 125 | else: |
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 | ||
143 | options = parser.parse_args() | 150 | options = parser.parse_args() |
144 | if is_dump: | 151 | if 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 | |||
8 | import argparse | ||
9 | import io | ||
10 | import os | ||
11 | import sys | ||
12 | import warnings | ||
13 | warnings.simplefilter("default") | ||
14 | |||
15 | bindir = os.path.dirname(__file__) | ||
16 | topdir = os.path.dirname(bindir) | ||
17 | sys.path[0:0] = [os.path.join(topdir, 'lib')] | ||
18 | |||
19 | import bb.tinfoil | ||
20 | |||
21 | if __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 | |||
13 | import sys | 13 | import sys |
14 | import threading | 14 | import threading |
15 | import time | 15 | import time |
16 | import warnings | ||
17 | import netrc | ||
18 | import json | ||
19 | warnings.simplefilter("default") | ||
16 | 20 | ||
17 | try: | 21 | try: |
18 | import tqdm | 22 | import tqdm |
@@ -34,18 +38,42 @@ except ImportError: | |||
34 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) | 38 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) |
35 | 39 | ||
36 | import hashserv | 40 | import hashserv |
41 | import bb.asyncrpc | ||
37 | 42 | ||
38 | DEFAULT_ADDRESS = 'unix://./hashserve.sock' | 43 | DEFAULT_ADDRESS = 'unix://./hashserve.sock' |
39 | METHOD = 'stress.test.method' | 44 | METHOD = 'stress.test.method' |
40 | 45 | ||
46 | def 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 | ||
42 | def main(): | 54 | def 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 | |||
10 | import logging | 10 | import logging |
11 | import argparse | 11 | import argparse |
12 | import sqlite3 | 12 | import sqlite3 |
13 | import warnings | ||
13 | 14 | ||
14 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) | 15 | warnings.simplefilter("default") |
16 | |||
17 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib")) | ||
15 | 18 | ||
16 | import hashserv | 19 | import hashserv |
20 | from hashserv.server import DEFAULT_ANON_PERMS | ||
17 | 21 | ||
18 | VERSION = "1.0.0" | 22 | VERSION = "1.0.0" |
19 | 23 | ||
20 | DEFAULT_BIND = 'unix://./hashserve.sock' | 24 | DEFAULT_BIND = "unix://./hashserve.sock" |
21 | 25 | ||
22 | 26 | ||
23 | def main(): | 27 | def 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 | 32 | The 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")') | 37 | To 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') | 41 | Note that the default Anonymous permissions are designed to not break existing |
42 | server instances when upgrading, but are not particularly secure defaults. If | ||
43 | you 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 | ||
45 | give un-authenticated users no access at all. | ||
46 | |||
47 | Setting "--anon-perms @all" or "--anon-perms @user-admin" is not allowed, since | ||
48 | this would allow anonymous users to manage all users accounts, which is a bad | ||
49 | idea. | ||
50 | |||
51 | If you are using user authentication, you should run your server in websockets | ||
52 | mode with an SSL terminating load balancer in front of it (as this server does | ||
53 | not implement SSL). Otherwise all usernames and passwords will be transmitted | ||
54 | in the clear. When configured this way, clients can connect using a secure | ||
55 | websocket, as in "wss://SERVER:PORT" | ||
56 | |||
57 | The 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 | ||
57 | if __name__ == '__main__': | 163 | if __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 | |||
14 | import os | 14 | import os |
15 | import sys | 15 | import sys |
16 | import argparse | 16 | import argparse |
17 | import warnings | ||
18 | warnings.simplefilter("default") | ||
17 | 19 | ||
18 | bindir = os.path.dirname(__file__) | 20 | bindir = os.path.dirname(__file__) |
19 | topdir = os.path.dirname(bindir) | 21 | topdir = 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 | ||
6 | import os | 8 | import os |
7 | import sys,logging | 9 | import sys,logging |
8 | import optparse | 10 | import argparse |
11 | import warnings | ||
12 | warnings.simplefilter("default") | ||
9 | 13 | ||
10 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)),'lib')) | 14 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib")) |
11 | 15 | ||
12 | import prserv | 16 | import prserv |
13 | import prserv.serv | 17 | import prserv.serv |
14 | 18 | ||
15 | __version__="1.0.0" | 19 | VERSION = "1.1.0" |
16 | 20 | ||
17 | PRHOST_DEFAULT='0.0.0.0' | 21 | PRHOST_DEFAULT="0.0.0.0" |
18 | PRPORT_DEFAULT=8585 | 22 | PRPORT_DEFAULT=8585 |
19 | 23 | ||
20 | def main(): | 24 | def 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 | ||
8 | import os | 8 | import os |
9 | import sys, logging | 9 | import sys, logging |
10 | import warnings | ||
11 | warnings.simplefilter("default") | ||
10 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) | 12 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) |
11 | 13 | ||
12 | import unittest | 14 | import 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 @@ | |||
8 | import os | 8 | import os |
9 | import sys | 9 | import sys |
10 | import warnings | 10 | import warnings |
11 | warnings.simplefilter("default") | ||
11 | import logging | 12 | import logging |
12 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) | 13 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) |
13 | 14 | ||
14 | if sys.getfilesystemencoding() != "utf-8": | 15 | import 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 | |
17 | bb.utils.check_system_locale() | ||
16 | 18 | ||
17 | # Users shouldn't be running this code directly | 19 | # Users shouldn't be running this code directly |
18 | if len(sys.argv) != 10 or not sys.argv[1].startswith("decafbad"): | 20 | if 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]) | |||
26 | logfile = sys.argv[4] | 28 | logfile = sys.argv[4] |
27 | lockname = sys.argv[5] | 29 | lockname = sys.argv[5] |
28 | sockname = sys.argv[6] | 30 | sockname = sys.argv[6] |
29 | timeout = sys.argv[7] | 31 | timeout = float(sys.argv[7]) |
30 | xmlrpcinterface = (sys.argv[8], int(sys.argv[9])) | 32 | profile = bool(int(sys.argv[8])) |
33 | xmlrpcinterface = (sys.argv[9], int(sys.argv[10])) | ||
31 | if xmlrpcinterface[0] == "None": | 34 | if xmlrpcinterface[0] == "None": |
32 | xmlrpcinterface = (None, xmlrpcinterface[1]) | 35 | xmlrpcinterface = (None, xmlrpcinterface[1]) |
33 | if timeout == "None": | ||
34 | timeout = None | ||
35 | 36 | ||
36 | # Replace standard fds with our own | 37 | # Replace standard fds with our own |
37 | with open('/dev/null', 'r') as si: | 38 | with open('/dev/null', 'r') as si: |
@@ -50,5 +51,5 @@ logger = logging.getLogger("BitBake") | |||
50 | handler = bb.event.LogHandler() | 51 | handler = bb.event.LogHandler() |
51 | logger.addHandler(handler) | 52 | logger.addHandler(handler) |
52 | 53 | ||
53 | bb.server.process.execServer(lockfd, readypipeinfd, lockname, sockname, timeout, xmlrpcinterface) | 54 | bb.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 | ||
6 | import os | 8 | import os |
7 | import sys | 9 | import sys |
8 | import warnings | 10 | import warnings |
11 | warnings.simplefilter("default") | ||
9 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) | 12 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) |
10 | from bb import fetch2 | 13 | from bb import fetch2 |
11 | import logging | 14 | import logging |
@@ -16,11 +19,12 @@ import signal | |||
16 | import pickle | 19 | import pickle |
17 | import traceback | 20 | import traceback |
18 | import queue | 21 | import queue |
22 | import shlex | ||
23 | import subprocess | ||
19 | from multiprocessing import Lock | 24 | from multiprocessing import Lock |
20 | from threading import Thread | 25 | from threading import Thread |
21 | 26 | ||
22 | if sys.getfilesystemencoding() != "utf-8": | 27 | bb.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 |
26 | if len(sys.argv) != 2 or not sys.argv[1].startswith("decafbad"): | 30 | if len(sys.argv) != 2 or not sys.argv[1].startswith("decafbad"): |
@@ -87,19 +91,19 @@ def worker_fire_prepickled(event): | |||
87 | worker_thread_exit = False | 91 | worker_thread_exit = False |
88 | 92 | ||
89 | def worker_flush(worker_queue): | 93 | def 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 | ||
143 | def fork_off_task(cfg, data, databuilder, workerdata, fn, task, taskname, taskhash, unihash, appends, taskdepdata, extraconfigdata, quieterrors=False, dry_run_exec=False): | 146 | def 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)) |
575 | finally: | ||
576 | worker_thread_exit = True | ||
577 | worker_thread.join() | ||
516 | 578 | ||
517 | worker_thread_exit = True | 579 | workerlog_write("exiting") |
518 | worker_thread.join() | 580 | if not normalexit: |
519 | 581 | sys.exit(1) | |
520 | workerlog_write("exitting") | ||
521 | sys.exit(0) | 582 | sys.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 | |||
16 | import os | 18 | import os |
17 | import subprocess | 19 | import subprocess |
18 | import sys | 20 | import sys |
21 | import warnings | ||
22 | warnings.simplefilter("default") | ||
19 | 23 | ||
20 | version = 1.0 | 24 | version = 1.0 |
21 | 25 | ||
22 | 26 | ||
27 | git_cmd = ['git', '-c', 'safe.bareRepository=all'] | ||
28 | |||
23 | def main(): | 29 | def 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 | ||
57 | def process_args(): | 63 | def 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 | ||
92 | def get_all_refs(ref_filter=None): | 98 | def 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 | ||
156 | def shrink_repo(git_dir): | 162 | def 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 | ||
168 | if __name__ == '__main__': | 174 | if __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 | |||
181 | export TOASTER_BUILDSERVER=1 | 181 | export TOASTER_BUILDSERVER=1 |
182 | ADDR_PORT="localhost:8000" | 182 | ADDR_PORT="localhost:8000" |
183 | TOASTERDIR=`dirname $BUILDDIR` | 183 | TOASTERDIR=`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. | ||
187 | TOASTER_LOGS_DIR="${BUILDDIR}/toaster_logs/" | ||
188 | if [ ! -d $TOASTER_LOGS_DIR ] | ||
189 | then | ||
190 | mkdir $TOASTER_LOGS_DIR | ||
191 | fi | ||
184 | unset CMD | 192 | unset CMD |
185 | for param in $*; do | 193 | for 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 |
250 | export TOASTER_DIR=$TOASTERDIR | 258 | export TOASTER_DIR=$TOASTERDIR |
251 | export BB_ENV_EXTRAWHITE="$BB_ENV_EXTRAWHITE TOASTER_DIR" | 259 | export 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 |
254 | if [ "$CMD" = "start" ] ; then | 262 | if [ "$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 | |||
19 | import json | 19 | import json |
20 | import pickle | 20 | import pickle |
21 | import codecs | 21 | import codecs |
22 | import warnings | ||
23 | warnings.simplefilter("default") | ||
22 | 24 | ||
23 | from collections import namedtuple | 25 | from collections import namedtuple |
24 | 26 | ||
@@ -28,79 +30,23 @@ sys.path.insert(0, join(dirname(dirname(abspath(__file__))), 'lib')) | |||
28 | 30 | ||
29 | import bb.cooker | 31 | import bb.cooker |
30 | from bb.ui import toasterui | 32 | from bb.ui import toasterui |
31 | 33 | from bb.ui import eventreplay | |
32 | class 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 | ||
97 | def main(argv): | 35 | def 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 | ||