summaryrefslogtreecommitdiffstats
path: root/bitbake/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/bin')
-rwxr-xr-xbitbake/bin/bitbake2
l---------bitbake/bin/bitbake-config-build1
-rwxr-xr-xbitbake/bin/bitbake-diffsigs9
-rwxr-xr-xbitbake/bin/bitbake-getvar15
-rwxr-xr-xbitbake/bin/bitbake-hashclient107
-rwxr-xr-xbitbake/bin/bitbake-hashserv10
-rwxr-xr-xbitbake/bin/bitbake-layers26
-rwxr-xr-xbitbake/bin/bitbake-prserv26
-rwxr-xr-xbitbake/bin/bitbake-selftest5
-rwxr-xr-xbitbake/bin/bitbake-server9
-rwxr-xr-xbitbake/bin/bitbake-setup860
-rwxr-xr-xbitbake/bin/bitbake-worker24
-rwxr-xr-xbitbake/bin/git-make-shallow4
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
28bb.utils.check_system_locale() 28bb.utils.check_system_locale()
29 29
30__version__ = "2.9.1" 30__version__ = "2.15.2"
31 31
32if __name__ == "__main__": 32if __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
10import os 10import os
11import sys 11import sys
12import warnings 12import warnings
13import logging
13warnings.simplefilter("default") 14warnings.simplefilter("default")
14 15
15bindir = os.path.dirname(__file__) 16bindir = os.path.dirname(__file__)
16topdir = os.path.dirname(bindir) 17topdir = os.path.dirname(bindir)
17sys.path[0:0] = [os.path.join(topdir, 'lib')] 18sys.path[0:0] = [os.path.join(topdir, 'lib')]
18 19
20import bb.providers
19import bb.tinfoil 21import bb.tinfoil
20 22
21if __name__ == "__main__": 23if __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
16import warnings 16import warnings
17import netrc 17import netrc
18import json 18import json
19import statistics
20import textwrap
19warnings.simplefilter("default") 21warnings.simplefilter("default")
20 22
21try: 23try:
@@ -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
18warnings.simplefilter("default") 18warnings.simplefilter("default")
19 19
20bindir = os.path.dirname(__file__) 20bindir = os.path.dirname(__file__)
21toolname = os.path.basename(__file__).split(".")[0]
21topdir = os.path.dirname(bindir) 22topdir = os.path.dirname(bindir)
22sys.path[0:0] = [os.path.join(topdir, 'lib')] 23sys.path[0:0] = [os.path.join(topdir, 'lib')]
23 24
24import bb.tinfoil 25import bb.tinfoil
25import bb.msg 26import bb.msg
26 27
27logger = bb.msg.logger_create('bitbake-layers', sys.stdout) 28logger = bb.msg.logger_create(toolname, sys.stdout)
28 29
29def main(): 30def 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
97if __name__ == "__main__": 97if __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
16import prserv 16import prserv
17import prserv.serv 17import prserv.serv
18 18
19VERSION = "1.1.0" 19VERSION = "2.0.0"
20 20
21PRHOST_DEFAULT="0.0.0.0" 21PRHOST_DEFAULT="0.0.0.0"
22PRPORT_DEFAULT=8585 22PRPORT_DEFAULT=8585
23 23
24def 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
24def main(): 31def 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
15try: 15try:
16 import bb 16 import bb
17 import hashserv 17 import hashserv
18 import prserv
18 import layerindexlib 19 import layerindexlib
19except RuntimeError as exc: 20except 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
9import sys 9import sys
10import warnings 10import warnings
11warnings.simplefilter("default") 11warnings.simplefilter("default")
12warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*use.of.fork.*may.lead.to.deadlocks.in.the.child.*")
12import logging 13import logging
13sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) 14sys.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]
29lockname = sys.argv[5] 30lockname = sys.argv[5]
30sockname = sys.argv[6] 31sockname = sys.argv[6]
31timeout = float(sys.argv[7]) 32timeout = float(sys.argv[7])
32profile = bool(int(sys.argv[8])) 33profile = sys.argv[8]
33xmlrpcinterface = (sys.argv[9], int(sys.argv[10])) 34xmlrpcinterface = (sys.argv[9], int(sys.argv[10]))
34if xmlrpcinterface[0] == "None": 35if xmlrpcinterface[0] == "None":
35 xmlrpcinterface = (None, xmlrpcinterface[1]) 36 xmlrpcinterface = (None, xmlrpcinterface[1])
@@ -38,9 +39,9 @@ if xmlrpcinterface[0] == "None":
38with open('/dev/null', 'r') as si: 39with open('/dev/null', 'r') as si:
39 os.dup2(si.fileno(), sys.stdin.fileno()) 40 os.dup2(si.fileno(), sys.stdin.fileno())
40 41
41so = open(logfile, 'a+') 42with open(logfile, 'a+') as so:
42os.dup2(so.fileno(), sys.stdout.fileno()) 43 os.dup2(so.fileno(), sys.stdout.fileno())
43os.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
7import logging
8import os
9import sys
10import argparse
11import warnings
12import json
13import shutil
14import time
15import stat
16import tempfile
17import configparser
18import datetime
19import glob
20import subprocess
21
22default_registry = os.path.normpath(os.path.dirname(__file__) + "/../default-registry")
23
24bindir = os.path.abspath(os.path.dirname(__file__))
25sys.path[0:0] = [os.path.join(os.path.dirname(bindir), 'lib')]
26
27import bb.msg
28import bb.process
29
30logger = bb.msg.logger_create('bitbake-setup', sys.stdout)
31
32def cache_dir(top_dir):
33 return os.path.join(top_dir, '.bitbake-setup-cache')
34
35def 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
48def save_bb_cache():
49 bb.fetch2.fetcher_parse_save()
50 bb.fetch2.fetcher_parse_done()
51
52def 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
60def 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
64def 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
68def _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
78def 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
117def 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
247Source the environment using '. {}' to run builds from the command line.
248The 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
263def 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
270def 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
281def 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
294def 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
317def 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
343def 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
367def 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
392def 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
432def 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
478def 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
488def 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
515def 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
548def build_update(top_dir, settings, args, d):
549 build_status(top_dir, settings, args, d, update=True)
550
551def 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
564def 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
577def has_expired(expiry_date):
578 if expiry_date:
579 return datetime.datetime.now() > datetime.datetime.fromisoformat(expiry_date)
580 return False
581
582def 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
599def 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
618def 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
637def default_settings_path(top_dir):
638 return os.path.join(top_dir, 'settings.conf')
639
640def 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
656def 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
659def 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
666def 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
688def 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
693def 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
699def 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
708def 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
723def 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
736def 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
860main()
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
9import sys 9import sys
10import warnings 10import warnings
11warnings.simplefilter("default") 11warnings.simplefilter("default")
12warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*use.of.fork.*may.lead.to.deadlocks.in.the.child.*")
12sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) 13sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib'))
13from bb import fetch2 14from bb import fetch2
14import logging 15import logging
@@ -21,9 +22,14 @@ import traceback
21import queue 22import queue
22import shlex 23import shlex
23import subprocess 24import subprocess
25import fcntl
24from multiprocessing import Lock 26from multiprocessing import Lock
25from threading import Thread 27from threading import Thread
26 28
29# Remove when we have a minimum of python 3.10
30if not hasattr(fcntl, 'F_SETPIPE_SZ'):
31 fcntl.F_SETPIPE_SZ = 1031
32
27bb.utils.check_system_locale() 33bb.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
45try: 51try:
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
57worker_pipe = sys.stdout.fileno() 62worker_pipe = sys.stdout.fileno()
58bb.utils.nonblockingfd(worker_pipe) 63bb.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.
66try:
67 fcntl.fcntl(worker_pipe, fcntl.F_SETPIPE_SZ, 512 * 1024)
68except:
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
61worker_pipe_lock = None 72worker_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
122def follow_history_intersections(revisions, refs): 122def follow_history_intersections(revisions, refs):