diff options
Diffstat (limited to 'bitbake/bin')
-rwxr-xr-x | bitbake/bin/bitbake | 2 | ||||
l--------- | bitbake/bin/bitbake-config-build | 1 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-diffsigs | 9 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-getvar | 15 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-hashclient | 107 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-hashserv | 10 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-layers | 26 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-prserv | 26 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-selftest | 5 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-server | 9 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-setup | 860 | ||||
-rwxr-xr-x | bitbake/bin/bitbake-worker | 24 | ||||
-rwxr-xr-x | bitbake/bin/git-make-shallow | 4 |
13 files changed, 1050 insertions, 48 deletions
diff --git a/bitbake/bin/bitbake b/bitbake/bin/bitbake index 8622a7bf94..3acf53229b 100755 --- a/bitbake/bin/bitbake +++ b/bitbake/bin/bitbake | |||
@@ -27,7 +27,7 @@ from bb.main import bitbake_main, BitBakeConfigParameters, BBMainException | |||
27 | 27 | ||
28 | bb.utils.check_system_locale() | 28 | bb.utils.check_system_locale() |
29 | 29 | ||
30 | __version__ = "2.9.1" | 30 | __version__ = "2.15.2" |
31 | 31 | ||
32 | if __name__ == "__main__": | 32 | if __name__ == "__main__": |
33 | if __version__ != bb.__version__: | 33 | if __version__ != bb.__version__: |
diff --git a/bitbake/bin/bitbake-config-build b/bitbake/bin/bitbake-config-build new file mode 120000 index 0000000000..11e6df80c4 --- /dev/null +++ b/bitbake/bin/bitbake-config-build | |||
@@ -0,0 +1 @@ | |||
bitbake-layers \ No newline at end of file | |||
diff --git a/bitbake/bin/bitbake-diffsigs b/bitbake/bin/bitbake-diffsigs index 8202c78623..9d6cb8c944 100755 --- a/bitbake/bin/bitbake-diffsigs +++ b/bitbake/bin/bitbake-diffsigs | |||
@@ -72,16 +72,17 @@ def find_siginfo_task(bbhandler, pn, taskname, sig1=None, sig2=None): | |||
72 | elif sig2 not in sigfiles: | 72 | elif sig2 not in sigfiles: |
73 | 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)) |
74 | sys.exit(1) | 74 | sys.exit(1) |
75 | |||
76 | latestfiles = [sigfiles[sig1]['path'], sigfiles[sig2]['path']] | ||
75 | else: | 77 | else: |
76 | sigfiles = find_siginfo(bbhandler, pn, taskname) | 78 | sigfiles = find_siginfo(bbhandler, pn, taskname) |
77 | latestsigs = sorted(sigfiles.keys(), key=lambda h: sigfiles[h]['time'])[-2:] | 79 | latestsigs = sorted(sigfiles.keys(), key=lambda h: sigfiles[h]['time'])[-2:] |
78 | if not latestsigs: | 80 | if not latestsigs: |
79 | logger.error('No sigdata files found matching %s %s' % (pn, taskname)) | 81 | logger.error('No sigdata files found matching %s %s' % (pn, taskname)) |
80 | sys.exit(1) | 82 | sys.exit(1) |
81 | sig1 = latestsigs[0] | 83 | latestfiles = [sigfiles[latestsigs[0]]['path']] |
82 | sig2 = latestsigs[1] | 84 | if len(latestsigs) > 1: |
83 | 85 | latestfiles.append(sigfiles[latestsigs[1]]['path']) | |
84 | latestfiles = [sigfiles[sig1]['path'], sigfiles[sig2]['path']] | ||
85 | 86 | ||
86 | return latestfiles | 87 | return latestfiles |
87 | 88 | ||
diff --git a/bitbake/bin/bitbake-getvar b/bitbake/bin/bitbake-getvar index 8901f99ae2..378fb13572 100755 --- a/bitbake/bin/bitbake-getvar +++ b/bitbake/bin/bitbake-getvar | |||
@@ -10,12 +10,14 @@ import io | |||
10 | import os | 10 | import os |
11 | import sys | 11 | import sys |
12 | import warnings | 12 | import warnings |
13 | import logging | ||
13 | warnings.simplefilter("default") | 14 | warnings.simplefilter("default") |
14 | 15 | ||
15 | bindir = os.path.dirname(__file__) | 16 | bindir = os.path.dirname(__file__) |
16 | topdir = os.path.dirname(bindir) | 17 | topdir = os.path.dirname(bindir) |
17 | sys.path[0:0] = [os.path.join(topdir, 'lib')] | 18 | sys.path[0:0] = [os.path.join(topdir, 'lib')] |
18 | 19 | ||
20 | import bb.providers | ||
19 | import bb.tinfoil | 21 | import bb.tinfoil |
20 | 22 | ||
21 | if __name__ == "__main__": | 23 | if __name__ == "__main__": |
@@ -37,13 +39,22 @@ if __name__ == "__main__": | |||
37 | sys.exit("--flag only makes sense with --value") | 39 | sys.exit("--flag only makes sense with --value") |
38 | 40 | ||
39 | quiet = args.quiet or args.value | 41 | quiet = args.quiet or args.value |
42 | if quiet: | ||
43 | logger = logging.getLogger("BitBake") | ||
44 | logger.setLevel(logging.WARNING) | ||
45 | |||
40 | with bb.tinfoil.Tinfoil(tracking=True, setup_logging=not quiet) as tinfoil: | 46 | with bb.tinfoil.Tinfoil(tracking=True, setup_logging=not quiet) as tinfoil: |
41 | if args.recipe: | 47 | if args.recipe: |
42 | tinfoil.prepare(quiet=3 if quiet else 2) | 48 | tinfoil.prepare(quiet=3 if quiet else 2) |
43 | d = tinfoil.parse_recipe(args.recipe) | 49 | try: |
50 | d = tinfoil.parse_recipe(args.recipe) | ||
51 | except bb.providers.NoProvider as e: | ||
52 | sys.exit(str(e)) | ||
44 | else: | 53 | else: |
45 | tinfoil.prepare(quiet=2, config_only=True) | 54 | tinfoil.prepare(quiet=2, config_only=True) |
46 | d = tinfoil.config_data | 55 | # Expand keys and run anonymous functions to get identical result to |
56 | # "bitbake -e" | ||
57 | d = tinfoil.finalizeData() | ||
47 | 58 | ||
48 | value = None | 59 | value = None |
49 | if args.flag: | 60 | if args.flag: |
diff --git a/bitbake/bin/bitbake-hashclient b/bitbake/bin/bitbake-hashclient index 610787ed2b..b8755c5797 100755 --- a/bitbake/bin/bitbake-hashclient +++ b/bitbake/bin/bitbake-hashclient | |||
@@ -16,6 +16,8 @@ import time | |||
16 | import warnings | 16 | import warnings |
17 | import netrc | 17 | import netrc |
18 | import json | 18 | import json |
19 | import statistics | ||
20 | import textwrap | ||
19 | warnings.simplefilter("default") | 21 | warnings.simplefilter("default") |
20 | 22 | ||
21 | try: | 23 | try: |
@@ -81,6 +83,7 @@ def main(): | |||
81 | nonlocal found_hashes | 83 | nonlocal found_hashes |
82 | nonlocal missed_hashes | 84 | nonlocal missed_hashes |
83 | nonlocal max_time | 85 | nonlocal max_time |
86 | nonlocal times | ||
84 | 87 | ||
85 | with hashserv.create_client(args.address) as client: | 88 | with hashserv.create_client(args.address) as client: |
86 | for i in range(args.requests): | 89 | for i in range(args.requests): |
@@ -98,29 +101,41 @@ def main(): | |||
98 | else: | 101 | else: |
99 | missed_hashes += 1 | 102 | missed_hashes += 1 |
100 | 103 | ||
101 | max_time = max(elapsed, max_time) | 104 | times.append(elapsed) |
102 | pbar.update() | 105 | pbar.update() |
103 | 106 | ||
104 | max_time = 0 | 107 | max_time = 0 |
105 | found_hashes = 0 | 108 | found_hashes = 0 |
106 | missed_hashes = 0 | 109 | missed_hashes = 0 |
107 | lock = threading.Lock() | 110 | lock = threading.Lock() |
108 | total_requests = args.clients * args.requests | 111 | times = [] |
109 | start_time = time.perf_counter() | 112 | start_time = time.perf_counter() |
110 | with ProgressBar(total=total_requests) as pbar: | 113 | with ProgressBar(total=args.clients * args.requests) as pbar: |
111 | threads = [threading.Thread(target=thread_main, args=(pbar, lock), daemon=False) for _ in range(args.clients)] | 114 | threads = [threading.Thread(target=thread_main, args=(pbar, lock), daemon=False) for _ in range(args.clients)] |
112 | for t in threads: | 115 | for t in threads: |
113 | t.start() | 116 | t.start() |
114 | 117 | ||
115 | for t in threads: | 118 | for t in threads: |
116 | t.join() | 119 | t.join() |
120 | total_elapsed = time.perf_counter() - start_time | ||
117 | 121 | ||
118 | elapsed = time.perf_counter() - start_time | ||
119 | with lock: | 122 | with lock: |
120 | print("%d requests in %.1fs. %.1f requests per second" % (total_requests, elapsed, total_requests / elapsed)) | 123 | mean = statistics.mean(times) |
121 | print("Average request time %.8fs" % (elapsed / total_requests)) | 124 | median = statistics.median(times) |
122 | print("Max request time was %.8fs" % max_time) | 125 | stddev = statistics.pstdev(times) |
123 | print("Found %d hashes, missed %d" % (found_hashes, missed_hashes)) | 126 | |
127 | print(f"Number of clients: {args.clients}") | ||
128 | print(f"Requests per client: {args.requests}") | ||
129 | print(f"Number of requests: {len(times)}") | ||
130 | print(f"Total elapsed time: {total_elapsed:.3f}s") | ||
131 | print(f"Total request rate: {len(times)/total_elapsed:.3f} req/s") | ||
132 | print(f"Average request time: {mean:.3f}s") | ||
133 | print(f"Median request time: {median:.3f}s") | ||
134 | print(f"Request time std dev: {stddev:.3f}s") | ||
135 | print(f"Maximum request time: {max(times):.3f}s") | ||
136 | print(f"Minimum request time: {min(times):.3f}s") | ||
137 | print(f"Hashes found: {found_hashes}") | ||
138 | print(f"Hashes missed: {missed_hashes}") | ||
124 | 139 | ||
125 | if args.report: | 140 | if args.report: |
126 | with ProgressBar(total=args.requests) as pbar: | 141 | with ProgressBar(total=args.requests) as pbar: |
@@ -212,6 +227,27 @@ def main(): | |||
212 | print("New hashes marked: %d" % result["count"]) | 227 | print("New hashes marked: %d" % result["count"]) |
213 | return 0 | 228 | return 0 |
214 | 229 | ||
230 | def handle_gc_mark_stream(args, client): | ||
231 | stdin = (l.strip() for l in sys.stdin) | ||
232 | marked_hashes = 0 | ||
233 | |||
234 | try: | ||
235 | result = client.gc_mark_stream(args.mark, stdin) | ||
236 | marked_hashes = result["count"] | ||
237 | except ConnectionError: | ||
238 | logger.warning( | ||
239 | "Server doesn't seem to support `gc-mark-stream`. Sending " | ||
240 | "hashes sequentially using `gc-mark` API." | ||
241 | ) | ||
242 | for line in stdin: | ||
243 | pairs = line.split() | ||
244 | condition = dict(zip(pairs[::2], pairs[1::2])) | ||
245 | result = client.gc_mark(args.mark, condition) | ||
246 | marked_hashes += result["count"] | ||
247 | |||
248 | print("New hashes marked: %d" % marked_hashes) | ||
249 | return 0 | ||
250 | |||
215 | def handle_gc_sweep(args, client): | 251 | def handle_gc_sweep(args, client): |
216 | result = client.gc_sweep(args.mark) | 252 | result = client.gc_sweep(args.mark) |
217 | print("Removed %d rows" % result["count"]) | 253 | print("Removed %d rows" % result["count"]) |
@@ -225,7 +261,45 @@ def main(): | |||
225 | print("true" if result else "false") | 261 | print("true" if result else "false") |
226 | return 0 | 262 | return 0 |
227 | 263 | ||
228 | parser = argparse.ArgumentParser(description='Hash Equivalence Client') | 264 | def handle_ping(args, client): |
265 | times = [] | ||
266 | for i in range(1, args.count + 1): | ||
267 | if not args.quiet: | ||
268 | print(f"Ping {i} of {args.count}... ", end="") | ||
269 | start_time = time.perf_counter() | ||
270 | client.ping() | ||
271 | elapsed = time.perf_counter() - start_time | ||
272 | times.append(elapsed) | ||
273 | if not args.quiet: | ||
274 | print(f"{elapsed:.3f}s") | ||
275 | |||
276 | mean = statistics.mean(times) | ||
277 | median = statistics.median(times) | ||
278 | std_dev = statistics.pstdev(times) | ||
279 | |||
280 | if not args.quiet: | ||
281 | print("------------------------") | ||
282 | print(f"Number of pings: {len(times)}") | ||
283 | print(f"Average round trip time: {mean:.3f}s") | ||
284 | print(f"Median round trip time: {median:.3f}s") | ||
285 | print(f"Round trip time std dev: {std_dev:.3f}s") | ||
286 | print(f"Min time is: {min(times):.3f}s") | ||
287 | print(f"Max time is: {max(times):.3f}s") | ||
288 | return 0 | ||
289 | |||
290 | parser = argparse.ArgumentParser( | ||
291 | formatter_class=argparse.RawDescriptionHelpFormatter, | ||
292 | description='Hash Equivalence Client', | ||
293 | epilog=textwrap.dedent( | ||
294 | """ | ||
295 | Possible ADDRESS options are: | ||
296 | unix://PATH Connect to UNIX domain socket at PATH | ||
297 | ws://HOST[:PORT] Connect to websocket at HOST:PORT (default port is 80) | ||
298 | wss://HOST[:PORT] Connect to secure websocket at HOST:PORT (default port is 443) | ||
299 | HOST:PORT Connect to TCP server at HOST:PORT | ||
300 | """ | ||
301 | ), | ||
302 | ) | ||
229 | parser.add_argument('--address', default=DEFAULT_ADDRESS, help='Server address (default "%(default)s")') | 303 | parser.add_argument('--address', default=DEFAULT_ADDRESS, help='Server address (default "%(default)s")') |
230 | parser.add_argument('--log', default='WARNING', help='Set logging level') | 304 | parser.add_argument('--log', default='WARNING', help='Set logging level') |
231 | parser.add_argument('--login', '-l', metavar="USERNAME", help="Authenticate as USERNAME") | 305 | parser.add_argument('--login', '-l', metavar="USERNAME", help="Authenticate as USERNAME") |
@@ -313,6 +387,16 @@ def main(): | |||
313 | help="Keep entries in table where KEY == VALUE") | 387 | help="Keep entries in table where KEY == VALUE") |
314 | gc_mark_parser.set_defaults(func=handle_gc_mark) | 388 | gc_mark_parser.set_defaults(func=handle_gc_mark) |
315 | 389 | ||
390 | gc_mark_parser_stream = subparsers.add_parser( | ||
391 | 'gc-mark-stream', | ||
392 | help=( | ||
393 | "Mark multiple hashes to be retained for garbage collection. Input should be provided via stdin, " | ||
394 | "with each line formatted as key-value pairs separated by spaces, for example 'column1 foo column2 bar'." | ||
395 | ) | ||
396 | ) | ||
397 | gc_mark_parser_stream.add_argument("mark", help="Mark for this garbage collection operation") | ||
398 | gc_mark_parser_stream.set_defaults(func=handle_gc_mark_stream) | ||
399 | |||
316 | gc_sweep_parser = subparsers.add_parser('gc-sweep', help="Perform garbage collection and delete any entries that are not marked") | 400 | 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") | 401 | gc_sweep_parser.add_argument("mark", help="Mark for this garbage collection operation") |
318 | gc_sweep_parser.set_defaults(func=handle_gc_sweep) | 402 | gc_sweep_parser.set_defaults(func=handle_gc_sweep) |
@@ -322,6 +406,11 @@ def main(): | |||
322 | unihash_exists_parser.add_argument("unihash", help="Unihash to check") | 406 | unihash_exists_parser.add_argument("unihash", help="Unihash to check") |
323 | unihash_exists_parser.set_defaults(func=handle_unihash_exists) | 407 | unihash_exists_parser.set_defaults(func=handle_unihash_exists) |
324 | 408 | ||
409 | ping_parser = subparsers.add_parser('ping', help="Ping server") | ||
410 | ping_parser.add_argument("-n", "--count", type=int, help="Number of pings. Default is %(default)s", default=10) | ||
411 | ping_parser.add_argument("-q", "--quiet", action="store_true", help="Don't print each ping; only print results") | ||
412 | ping_parser.set_defaults(func=handle_ping) | ||
413 | |||
325 | args = parser.parse_args() | 414 | args = parser.parse_args() |
326 | 415 | ||
327 | logger = logging.getLogger('hashserv') | 416 | logger = logging.getLogger('hashserv') |
diff --git a/bitbake/bin/bitbake-hashserv b/bitbake/bin/bitbake-hashserv index 4bfb7abfbc..01503736b9 100755 --- a/bitbake/bin/bitbake-hashserv +++ b/bitbake/bin/bitbake-hashserv | |||
@@ -125,6 +125,11 @@ The following permissions are supported by the server: | |||
125 | default=os.environ.get("HASHSERVER_ADMIN_PASSWORD", None), | 125 | default=os.environ.get("HASHSERVER_ADMIN_PASSWORD", None), |
126 | help="Create default admin user with password ADMIN_PASSWORD ($HASHSERVER_ADMIN_PASSWORD)", | 126 | help="Create default admin user with password ADMIN_PASSWORD ($HASHSERVER_ADMIN_PASSWORD)", |
127 | ) | 127 | ) |
128 | parser.add_argument( | ||
129 | "--reuseport", | ||
130 | action="store_true", | ||
131 | help="Enable SO_REUSEPORT, allowing multiple servers to bind to the same port for load balancing", | ||
132 | ) | ||
128 | 133 | ||
129 | args = parser.parse_args() | 134 | args = parser.parse_args() |
130 | 135 | ||
@@ -132,7 +137,9 @@ The following permissions are supported by the server: | |||
132 | 137 | ||
133 | level = getattr(logging, args.log.upper(), None) | 138 | level = getattr(logging, args.log.upper(), None) |
134 | if not isinstance(level, int): | 139 | if not isinstance(level, int): |
135 | raise ValueError("Invalid log level: %s (Try ERROR/WARNING/INFO/DEBUG)" % args.log) | 140 | raise ValueError( |
141 | "Invalid log level: %s (Try ERROR/WARNING/INFO/DEBUG)" % args.log | ||
142 | ) | ||
136 | 143 | ||
137 | logger.setLevel(level) | 144 | logger.setLevel(level) |
138 | console = logging.StreamHandler() | 145 | console = logging.StreamHandler() |
@@ -155,6 +162,7 @@ The following permissions are supported by the server: | |||
155 | anon_perms=anon_perms, | 162 | anon_perms=anon_perms, |
156 | admin_username=args.admin_user, | 163 | admin_username=args.admin_user, |
157 | admin_password=args.admin_password, | 164 | admin_password=args.admin_password, |
165 | reuseport=args.reuseport, | ||
158 | ) | 166 | ) |
159 | server.serve_forever() | 167 | server.serve_forever() |
160 | return 0 | 168 | return 0 |
diff --git a/bitbake/bin/bitbake-layers b/bitbake/bin/bitbake-layers index aebb5100c2..341ecbcd97 100755 --- a/bitbake/bin/bitbake-layers +++ b/bitbake/bin/bitbake-layers | |||
@@ -18,13 +18,14 @@ import warnings | |||
18 | warnings.simplefilter("default") | 18 | warnings.simplefilter("default") |
19 | 19 | ||
20 | bindir = os.path.dirname(__file__) | 20 | bindir = os.path.dirname(__file__) |
21 | toolname = os.path.basename(__file__).split(".")[0] | ||
21 | topdir = os.path.dirname(bindir) | 22 | topdir = os.path.dirname(bindir) |
22 | sys.path[0:0] = [os.path.join(topdir, 'lib')] | 23 | sys.path[0:0] = [os.path.join(topdir, 'lib')] |
23 | 24 | ||
24 | import bb.tinfoil | 25 | import bb.tinfoil |
25 | import bb.msg | 26 | import bb.msg |
26 | 27 | ||
27 | logger = bb.msg.logger_create('bitbake-layers', sys.stdout) | 28 | logger = bb.msg.logger_create(toolname, sys.stdout) |
28 | 29 | ||
29 | def main(): | 30 | def main(): |
30 | parser = argparse.ArgumentParser( | 31 | parser = argparse.ArgumentParser( |
@@ -57,17 +58,18 @@ def main(): | |||
57 | level=logger.getEffectiveLevel()) | 58 | level=logger.getEffectiveLevel()) |
58 | 59 | ||
59 | plugins = [] | 60 | plugins = [] |
60 | tinfoil = bb.tinfoil.Tinfoil(tracking=True) | 61 | with bb.tinfoil.Tinfoil(tracking=True) as tinfoil: |
61 | tinfoil.logger.setLevel(logger.getEffectiveLevel()) | 62 | tinfoil.logger.setLevel(logger.getEffectiveLevel()) |
62 | if global_args.force > 1: | 63 | |
63 | bbpaths = [] | 64 | if global_args.force > 1: |
64 | else: | 65 | bbpaths = [] |
65 | tinfoil.prepare(True) | 66 | else: |
66 | bbpaths = tinfoil.config_data.getVar('BBPATH').split(':') | 67 | tinfoil.prepare(True) |
67 | 68 | bbpaths = tinfoil.config_data.getVar('BBPATH').split(':') | |
68 | try: | 69 | |
69 | for path in ([topdir] + bbpaths): | 70 | for path in ([topdir] + bbpaths): |
70 | pluginpath = os.path.join(path, 'lib', 'bblayers') | 71 | pluginbasepath = {"bitbake-layers":'bblayers', 'bitbake-config-build':'bbconfigbuild'}[toolname] |
72 | pluginpath = os.path.join(path, 'lib', pluginbasepath) | ||
71 | bb.utils.load_plugins(logger, plugins, pluginpath) | 73 | bb.utils.load_plugins(logger, plugins, pluginpath) |
72 | 74 | ||
73 | registered = False | 75 | registered = False |
@@ -90,8 +92,6 @@ def main(): | |||
90 | tinfoil.config_data.enableTracking() | 92 | tinfoil.config_data.enableTracking() |
91 | 93 | ||
92 | return args.func(args) | 94 | return args.func(args) |
93 | finally: | ||
94 | tinfoil.shutdown() | ||
95 | 95 | ||
96 | 96 | ||
97 | if __name__ == "__main__": | 97 | if __name__ == "__main__": |
diff --git a/bitbake/bin/bitbake-prserv b/bitbake/bin/bitbake-prserv index ad0a069401..3992e84eab 100755 --- a/bitbake/bin/bitbake-prserv +++ b/bitbake/bin/bitbake-prserv | |||
@@ -16,11 +16,18 @@ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib | |||
16 | import prserv | 16 | import prserv |
17 | import prserv.serv | 17 | import prserv.serv |
18 | 18 | ||
19 | VERSION = "1.1.0" | 19 | VERSION = "2.0.0" |
20 | 20 | ||
21 | PRHOST_DEFAULT="0.0.0.0" | 21 | PRHOST_DEFAULT="0.0.0.0" |
22 | PRPORT_DEFAULT=8585 | 22 | PRPORT_DEFAULT=8585 |
23 | 23 | ||
24 | def init_logger(logfile, loglevel): | ||
25 | numeric_level = getattr(logging, loglevel.upper(), None) | ||
26 | if not isinstance(numeric_level, int): | ||
27 | raise ValueError("Invalid log level: %s" % loglevel) | ||
28 | FORMAT = "%(asctime)-15s %(message)s" | ||
29 | logging.basicConfig(level=numeric_level, filename=logfile, format=FORMAT) | ||
30 | |||
24 | def main(): | 31 | def main(): |
25 | parser = argparse.ArgumentParser( | 32 | parser = argparse.ArgumentParser( |
26 | description="BitBake PR Server. Version=%s" % VERSION, | 33 | description="BitBake PR Server. Version=%s" % VERSION, |
@@ -70,12 +77,25 @@ def main(): | |||
70 | action="store_true", | 77 | action="store_true", |
71 | help="open database in read-only mode", | 78 | help="open database in read-only mode", |
72 | ) | 79 | ) |
80 | parser.add_argument( | ||
81 | "-u", | ||
82 | "--upstream", | ||
83 | default=os.environ.get("PRSERV_UPSTREAM", None), | ||
84 | help="Upstream PR service (host:port)", | ||
85 | ) | ||
73 | 86 | ||
74 | args = parser.parse_args() | 87 | args = parser.parse_args() |
75 | prserv.init_logger(os.path.abspath(args.log), args.loglevel) | 88 | init_logger(os.path.abspath(args.log), args.loglevel) |
76 | 89 | ||
77 | if args.start: | 90 | if args.start: |
78 | ret=prserv.serv.start_daemon(args.file, args.host, args.port, os.path.abspath(args.log), args.read_only) | 91 | ret=prserv.serv.start_daemon( |
92 | args.file, | ||
93 | args.host, | ||
94 | args.port, | ||
95 | os.path.abspath(args.log), | ||
96 | args.read_only, | ||
97 | args.upstream | ||
98 | ) | ||
79 | elif args.stop: | 99 | elif args.stop: |
80 | ret=prserv.serv.stop_daemon(args.host, args.port) | 100 | ret=prserv.serv.stop_daemon(args.host, args.port) |
81 | else: | 101 | else: |
diff --git a/bitbake/bin/bitbake-selftest b/bitbake/bin/bitbake-selftest index f25f23b1ae..fb7c57dd83 100755 --- a/bitbake/bin/bitbake-selftest +++ b/bitbake/bin/bitbake-selftest | |||
@@ -15,6 +15,7 @@ import unittest | |||
15 | try: | 15 | try: |
16 | import bb | 16 | import bb |
17 | import hashserv | 17 | import hashserv |
18 | import prserv | ||
18 | import layerindexlib | 19 | import layerindexlib |
19 | except RuntimeError as exc: | 20 | except RuntimeError as exc: |
20 | sys.exit(str(exc)) | 21 | sys.exit(str(exc)) |
@@ -27,12 +28,14 @@ tests = ["bb.tests.codeparser", | |||
27 | "bb.tests.event", | 28 | "bb.tests.event", |
28 | "bb.tests.fetch", | 29 | "bb.tests.fetch", |
29 | "bb.tests.parse", | 30 | "bb.tests.parse", |
30 | "bb.tests.persist_data", | ||
31 | "bb.tests.runqueue", | 31 | "bb.tests.runqueue", |
32 | "bb.tests.setup", | ||
32 | "bb.tests.siggen", | 33 | "bb.tests.siggen", |
33 | "bb.tests.utils", | 34 | "bb.tests.utils", |
34 | "bb.tests.compression", | 35 | "bb.tests.compression", |
36 | "bb.tests.filter", | ||
35 | "hashserv.tests", | 37 | "hashserv.tests", |
38 | "prserv.tests", | ||
36 | "layerindexlib.tests.layerindexobj", | 39 | "layerindexlib.tests.layerindexobj", |
37 | "layerindexlib.tests.restapi", | 40 | "layerindexlib.tests.restapi", |
38 | "layerindexlib.tests.cooker"] | 41 | "layerindexlib.tests.cooker"] |
diff --git a/bitbake/bin/bitbake-server b/bitbake/bin/bitbake-server index 454a3919aa..01f83d982f 100755 --- a/bitbake/bin/bitbake-server +++ b/bitbake/bin/bitbake-server | |||
@@ -9,6 +9,7 @@ import os | |||
9 | import sys | 9 | import sys |
10 | import warnings | 10 | import warnings |
11 | warnings.simplefilter("default") | 11 | warnings.simplefilter("default") |
12 | warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*use.of.fork.*may.lead.to.deadlocks.in.the.child.*") | ||
12 | import logging | 13 | import logging |
13 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) | 14 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) |
14 | 15 | ||
@@ -29,7 +30,7 @@ logfile = sys.argv[4] | |||
29 | lockname = sys.argv[5] | 30 | lockname = sys.argv[5] |
30 | sockname = sys.argv[6] | 31 | sockname = sys.argv[6] |
31 | timeout = float(sys.argv[7]) | 32 | timeout = float(sys.argv[7]) |
32 | profile = bool(int(sys.argv[8])) | 33 | profile = sys.argv[8] |
33 | xmlrpcinterface = (sys.argv[9], int(sys.argv[10])) | 34 | xmlrpcinterface = (sys.argv[9], int(sys.argv[10])) |
34 | if xmlrpcinterface[0] == "None": | 35 | if xmlrpcinterface[0] == "None": |
35 | xmlrpcinterface = (None, xmlrpcinterface[1]) | 36 | xmlrpcinterface = (None, xmlrpcinterface[1]) |
@@ -38,9 +39,9 @@ if xmlrpcinterface[0] == "None": | |||
38 | with open('/dev/null', 'r') as si: | 39 | with open('/dev/null', 'r') as si: |
39 | os.dup2(si.fileno(), sys.stdin.fileno()) | 40 | os.dup2(si.fileno(), sys.stdin.fileno()) |
40 | 41 | ||
41 | so = open(logfile, 'a+') | 42 | with open(logfile, 'a+') as so: |
42 | os.dup2(so.fileno(), sys.stdout.fileno()) | 43 | os.dup2(so.fileno(), sys.stdout.fileno()) |
43 | os.dup2(so.fileno(), sys.stderr.fileno()) | 44 | os.dup2(so.fileno(), sys.stderr.fileno()) |
44 | 45 | ||
45 | # Have stdout and stderr be the same so log output matches chronologically | 46 | # Have stdout and stderr be the same so log output matches chronologically |
46 | # and there aren't two seperate buffers | 47 | # and there aren't two seperate buffers |
diff --git a/bitbake/bin/bitbake-setup b/bitbake/bin/bitbake-setup new file mode 100755 index 0000000000..38bb8099fd --- /dev/null +++ b/bitbake/bin/bitbake-setup | |||
@@ -0,0 +1,860 @@ | |||
1 | #!/usr/bin/env python3 | ||
2 | |||
3 | # | ||
4 | # SPDX-License-Identifier: GPL-2.0-only | ||
5 | # | ||
6 | |||
7 | import logging | ||
8 | import os | ||
9 | import sys | ||
10 | import argparse | ||
11 | import warnings | ||
12 | import json | ||
13 | import shutil | ||
14 | import time | ||
15 | import stat | ||
16 | import tempfile | ||
17 | import configparser | ||
18 | import datetime | ||
19 | import glob | ||
20 | import subprocess | ||
21 | |||
22 | default_registry = os.path.normpath(os.path.dirname(__file__) + "/../default-registry") | ||
23 | |||
24 | bindir = os.path.abspath(os.path.dirname(__file__)) | ||
25 | sys.path[0:0] = [os.path.join(os.path.dirname(bindir), 'lib')] | ||
26 | |||
27 | import bb.msg | ||
28 | import bb.process | ||
29 | |||
30 | logger = bb.msg.logger_create('bitbake-setup', sys.stdout) | ||
31 | |||
32 | def cache_dir(top_dir): | ||
33 | return os.path.join(top_dir, '.bitbake-setup-cache') | ||
34 | |||
35 | def init_bb_cache(top_dir, settings, args): | ||
36 | dldir = settings["default"]["dl-dir"] | ||
37 | bb_cachedir = os.path.join(cache_dir(top_dir), 'bitbake-cache') | ||
38 | |||
39 | d = bb.data.init() | ||
40 | d.setVar("DL_DIR", dldir) | ||
41 | d.setVar("BB_CACHEDIR", bb_cachedir) | ||
42 | d.setVar("__BBSRCREV_SEEN", "1") | ||
43 | if args.no_network: | ||
44 | d.setVar("BB_SRCREV_POLICY", "cache") | ||
45 | bb.fetch.fetcher_init(d) | ||
46 | return d | ||
47 | |||
48 | def save_bb_cache(): | ||
49 | bb.fetch2.fetcher_parse_save() | ||
50 | bb.fetch2.fetcher_parse_done() | ||
51 | |||
52 | def get_config_name(config): | ||
53 | suffix = '.conf.json' | ||
54 | config_file = os.path.basename(config) | ||
55 | if config_file.endswith(suffix): | ||
56 | return config_file[:-len(suffix)] | ||
57 | else: | ||
58 | raise Exception("Config file {} does not end with {}, please rename the file.".format(config, suffix)) | ||
59 | |||
60 | def write_config(config, config_dir): | ||
61 | with open(os.path.join(config_dir, "config-upstream.json"),'w') as s: | ||
62 | json.dump(config, s, sort_keys=True, indent=4) | ||
63 | |||
64 | def commit_config(config_dir): | ||
65 | bb.process.run("git -C {} add .".format(config_dir)) | ||
66 | bb.process.run("git -C {} commit --no-verify -a -m 'Configuration at {}'".format(config_dir, time.asctime())) | ||
67 | |||
68 | def _write_layer_list(dest, repodirs): | ||
69 | layers = [] | ||
70 | for r in repodirs: | ||
71 | for root, dirs, files in os.walk(os.path.join(dest,r)): | ||
72 | if os.path.basename(root) == 'conf' and 'layer.conf' in files: | ||
73 | layers.append(os.path.relpath(os.path.dirname(root), dest)) | ||
74 | layers_f = os.path.join(dest, ".oe-layers.json") | ||
75 | with open(layers_f, 'w') as f: | ||
76 | json.dump({"version":"1.0","layers":layers}, f, sort_keys=True, indent=4) | ||
77 | |||
78 | def checkout_layers(layers, layerdir, d): | ||
79 | repodirs = [] | ||
80 | oesetupbuild = None | ||
81 | print("Fetching layer/tool repositories into {}".format(layerdir)) | ||
82 | for r_name in layers: | ||
83 | r_data = layers[r_name] | ||
84 | repodir = r_data["path"] | ||
85 | repodirs.append(repodir) | ||
86 | |||
87 | r_remote = r_data['git-remote'] | ||
88 | rev = r_remote['rev'] | ||
89 | branch = r_remote.get('branch', None) | ||
90 | remotes = r_remote['remotes'] | ||
91 | |||
92 | for remote in remotes: | ||
93 | type,host,path,user,pswd,params = bb.fetch.decodeurl(remotes[remote]["uri"]) | ||
94 | fetchuri = bb.fetch.encodeurl(('git',host,path,user,pswd,params)) | ||
95 | print(" {}".format(r_name)) | ||
96 | if branch: | ||
97 | fetcher = bb.fetch.Fetch(["{};protocol={};rev={};branch={};destsuffix={}".format(fetchuri,type,rev,branch,repodir)], d) | ||
98 | else: | ||
99 | fetcher = bb.fetch.Fetch(["{};protocol={};rev={};nobranch=1;destsuffix={}".format(fetchuri,type,rev,repodir)], d) | ||
100 | do_fetch(fetcher, layerdir) | ||
101 | |||
102 | if os.path.exists(os.path.join(layerdir, repodir, 'scripts/oe-setup-build')): | ||
103 | oesetupbuild = os.path.join(layerdir, repodir, 'scripts/oe-setup-build') | ||
104 | oeinitbuildenvdir = os.path.join(layerdir, repodir) | ||
105 | |||
106 | print(" ") | ||
107 | _write_layer_list(layerdir, repodirs) | ||
108 | |||
109 | if oesetupbuild: | ||
110 | links = {'setup-build': oesetupbuild, 'oe-scripts': os.path.dirname(oesetupbuild), 'oe-init-build-env-dir': oeinitbuildenvdir} | ||
111 | for l,t in links.items(): | ||
112 | symlink = os.path.join(layerdir, l) | ||
113 | if os.path.lexists(symlink): | ||
114 | os.remove(symlink) | ||
115 | os.symlink(os.path.relpath(t,layerdir),symlink) | ||
116 | |||
117 | def setup_bitbake_build(bitbake_config, layerdir, builddir, thisdir): | ||
118 | def _setup_build_conf(layers, build_conf_dir): | ||
119 | os.makedirs(build_conf_dir) | ||
120 | layers_s = [] | ||
121 | for l in layers: | ||
122 | if l.startswith("{THISDIR}/"): | ||
123 | if thisdir: | ||
124 | l = l.format(THISDIR=thisdir) | ||
125 | else: | ||
126 | raise Exception("Configuration is using {THISDIR} to specify " \ | ||
127 | "a layer path relative to itself. This can be done only " \ | ||
128 | "when the configuration is specified by its path on local " \ | ||
129 | "disk, not when it's in a registry or is fetched over http.") | ||
130 | if not os.path.isabs(l): | ||
131 | l = os.path.join(layerdir, l) | ||
132 | layers_s.append(" {} \\".format(l)) | ||
133 | layers_s = "\n".join(layers_s) | ||
134 | bblayers_conf = """BBLAYERS ?= " \\ | ||
135 | {} | ||
136 | " | ||
137 | """.format(layers_s) | ||
138 | with open(os.path.join(build_conf_dir, "bblayers.conf"), 'w') as f: | ||
139 | f.write(bblayers_conf) | ||
140 | |||
141 | local_conf = """# | ||
142 | # This file is intended for local configuration tweaks. | ||
143 | # | ||
144 | # If you would like to publish and share changes made to this file, | ||
145 | # it is recommended to put them into a distro config, or to create | ||
146 | # layer fragments from changes made here. | ||
147 | # | ||
148 | """ | ||
149 | with open(os.path.join(build_conf_dir, "local.conf"), 'w') as f: | ||
150 | f.write(local_conf) | ||
151 | |||
152 | with open(os.path.join(build_conf_dir, "templateconf.cfg"), 'w') as f: | ||
153 | f.write("") | ||
154 | |||
155 | with open(os.path.join(build_conf_dir, "conf-summary.txt"), 'w') as f: | ||
156 | f.write(bitbake_config["description"] + "\n") | ||
157 | |||
158 | with open(os.path.join(build_conf_dir, "conf-notes.txt"), 'w') as f: | ||
159 | f.write("") | ||
160 | |||
161 | def _make_init_build_env(builddir, oeinitbuildenvdir): | ||
162 | builddir = os.path.realpath(builddir) | ||
163 | cmd = "cd {}\nset {}\n. ./oe-init-build-env\n".format(oeinitbuildenvdir, builddir) | ||
164 | initbuild_in_builddir = os.path.join(builddir, 'init-build-env') | ||
165 | |||
166 | with open(initbuild_in_builddir, 'w') as f: | ||
167 | f.write("# init-build-env wrapper created by bitbake-setup\n") | ||
168 | f.write(cmd + '\n') | ||
169 | |||
170 | def _prepend_passthrough_to_init_build_env(builddir): | ||
171 | env = bitbake_config.get("bb-env-passthrough-additions") | ||
172 | if not env: | ||
173 | return | ||
174 | |||
175 | initbuild_in_builddir = os.path.join(builddir, 'init-build-env') | ||
176 | with open(initbuild_in_builddir) as f: | ||
177 | content = f.read() | ||
178 | |||
179 | joined = " \\\n".join(env) | ||
180 | env = "export BB_ENV_PASSTHROUGH_ADDITIONS=\" \\\n" | ||
181 | env += "${BB_ENV_PASSTHROUGH_ADDITIONS} \\\n" | ||
182 | env += joined | ||
183 | env += '"' | ||
184 | |||
185 | with open(initbuild_in_builddir, 'w') as f: | ||
186 | f.write("# environment passthrough added by bitbake-setup\n") | ||
187 | f.write(env + '\n') | ||
188 | f.write('\n') | ||
189 | f.write(content) | ||
190 | |||
191 | bitbake_builddir = os.path.join(builddir, "build") | ||
192 | print("Setting up bitbake configuration in\n {}\n".format(bitbake_builddir)) | ||
193 | |||
194 | template = bitbake_config.get("oe-template") | ||
195 | layers = bitbake_config.get("bb-layers") | ||
196 | if not template and not layers: | ||
197 | print("Bitbake configuration does not contain a reference to an OpenEmbedded build template via 'oe-template' or a list of layers via 'bb-layers'; please use oe-setup-build, oe-init-build-env or another mechanism manually to complete the setup.") | ||
198 | return | ||
199 | oesetupbuild = os.path.join(layerdir, 'setup-build') | ||
200 | if template and not os.path.exists(oesetupbuild): | ||
201 | raise Exception("Cannot complete setting up a bitbake build directory from OpenEmbedded template '{}' as oe-setup-build was not found in any layers; please use oe-init-build-env manually.".format(template)) | ||
202 | |||
203 | bitbake_confdir = os.path.join(bitbake_builddir, 'conf') | ||
204 | backup_bitbake_confdir = bitbake_confdir + "-backup.{}".format(time.strftime("%Y%m%d%H%M%S")) | ||
205 | if os.path.exists(bitbake_confdir): | ||
206 | os.rename(bitbake_confdir, backup_bitbake_confdir) | ||
207 | |||
208 | if layers: | ||
209 | _setup_build_conf(layers, bitbake_confdir) | ||
210 | |||
211 | if template: | ||
212 | bb.process.run("{} setup -c {} -b {} --no-shell".format(oesetupbuild, template, bitbake_builddir)) | ||
213 | else: | ||
214 | oeinitbuildenvdir = os.path.join(layerdir, 'oe-init-build-env-dir') | ||
215 | if not os.path.exists(os.path.join(oeinitbuildenvdir, "oe-init-build-env")): | ||
216 | print("Could not find oe-init-build-env in any of the layers; please use another mechanism to initialize the bitbake environment") | ||
217 | return | ||
218 | _make_init_build_env(bitbake_builddir, os.path.realpath(oeinitbuildenvdir)) | ||
219 | |||
220 | _prepend_passthrough_to_init_build_env(bitbake_builddir) | ||
221 | |||
222 | siteconf_symlink = os.path.join(bitbake_confdir, "site.conf") | ||
223 | siteconf = os.path.normpath(os.path.join(builddir, '..', "site.conf")) | ||
224 | if os.path.lexists(siteconf_symlink): | ||
225 | os.remove(symlink) | ||
226 | os.symlink(os.path.relpath(siteconf, bitbake_confdir) ,siteconf_symlink) | ||
227 | |||
228 | |||
229 | init_script = os.path.join(bitbake_builddir, "init-build-env") | ||
230 | shell = "bash" | ||
231 | fragments = bitbake_config.get("oe-fragments", []) + sorted(bitbake_config.get("oe-fragment-choices",{}).values()) | ||
232 | if fragments: | ||
233 | bb.process.run("{} -c '. {} && bitbake-config-build enable-fragment {}'".format(shell, init_script, " ".join(fragments))) | ||
234 | |||
235 | if os.path.exists(backup_bitbake_confdir): | ||
236 | bitbake_config_diff = get_diff(backup_bitbake_confdir, bitbake_confdir) | ||
237 | if bitbake_config_diff: | ||
238 | print("Existing bitbake configuration directory renamed to {}".format(backup_bitbake_confdir)) | ||
239 | print("The bitbake configuration has changed:") | ||
240 | print(bitbake_config_diff) | ||
241 | else: | ||
242 | shutil.rmtree(backup_bitbake_confdir) | ||
243 | |||
244 | print("This bitbake configuration provides:\n {}\n".format(bitbake_config["description"])) | ||
245 | |||
246 | readme = """{}\n\nAdditional information is in {} and {}\n | ||
247 | Source the environment using '. {}' to run builds from the command line. | ||
248 | The bitbake configuration files (local.conf, bblayers.conf and more) can be found in {}/conf | ||
249 | """.format( | ||
250 | bitbake_config["description"], | ||
251 | os.path.join(bitbake_builddir,'conf/conf-summary.txt'), | ||
252 | os.path.join(bitbake_builddir,'conf/conf-notes.txt'), | ||
253 | init_script, | ||
254 | bitbake_builddir | ||
255 | ) | ||
256 | readme_file = os.path.join(bitbake_builddir, "README") | ||
257 | with open(readme_file, 'w') as f: | ||
258 | f.write(readme) | ||
259 | print("Usage instructions and additional information are in\n {}\n".format(readme_file)) | ||
260 | print("The bitbake configuration files (local.conf, bblayers.conf and more) can be found in\n {}/conf\n".format(bitbake_builddir)) | ||
261 | print("To run builds, source the environment using\n . {}".format(init_script)) | ||
262 | |||
263 | def get_registry_config(registry_path, id): | ||
264 | for root, dirs, files in os.walk(registry_path): | ||
265 | for f in files: | ||
266 | if f.endswith('.conf.json') and id == get_config_name(f): | ||
267 | return os.path.join(root, f) | ||
268 | raise Exception("Unable to find {} in available configurations; use 'list' sub-command to see what is available".format(id)) | ||
269 | |||
270 | def update_build(config, confdir, builddir, layerdir, d): | ||
271 | layer_config = config["data"]["sources"] | ||
272 | layer_overrides = config["source-overrides"]["sources"] | ||
273 | for k,v in layer_overrides.items(): | ||
274 | if k in layer_config: | ||
275 | layer_config[k]["git-remote"] = v["git-remote"] | ||
276 | checkout_layers(layer_config, layerdir, d) | ||
277 | bitbake_config = config["bitbake-config"] | ||
278 | thisdir = os.path.dirname(config["path"]) if config["type"] == 'local' else None | ||
279 | setup_bitbake_build(bitbake_config, layerdir, builddir, thisdir) | ||
280 | |||
281 | def int_input(allowed_values): | ||
282 | n = None | ||
283 | while n is None: | ||
284 | try: | ||
285 | n = int(input()) | ||
286 | except ValueError: | ||
287 | print('Not a valid number, please try again:') | ||
288 | continue | ||
289 | if n not in allowed_values: | ||
290 | print('Number {} not one of {}, please try again:'.format(n, allowed_values)) | ||
291 | n = None | ||
292 | return n | ||
293 | |||
294 | def flatten_bitbake_configs(configs): | ||
295 | def merge_configs(c1,c2): | ||
296 | c_merged = {} | ||
297 | for k,v in c2.items(): | ||
298 | if k not in c1.keys(): | ||
299 | c_merged[k] = v | ||
300 | for k,v in c1.items(): | ||
301 | if k not in c2.keys(): | ||
302 | c_merged[k] = v | ||
303 | else: | ||
304 | c_merged[k] = c1[k] + c2[k] | ||
305 | del c_merged['configurations'] | ||
306 | return c_merged | ||
307 | |||
308 | flattened_configs = [] | ||
309 | for c in configs: | ||
310 | if 'configurations' not in c: | ||
311 | flattened_configs.append(c) | ||
312 | else: | ||
313 | for sub_c in flatten_bitbake_configs(c['configurations']): | ||
314 | flattened_configs.append(merge_configs(c, sub_c)) | ||
315 | return flattened_configs | ||
316 | |||
317 | def choose_bitbake_config(configs, parameters, non_interactive): | ||
318 | flattened_configs = flatten_bitbake_configs(configs) | ||
319 | configs_dict = {i["name"]:i for i in flattened_configs} | ||
320 | |||
321 | if parameters: | ||
322 | config_id = parameters[0] | ||
323 | if config_id not in configs_dict: | ||
324 | raise Exception("Bitbake configuration {} not found; replace with one of {}".format(config_id, configs_dict)) | ||
325 | return configs_dict[config_id] | ||
326 | |||
327 | enumerated_configs = list(enumerate(flattened_configs)) | ||
328 | if len(enumerated_configs) == 1: | ||
329 | only_config = flattened_configs[0] | ||
330 | print("\nSelecting the only available bitbake configuration {}".format(only_config["name"])) | ||
331 | return only_config | ||
332 | |||
333 | if non_interactive: | ||
334 | raise Exception("Unable to choose from bitbake configurations in non-interactive mode: {}".format(configs_dict)) | ||
335 | |||
336 | print("\nAvailable bitbake configurations:") | ||
337 | for n, config_data in enumerated_configs: | ||
338 | print("{}. {}\t{}".format(n, config_data["name"], config_data["description"])) | ||
339 | print("\nPlease select one of the above bitbake configurations by its number:") | ||
340 | config_n = int_input([i[0] for i in enumerated_configs]) | ||
341 | return flattened_configs[config_n] | ||
342 | |||
343 | def choose_config(configs, non_interactive): | ||
344 | not_expired_configs = [k for k in configs.keys() if not has_expired(configs[k].get("expires", None))] | ||
345 | config_list = list(enumerate(not_expired_configs)) | ||
346 | if len(config_list) == 1: | ||
347 | only_config = config_list[0][1] | ||
348 | print("\nSelecting the only available configuration {}\n".format(only_config)) | ||
349 | return only_config | ||
350 | |||
351 | if non_interactive: | ||
352 | raise Exception("Unable to choose from configurations in non-interactive mode: {}".format(not_expired_configs)) | ||
353 | |||
354 | print("\nAvailable configurations:") | ||
355 | for n, config_name in config_list: | ||
356 | config_data = configs[config_name] | ||
357 | expiry_date = config_data.get("expires", None) | ||
358 | config_desc = config_data["description"] | ||
359 | if expiry_date: | ||
360 | print("{}. {}\t{} (supported until {})".format(n, config_name, config_desc, expiry_date)) | ||
361 | else: | ||
362 | print("{}. {}\t{}".format(n, config_name, config_desc)) | ||
363 | print("\nPlease select one of the above configurations by its number:") | ||
364 | config_n = int_input([i[0] for i in config_list]) | ||
365 | return config_list[config_n][1] | ||
366 | |||
367 | def choose_fragments(possibilities, parameters, non_interactive, skip_selection): | ||
368 | choices = {} | ||
369 | for k,v in possibilities.items(): | ||
370 | if skip_selection and k in skip_selection: | ||
371 | print("Skipping a selection of {}, as requested on command line. The resulting bitbake configuration may require further manual adjustments.".format(k)) | ||
372 | continue | ||
373 | choice = [o for o in v["options"] if o in parameters] | ||
374 | if len(choice) > 1: | ||
375 | raise Exception("Options specified on command line do not allow a single selection from possibilities {}, please remove one or more from {}".format(v["options"], parameters)) | ||
376 | if len(choice) == 1: | ||
377 | choices[k] = choice[0] | ||
378 | continue | ||
379 | |||
380 | if non_interactive: | ||
381 | raise Exception("Unable to choose from options in non-interactive mode: {}".format(v["options"])) | ||
382 | |||
383 | print("\n" + v["description"] + ":") | ||
384 | options_enumerated = list(enumerate(v["options"])) | ||
385 | for n,o in options_enumerated: | ||
386 | print("{}. {}".format(n, o)) | ||
387 | print("\nPlease select one of the above options by its number:") | ||
388 | option_n = int_input([i[0] for i in options_enumerated]) | ||
389 | choices[k] = options_enumerated[option_n][1] | ||
390 | return choices | ||
391 | |||
392 | def obtain_config(top_dir, settings, args, source_overrides, d): | ||
393 | if args.config: | ||
394 | config_id = args.config[0] | ||
395 | config_parameters = args.config[1:] | ||
396 | if os.path.exists(config_id): | ||
397 | print("Reading configuration from local file\n {}".format(config_id)) | ||
398 | upstream_config = {'type':'local', | ||
399 | 'path':os.path.abspath(config_id), | ||
400 | 'name':get_config_name(config_id), | ||
401 | 'data':json.load(open(config_id)) | ||
402 | } | ||
403 | elif config_id.startswith("http://") or config_id.startswith("https://"): | ||
404 | print("Reading configuration from network URI\n {}".format(config_id)) | ||
405 | import urllib.request | ||
406 | with urllib.request.urlopen(config_id) as f: | ||
407 | upstream_config = {'type':'network','uri':config_id,'name':get_config_name(config_id),'data':json.load(f)} | ||
408 | else: | ||
409 | print("Looking up config {} in configuration registry".format(config_id)) | ||
410 | registry_path = update_registry(settings["default"]["registry"], cache_dir(top_dir), d) | ||
411 | registry_configs = list_registry(registry_path, with_expired=True) | ||
412 | if config_id not in registry_configs: | ||
413 | raise Exception("Config {} not found in configuration registry, re-run 'init' without parameters to choose from available configurations.".format(config_id)) | ||
414 | upstream_config = {'type':'registry','registry':settings["default"]["registry"],'name':config_id,'data':json.load(open(get_registry_config(registry_path,config_id)))} | ||
415 | expiry_date = upstream_config['data'].get("expires", None) | ||
416 | if has_expired(expiry_date): | ||
417 | print("This configuration is no longer supported after {}. Please consider changing to a supported configuration.".format(expiry_date)) | ||
418 | else: | ||
419 | registry_path = update_registry(settings["default"]["registry"], cache_dir(top_dir), d) | ||
420 | registry_configs = list_registry(registry_path, with_expired=True) | ||
421 | config_id = choose_config(registry_configs, args.non_interactive) | ||
422 | config_parameters = [] | ||
423 | upstream_config = {'type':'registry','registry':settings["default"]["registry"],'name':config_id,'data':json.load(open(get_registry_config(registry_path,config_id)))} | ||
424 | |||
425 | upstream_config['bitbake-config'] = choose_bitbake_config(upstream_config['data']['bitbake-setup']['configurations'], config_parameters, args.non_interactive) | ||
426 | upstream_config['bitbake-config']['oe-fragment-choices'] = choose_fragments(upstream_config['bitbake-config'].get('oe-fragments-one-of',{}), config_parameters[1:], args.non_interactive, args.skip_selection) | ||
427 | upstream_config['non-interactive-cmdline-options'] = [config_id, upstream_config['bitbake-config']['name']] + sorted(upstream_config['bitbake-config']['oe-fragment-choices'].values()) | ||
428 | upstream_config['source-overrides'] = source_overrides | ||
429 | upstream_config['skip-selection'] = args.skip_selection | ||
430 | return upstream_config | ||
431 | |||
432 | def init_config(top_dir, settings, args, d): | ||
433 | stdout = sys.stdout | ||
434 | def handle_task_progress(event, d): | ||
435 | rate = event.rate if event.rate else '' | ||
436 | progress = event.progress if event.progress > 0 else 0 | ||
437 | print("{}% {} ".format(progress, rate), file=stdout, end='\r') | ||
438 | |||
439 | create_siteconf(top_dir, args.non_interactive) | ||
440 | source_overrides = json.load(open(args.source_overrides)) if args.source_overrides else {'sources':{}} | ||
441 | upstream_config = obtain_config(top_dir, settings, args, source_overrides, d) | ||
442 | print("\nRun 'bitbake-setup init --non-interactive {}' to select this configuration non-interactively.\n".format(" ".join(upstream_config['non-interactive-cmdline-options']))) | ||
443 | |||
444 | builddir = os.path.join(os.path.abspath(top_dir), args.build_dir_name or "{}-{}".format(upstream_config['name']," ".join(upstream_config['non-interactive-cmdline-options'][1:]).replace(" ","-").replace("/","_"))) | ||
445 | if os.path.exists(os.path.join(builddir, "layers")): | ||
446 | print(f"Build already initialized in:\n {builddir}\nUse 'bitbake-setup status' to check if it needs to be updated, or 'bitbake-setup update' to perform the update.\nIf you would like to start over and re-initialize a build in this directory, remove it, and run 'bitbake-setup init' again.") | ||
447 | return | ||
448 | |||
449 | print("Initializing a build in\n {}".format(builddir)) | ||
450 | if not args.non_interactive: | ||
451 | y_or_n = input('Continue? (y/N): ') | ||
452 | if y_or_n != 'y': | ||
453 | exit() | ||
454 | print() | ||
455 | |||
456 | os.makedirs(builddir, exist_ok=True) | ||
457 | |||
458 | confdir = os.path.join(builddir, "config") | ||
459 | layerdir = os.path.join(builddir, "layers") | ||
460 | |||
461 | os.makedirs(confdir) | ||
462 | os.makedirs(layerdir) | ||
463 | |||
464 | bb.process.run("git -C {} init -b main".format(confdir)) | ||
465 | # Make sure commiting doesn't fail if no default git user is configured on the machine | ||
466 | bb.process.run("git -C {} config user.name bitbake-setup".format(confdir)) | ||
467 | bb.process.run("git -C {} config user.email bitbake-setup@not.set".format(confdir)) | ||
468 | bb.process.run("git -C {} commit --no-verify --allow-empty -m 'Initial commit'".format(confdir)) | ||
469 | |||
470 | bb.event.register("bb.build.TaskProgress", handle_task_progress, data=d) | ||
471 | |||
472 | write_config(upstream_config, confdir) | ||
473 | commit_config(confdir) | ||
474 | update_build(upstream_config, confdir, builddir, layerdir, d) | ||
475 | |||
476 | bb.event.remove("bb.build.TaskProgress", None) | ||
477 | |||
478 | def get_diff(file1, file2): | ||
479 | try: | ||
480 | bb.process.run('diff -uNr {} {}'.format(file1, file2)) | ||
481 | except bb.process.ExecutionError as e: | ||
482 | if e.exitcode == 1: | ||
483 | return e.stdout | ||
484 | else: | ||
485 | raise e | ||
486 | return None | ||
487 | |||
488 | def are_layers_changed(layers, layerdir, d): | ||
489 | changed = False | ||
490 | for r_name in layers: | ||
491 | r_data = layers[r_name] | ||
492 | repodir = r_data["path"] | ||
493 | |||
494 | r_remote = r_data['git-remote'] | ||
495 | rev = r_remote['rev'] | ||
496 | branch = r_remote.get('branch', None) | ||
497 | remotes = r_remote['remotes'] | ||
498 | |||
499 | for remote in remotes: | ||
500 | type,host,path,user,pswd,params = bb.fetch.decodeurl(remotes[remote]["uri"]) | ||
501 | fetchuri = bb.fetch.encodeurl(('git',host,path,user,pswd,params)) | ||
502 | if branch: | ||
503 | fetcher = bb.fetch.FetchData("{};protocol={};rev={};branch={};destsuffix={}".format(fetchuri,type,rev,branch,repodir), d) | ||
504 | else: | ||
505 | fetcher = bb.fetch.FetchData("{};protocol={};rev={};nobranch=1;destsuffix={}".format(fetchuri,type,rev,repodir), d) | ||
506 | upstream_revision = fetcher.method.latest_revision(fetcher, d, 'default') | ||
507 | rev_parse_result = bb.process.run('git -C {} rev-parse HEAD'.format(os.path.join(layerdir, repodir))) | ||
508 | local_revision = rev_parse_result[0].strip() | ||
509 | if upstream_revision != local_revision: | ||
510 | changed = True | ||
511 | print('Layer repository {} checked out into {} updated revision {} from {} to {}'.format(remotes[remote]["uri"], os.path.join(layerdir, repodir), rev, local_revision, upstream_revision)) | ||
512 | |||
513 | return changed | ||
514 | |||
515 | def build_status(top_dir, settings, args, d, update=False): | ||
516 | builddir = args.build_dir | ||
517 | |||
518 | confdir = os.path.join(builddir, "config") | ||
519 | layerdir = os.path.join(builddir, "layers") | ||
520 | |||
521 | current_upstream_config = json.load(open(os.path.join(confdir, "config-upstream.json"))) | ||
522 | |||
523 | args.config = current_upstream_config['non-interactive-cmdline-options'] | ||
524 | args.non_interactive = True | ||
525 | args.skip_selection = current_upstream_config['skip-selection'] | ||
526 | source_overrides = current_upstream_config["source-overrides"] | ||
527 | new_upstream_config = obtain_config(top_dir, settings, args, source_overrides, d) | ||
528 | |||
529 | write_config(new_upstream_config, confdir) | ||
530 | config_diff = bb.process.run('git -C {} diff'.format(confdir))[0] | ||
531 | |||
532 | if config_diff: | ||
533 | print('\nConfiguration in {} has changed:\n{}'.format(builddir, config_diff)) | ||
534 | if update: | ||
535 | commit_config(confdir) | ||
536 | update_build(new_upstream_config, confdir, builddir, layerdir, d) | ||
537 | else: | ||
538 | bb.process.run('git -C {} restore config-upstream.json'.format(confdir)) | ||
539 | return | ||
540 | |||
541 | if are_layers_changed(current_upstream_config["data"]["sources"], layerdir, d): | ||
542 | if update: | ||
543 | update_build(current_upstream_config, confdir, builddir, layerdir, d) | ||
544 | return | ||
545 | |||
546 | print("\nConfiguration in {} has not changed.".format(builddir)) | ||
547 | |||
548 | def build_update(top_dir, settings, args, d): | ||
549 | build_status(top_dir, settings, args, d, update=True) | ||
550 | |||
551 | def do_fetch(fetcher, dir): | ||
552 | # git fetcher simply dumps git output to stdout; in bitbake context that is redirected to temp/log.do_fetch | ||
553 | # and we need to set up smth similar here | ||
554 | fetchlogdir = os.path.join(dir, 'logs') | ||
555 | os.makedirs(fetchlogdir, exist_ok=True) | ||
556 | fetchlog = os.path.join(fetchlogdir, 'fetch_log.{}'.format(datetime.datetime.now().strftime("%Y%m%d%H%M%S"))) | ||
557 | with open(fetchlog, 'a') as f: | ||
558 | oldstdout = sys.stdout | ||
559 | sys.stdout = f | ||
560 | fetcher.download() | ||
561 | fetcher.unpack(dir) | ||
562 | sys.stdout = oldstdout | ||
563 | |||
564 | def update_registry(registry, cachedir, d): | ||
565 | registrydir = 'configurations' | ||
566 | if registry.startswith("."): | ||
567 | full_registrydir = os.path.join(os.getcwd(), registry, registrydir) | ||
568 | elif registry.startswith("/"): | ||
569 | full_registrydir = os.path.join(registry, registrydir) | ||
570 | else: | ||
571 | full_registrydir = os.path.join(cachedir, registrydir) | ||
572 | print("Fetching configuration registry\n {}\ninto\n {}".format(registry, full_registrydir)) | ||
573 | fetcher = bb.fetch.Fetch(["{};destsuffix={}".format(registry, registrydir)], d) | ||
574 | do_fetch(fetcher, cachedir) | ||
575 | return full_registrydir | ||
576 | |||
577 | def has_expired(expiry_date): | ||
578 | if expiry_date: | ||
579 | return datetime.datetime.now() > datetime.datetime.fromisoformat(expiry_date) | ||
580 | return False | ||
581 | |||
582 | def list_registry(registry_path, with_expired): | ||
583 | json_data = {} | ||
584 | |||
585 | for root, dirs, files in os.walk(registry_path): | ||
586 | for f in files: | ||
587 | if f.endswith('.conf.json'): | ||
588 | config_name = get_config_name(f) | ||
589 | config_data = json.load(open(os.path.join(root, f))) | ||
590 | config_desc = config_data["description"] | ||
591 | expiry_date = config_data.get("expires", None) | ||
592 | if expiry_date: | ||
593 | if with_expired or not has_expired(expiry_date): | ||
594 | json_data[config_name] = {"description": config_desc, "expires": expiry_date} | ||
595 | else: | ||
596 | json_data[config_name] = {"description": config_desc} | ||
597 | return json_data | ||
598 | |||
599 | def list_configs(top_dir, settings, args, d): | ||
600 | registry_path = update_registry(settings["default"]["registry"], cache_dir(top_dir), d) | ||
601 | json_data = list_registry(registry_path, args.with_expired) | ||
602 | print("\nAvailable configurations:") | ||
603 | for config_name, config_data in json_data.items(): | ||
604 | expiry_date = config_data.get("expires", None) | ||
605 | config_desc = config_data["description"] | ||
606 | if expiry_date: | ||
607 | if args.with_expired or not has_expired(expiry_date): | ||
608 | print("{}\t{} (supported until {})".format(config_name, config_desc, expiry_date)) | ||
609 | else: | ||
610 | print("{}\t{}".format(config_name, config_desc)) | ||
611 | print("\nRun 'init' with one of the above configuration identifiers to set up a build.") | ||
612 | |||
613 | if args.write_json: | ||
614 | with open(args.write_json, 'w') as f: | ||
615 | json.dump(json_data, f, sort_keys=True, indent=4) | ||
616 | print("Available configurations written into {}".format(args.write_json)) | ||
617 | |||
618 | def install_buildtools(top_dir, settings, args, d): | ||
619 | buildtools_install_dir = os.path.join(args.build_dir, 'buildtools') | ||
620 | if os.path.exists(buildtools_install_dir): | ||
621 | if not args.force: | ||
622 | print("Buildtools are already installed in {}.".format(buildtools_install_dir)) | ||
623 | env_scripts = glob.glob(os.path.join(buildtools_install_dir, 'environment-setup-*')) | ||
624 | if env_scripts: | ||
625 | print("If you wish to use them, you need to source the environment setup script e.g.") | ||
626 | for s in env_scripts: | ||
627 | print("$ . {}".format(s)) | ||
628 | print("You can also re-run bitbake-setup install-buildtools with --force option to force a reinstallation.") | ||
629 | return | ||
630 | shutil.rmtree(buildtools_install_dir) | ||
631 | |||
632 | install_buildtools = os.path.join(args.build_dir, 'layers/oe-scripts/install-buildtools') | ||
633 | buildtools_download_dir = os.path.join(args.build_dir, 'buildtools-downloads/{}'.format(time.strftime("%Y%m%d%H%M%S"))) | ||
634 | print("Buildtools archive is downloaded into {} and its content installed into {}".format(buildtools_download_dir, buildtools_install_dir)) | ||
635 | subprocess.check_call("{} -d {} --downloads-directory {}".format(install_buildtools, buildtools_install_dir, buildtools_download_dir), shell=True) | ||
636 | |||
637 | def default_settings_path(top_dir): | ||
638 | return os.path.join(top_dir, 'settings.conf') | ||
639 | |||
640 | def create_siteconf(top_dir, non_interactive=True): | ||
641 | siteconfpath = os.path.join(top_dir, 'site.conf') | ||
642 | print('A common site.conf file will be created, please edit or replace before running builds\n {}\n'.format(siteconfpath)) | ||
643 | if not non_interactive: | ||
644 | y_or_n = input('Proceed? (y/N): ') | ||
645 | if y_or_n != 'y': | ||
646 | exit() | ||
647 | |||
648 | os.makedirs(os.path.dirname(top_dir), exist_ok=True) | ||
649 | if os.path.exists(siteconfpath): | ||
650 | backup_siteconf = siteconfpath + "-backup.{}".format(time.strftime("%Y%m%d%H%M%S")) | ||
651 | os.rename(siteconfpath, backup_siteconf) | ||
652 | print("Previous settings are in {}".format(backup_siteconf)) | ||
653 | with open(siteconfpath, 'w') as siteconffile: | ||
654 | siteconffile.write('# This file is intended for build host-specific bitbake settings\n') | ||
655 | |||
656 | def global_settings_path(args): | ||
657 | return os.path.abspath(args.global_settings) if args.global_settings else os.path.join(os.path.expanduser('~'), '.config', 'bitbake-setup', 'settings.conf') | ||
658 | |||
659 | def load_settings(settings_path): | ||
660 | settings = configparser.ConfigParser() | ||
661 | if os.path.exists(settings_path): | ||
662 | print('Loading settings from\n {}\n'.format(settings_path)) | ||
663 | settings.read_file(open(settings_path)) | ||
664 | return settings | ||
665 | |||
666 | def change_setting(top_dir, args): | ||
667 | if vars(args)['global']: | ||
668 | settings_path = global_settings_path(args) | ||
669 | else: | ||
670 | settings_path = default_settings_path(top_dir) | ||
671 | settings = load_settings(settings_path) | ||
672 | |||
673 | if args.subcommand == 'set': | ||
674 | if args.section not in settings.keys(): | ||
675 | settings[args.section] = {} | ||
676 | settings[args.section][args.setting] = args.value | ||
677 | print(f"From section '{args.section}' the setting '{args.setting}' was changed to '{args.value}'") | ||
678 | if args.subcommand == 'unset': | ||
679 | if args.section in settings.keys() and args.setting in settings[args.section].keys(): | ||
680 | del settings[args.section][args.setting] | ||
681 | print(f"From section '{args.section}' the setting '{args.setting}' has been removed") | ||
682 | |||
683 | os.makedirs(os.path.dirname(settings_path), exist_ok=True) | ||
684 | with open(settings_path, 'w') as settingsfile: | ||
685 | settings.write(settingsfile) | ||
686 | print(f"Settings written to {settings_path}") | ||
687 | |||
688 | def list_settings(all_settings): | ||
689 | for section, section_settings in all_settings.items(): | ||
690 | for key, value in section_settings.items(): | ||
691 | print("{} {} {}".format(section, key, value)) | ||
692 | |||
693 | def settings_func(top_dir, all_settings, args): | ||
694 | if args.subcommand == 'list': | ||
695 | list_settings(all_settings) | ||
696 | elif args.subcommand == 'set' or args.subcommand == 'unset': | ||
697 | change_setting(top_dir, args) | ||
698 | |||
699 | def get_build_dir_via_bbpath(): | ||
700 | bbpath = os.environ.get('BBPATH') | ||
701 | if bbpath: | ||
702 | bitbake_dir = os.path.normpath(bbpath.split(':')[0]) | ||
703 | if os.path.exists(os.path.join(bitbake_dir,'init-build-env')): | ||
704 | build_dir = os.path.dirname(bitbake_dir) | ||
705 | return build_dir | ||
706 | return None | ||
707 | |||
708 | def get_top_dir(args, settings): | ||
709 | build_dir_via_bbpath = get_build_dir_via_bbpath() | ||
710 | if build_dir_via_bbpath: | ||
711 | top_dir = os.path.dirname(build_dir_via_bbpath) | ||
712 | if os.path.exists(default_settings_path(top_dir)): | ||
713 | return top_dir | ||
714 | |||
715 | if hasattr(args, 'build_dir'): | ||
716 | top_dir = os.path.dirname(os.path.normpath(args.build_dir)) | ||
717 | return top_dir | ||
718 | |||
719 | top_dir_prefix = settings['default']['top-dir-prefix'] | ||
720 | top_dir_name = settings['default']['top-dir-name'] | ||
721 | return os.path.join(top_dir_prefix, top_dir_name) | ||
722 | |||
723 | def merge_settings(builtin_settings, global_settings, local_settings, cmdline_settings): | ||
724 | all_settings = builtin_settings | ||
725 | |||
726 | for s in (global_settings, local_settings): | ||
727 | for section, section_settings in s.items(): | ||
728 | for setting, value in section_settings.items(): | ||
729 | all_settings[section][setting] = value | ||
730 | |||
731 | for (section, setting, value) in cmdline_settings: | ||
732 | all_settings[section][setting] = value | ||
733 | |||
734 | return all_settings | ||
735 | |||
736 | def main(): | ||
737 | def add_build_dir_arg(parser): | ||
738 | build_dir = get_build_dir_via_bbpath() | ||
739 | if build_dir: | ||
740 | parser.add_argument('--build-dir', default=build_dir, help="Path to the build, default is %(default)s via BBPATH") | ||
741 | else: | ||
742 | parser.add_argument('--build-dir', required=True, help="Path to the build") | ||
743 | |||
744 | parser = argparse.ArgumentParser( | ||
745 | description="BitBake setup utility. Run with 'init' argument to get started.", | ||
746 | epilog="Use %(prog)s <subcommand> --help to get help on a specific command" | ||
747 | ) | ||
748 | parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true') | ||
749 | parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true') | ||
750 | parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR') | ||
751 | parser.add_argument('--no-network', action='store_true', help='Do not check whether configuration repositories and layer repositories have been updated; use only the local cache.') | ||
752 | parser.add_argument('--global-settings', action='store', metavar='PATH', help='Path to the global settings file.') | ||
753 | parser.add_argument('--setting', default=[], action='append', dest='cmdline_settings', | ||
754 | nargs=3, metavar=('SECTION', 'SETTING', 'VALUE'), | ||
755 | help='Modify a setting (for this bitbake-setup invocation only), for example "--setting default top-dir-prefix /path/to/top/dir".') | ||
756 | |||
757 | subparsers = parser.add_subparsers() | ||
758 | |||
759 | parser_list = subparsers.add_parser('list', help='List available configurations') | ||
760 | parser_list.add_argument('--with-expired', action='store_true', help='List also configurations that are no longer supported due to reaching their end-of-life dates.') | ||
761 | parser_list.add_argument('--write-json', action='store', help='Write available configurations into a json file so they can be programmatically processed.') | ||
762 | parser_list.set_defaults(func=list_configs) | ||
763 | |||
764 | parser_init = subparsers.add_parser('init', help='Select a configuration and initialize a build from it') | ||
765 | parser_init.add_argument('config', nargs='*', help="path/URL/id to a configuration file (use 'list' command to get available ids), followed by configuration options. Bitbake-setup will ask to choose from available choices if command line doesn't completely specify them.") | ||
766 | parser_init.add_argument('--non-interactive', action='store_true', help='Do not ask to interactively choose from available options; if bitbake-setup cannot make a decision it will stop with a failure.') | ||
767 | parser_init.add_argument('--source-overrides', action='store', help='Override sources information (repositories/revisions) with values from a local json file.') | ||
768 | parser_init.add_argument('--build-dir-name', action='store', help='A custom build directory name under the top directory.') | ||
769 | parser_init.add_argument('--skip-selection', action='append', help='Do not select and set an option/fragment from available choices; the resulting bitbake configuration may be incomplete.') | ||
770 | parser_init.set_defaults(func=init_config) | ||
771 | |||
772 | parser_status = subparsers.add_parser('status', help='Check if the build needs to be synchronized with configuration') | ||
773 | add_build_dir_arg(parser_status) | ||
774 | parser_status.set_defaults(func=build_status) | ||
775 | |||
776 | parser_update = subparsers.add_parser('update', help='Update a build to be in sync with configuration') | ||
777 | add_build_dir_arg(parser_update) | ||
778 | parser_update.set_defaults(func=build_update) | ||
779 | |||
780 | parser_install_buildtools = subparsers.add_parser('install-buildtools', help='Install buildtools which can help fulfil missing or incorrect dependencies on the host machine') | ||
781 | add_build_dir_arg(parser_install_buildtools) | ||
782 | parser_install_buildtools.add_argument('--force', action='store_true', help='Force a reinstall of buildtools over the previous installation.') | ||
783 | parser_install_buildtools.set_defaults(func=install_buildtools) | ||
784 | |||
785 | parser_settings_arg_global = argparse.ArgumentParser(add_help=False) | ||
786 | parser_settings_arg_global.add_argument('--global', action='store_true', help="Modify the setting in a global settings file, rather than one specific to a top directory") | ||
787 | |||
788 | parser_settings = subparsers.add_parser('settings', parents=[parser_settings_arg_global], | ||
789 | help='List current settings, or set or unset a setting in a settings file (e.g. the default prefix and name of the top directory, the location of build configuration registry, downloads directory and other settings specific to a top directory)') | ||
790 | parser_settings.set_defaults(func=settings_func) | ||
791 | |||
792 | subparser_settings = parser_settings.add_subparsers(dest="subcommand", required=True, help="The action to perform on the settings file") | ||
793 | |||
794 | parser_settings_list = subparser_settings.add_parser('list', | ||
795 | help="List all settings with their values") | ||
796 | |||
797 | parser_settings_set = subparser_settings.add_parser('set', parents=[parser_settings_arg_global], | ||
798 | help="In a Section, set a setting to a certain value") | ||
799 | parser_settings_set.add_argument("section", metavar="<section>", help="Section in a settings file, typically 'default'") | ||
800 | parser_settings_set.add_argument("setting", metavar="<setting>", help="Name of a setting") | ||
801 | parser_settings_set.add_argument("value", metavar="<value>", help="The setting value") | ||
802 | |||
803 | parser_settings_unset = subparser_settings.add_parser('unset', parents=[parser_settings_arg_global], | ||
804 | help="Unset a setting, e.g. 'bitbake-setup settings unset default registry' would revert to the registry setting in a global settings file") | ||
805 | parser_settings_unset.add_argument("section", metavar="<section>", help="Section in a settings file, typically 'default'") | ||
806 | parser_settings_unset.add_argument("setting", metavar="<setting>", help="The setting to remove") | ||
807 | |||
808 | args = parser.parse_args() | ||
809 | |||
810 | logging.basicConfig(stream=sys.stdout) | ||
811 | if args.debug: | ||
812 | logger.setLevel(logging.DEBUG) | ||
813 | elif args.quiet: | ||
814 | logger.setLevel(logging.ERROR) | ||
815 | |||
816 | # Need to re-run logger_create with color argument | ||
817 | # (will be the same logger since it has the same name) | ||
818 | bb.msg.logger_create('bitbake-setup', output=sys.stdout, | ||
819 | color=args.color, | ||
820 | level=logger.getEffectiveLevel()) | ||
821 | |||
822 | if 'func' in args: | ||
823 | if hasattr(args, 'build_dir'): | ||
824 | if not os.path.exists(os.path.join(args.build_dir,'build', 'init-build-env')): | ||
825 | print("Not a valid build directory: build/init-build-env does not exist in {}".format(args.build_dir)) | ||
826 | return | ||
827 | |||
828 | if not hasattr(args, 'non_interactive'): | ||
829 | args.non_interactive = True | ||
830 | |||
831 | builtin_settings = {} | ||
832 | builtin_settings['default'] = { | ||
833 | 'top-dir-prefix':os.path.expanduser('~'), | ||
834 | 'top-dir-name':'bitbake-builds', | ||
835 | 'registry':default_registry, | ||
836 | } | ||
837 | |||
838 | global_settings = load_settings(global_settings_path(args)) | ||
839 | top_dir = get_top_dir(args, merge_settings(builtin_settings, global_settings, {}, args.cmdline_settings)) | ||
840 | |||
841 | # This cannot be set with the rest of the builtin settings as top_dir needs to be determined first | ||
842 | builtin_settings['default']['dl-dir'] = os.path.join(top_dir, '.bitbake-setup-downloads') | ||
843 | |||
844 | topdir_settings = load_settings(default_settings_path(top_dir)) | ||
845 | all_settings = merge_settings(builtin_settings, global_settings, topdir_settings, args.cmdline_settings) | ||
846 | |||
847 | if args.func == settings_func: | ||
848 | settings_func(top_dir, all_settings, args) | ||
849 | return | ||
850 | |||
851 | print('Bitbake-setup is using {} as top directory ("bitbake-setup settings --help" shows how to change it).\n'.format(top_dir, global_settings_path(args))) | ||
852 | |||
853 | d = init_bb_cache(top_dir, all_settings, args) | ||
854 | args.func(top_dir, all_settings, args, d) | ||
855 | save_bb_cache() | ||
856 | else: | ||
857 | from argparse import Namespace | ||
858 | parser.print_help() | ||
859 | |||
860 | main() | ||
diff --git a/bitbake/bin/bitbake-worker b/bitbake/bin/bitbake-worker index e8073f2ac3..d2b146a6a9 100755 --- a/bitbake/bin/bitbake-worker +++ b/bitbake/bin/bitbake-worker | |||
@@ -9,6 +9,7 @@ import os | |||
9 | import sys | 9 | import sys |
10 | import warnings | 10 | import warnings |
11 | warnings.simplefilter("default") | 11 | warnings.simplefilter("default") |
12 | warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*use.of.fork.*may.lead.to.deadlocks.in.the.child.*") | ||
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 | from bb import fetch2 | 14 | from bb import fetch2 |
14 | import logging | 15 | import logging |
@@ -21,9 +22,14 @@ import traceback | |||
21 | import queue | 22 | import queue |
22 | import shlex | 23 | import shlex |
23 | import subprocess | 24 | import subprocess |
25 | import fcntl | ||
24 | from multiprocessing import Lock | 26 | from multiprocessing import Lock |
25 | from threading import Thread | 27 | from threading import Thread |
26 | 28 | ||
29 | # Remove when we have a minimum of python 3.10 | ||
30 | if not hasattr(fcntl, 'F_SETPIPE_SZ'): | ||
31 | fcntl.F_SETPIPE_SZ = 1031 | ||
32 | |||
27 | bb.utils.check_system_locale() | 33 | bb.utils.check_system_locale() |
28 | 34 | ||
29 | # Users shouldn't be running this code directly | 35 | # Users shouldn't be running this code directly |
@@ -44,7 +50,6 @@ if sys.argv[1].startswith("decafbadbad"): | |||
44 | # updates to log files for use with tail | 50 | # updates to log files for use with tail |
45 | try: | 51 | try: |
46 | if sys.stdout.name == '<stdout>': | 52 | if sys.stdout.name == '<stdout>': |
47 | import fcntl | ||
48 | fl = fcntl.fcntl(sys.stdout.fileno(), fcntl.F_GETFL) | 53 | fl = fcntl.fcntl(sys.stdout.fileno(), fcntl.F_GETFL) |
49 | fl |= os.O_SYNC | 54 | fl |= os.O_SYNC |
50 | fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, fl) | 55 | fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, fl) |
@@ -56,6 +61,12 @@ logger = logging.getLogger("BitBake") | |||
56 | 61 | ||
57 | worker_pipe = sys.stdout.fileno() | 62 | worker_pipe = sys.stdout.fileno() |
58 | bb.utils.nonblockingfd(worker_pipe) | 63 | bb.utils.nonblockingfd(worker_pipe) |
64 | # Try to make the pipe buffers larger as it is much more efficient. If we can't | ||
65 | # e.g. out of buffer space (/proc/sys/fs/pipe-user-pages-soft) then just pass over. | ||
66 | try: | ||
67 | fcntl.fcntl(worker_pipe, fcntl.F_SETPIPE_SZ, 512 * 1024) | ||
68 | except: | ||
69 | pass | ||
59 | # Need to guard against multiprocessing being used in child processes | 70 | # Need to guard against multiprocessing being used in child processes |
60 | # and multiple processes trying to write to the parent at the same time | 71 | # and multiple processes trying to write to the parent at the same time |
61 | worker_pipe_lock = None | 72 | worker_pipe_lock = None |
@@ -105,7 +116,7 @@ def worker_flush(worker_queue): | |||
105 | if not worker_queue.empty(): | 116 | if not worker_queue.empty(): |
106 | worker_queue_int.extend(worker_queue.get()) | 117 | worker_queue_int.extend(worker_queue.get()) |
107 | written = os.write(worker_pipe, worker_queue_int) | 118 | written = os.write(worker_pipe, worker_queue_int) |
108 | worker_queue_int = worker_queue_int[written:] | 119 | del worker_queue_int[0:written] |
109 | except (IOError, OSError) as e: | 120 | except (IOError, OSError) as e: |
110 | if e.errno != errno.EAGAIN and e.errno != errno.EPIPE: | 121 | if e.errno != errno.EAGAIN and e.errno != errno.EPIPE: |
111 | raise | 122 | raise |
@@ -171,11 +182,8 @@ def fork_off_task(cfg, data, databuilder, workerdata, extraconfigdata, runtask): | |||
171 | elif workerdata["umask"]: | 182 | elif workerdata["umask"]: |
172 | umask = workerdata["umask"] | 183 | umask = workerdata["umask"] |
173 | if umask: | 184 | if umask: |
174 | # umask might come in as a number or text string.. | 185 | # Convert to a python numeric value as it could be a string |
175 | try: | 186 | umask = bb.utils.to_filemode(umask) |
176 | umask = int(umask, 8) | ||
177 | except TypeError: | ||
178 | pass | ||
179 | 187 | ||
180 | dry_run = cfg.dry_run or runtask['dry_run'] | 188 | dry_run = cfg.dry_run or runtask['dry_run'] |
181 | 189 | ||
@@ -357,7 +365,7 @@ class runQueueWorkerPipe(): | |||
357 | def read(self): | 365 | def read(self): |
358 | start = len(self.queue) | 366 | start = len(self.queue) |
359 | try: | 367 | try: |
360 | self.queue.extend(self.input.read(102400) or b"") | 368 | self.queue.extend(self.input.read(512*1024) or b"") |
361 | except (OSError, IOError) as e: | 369 | except (OSError, IOError) as e: |
362 | if e.errno != errno.EAGAIN: | 370 | if e.errno != errno.EAGAIN: |
363 | raise | 371 | raise |
diff --git a/bitbake/bin/git-make-shallow b/bitbake/bin/git-make-shallow index 9de557c10e..e6c180b4d6 100755 --- a/bitbake/bin/git-make-shallow +++ b/bitbake/bin/git-make-shallow | |||
@@ -115,8 +115,8 @@ def filter_refs(refs): | |||
115 | all_refs = get_all_refs() | 115 | all_refs = get_all_refs() |
116 | to_remove = set(all_refs) - set(refs) | 116 | to_remove = set(all_refs) - set(refs) |
117 | if to_remove: | 117 | if to_remove: |
118 | check_output(['xargs', '-0', '-n', '1'] + git_cmd + ['update-ref', '-d', '--no-deref'], | 118 | check_output(git_cmd + ['update-ref', '--no-deref', '--stdin', '-z'], |
119 | input=''.join(l + '\0' for l in to_remove)) | 119 | input=''.join('delete ' + l + '\0\0' for l in to_remove)) |
120 | 120 | ||
121 | 121 | ||
122 | def follow_history_intersections(revisions, refs): | 122 | def follow_history_intersections(revisions, refs): |