summaryrefslogtreecommitdiffstats
path: root/bitbake
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake')
-rwxr-xr-xbitbake/bin/bitbake-selftest1
-rwxr-xr-xbitbake/bin/bitbake-setup860
-rw-r--r--bitbake/default-registry/configurations/oe-nodistro.conf.json57
-rw-r--r--bitbake/default-registry/configurations/poky-master.conf.json82
-rw-r--r--bitbake/lib/bb/cooker.py5
-rw-r--r--bitbake/lib/bb/tests/setup.py358
-rw-r--r--bitbake/lib/bb/ui/knotty.py5
-rw-r--r--bitbake/lib/bb/ui/uihelper.py3
8 files changed, 1363 insertions, 8 deletions
diff --git a/bitbake/bin/bitbake-selftest b/bitbake/bin/bitbake-selftest
index d45c2d406d..fb7c57dd83 100755
--- a/bitbake/bin/bitbake-selftest
+++ b/bitbake/bin/bitbake-selftest
@@ -29,6 +29,7 @@ tests = ["bb.tests.codeparser",
29 "bb.tests.fetch", 29 "bb.tests.fetch",
30 "bb.tests.parse", 30 "bb.tests.parse",
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",
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/default-registry/configurations/oe-nodistro.conf.json b/bitbake/default-registry/configurations/oe-nodistro.conf.json
new file mode 100644
index 0000000000..7619738b1d
--- /dev/null
+++ b/bitbake/default-registry/configurations/oe-nodistro.conf.json
@@ -0,0 +1,57 @@
1{
2 "description": "OpenEmbedded - 'nodistro' basic configuration",
3 "sources": {
4 "bitbake": {
5 "git-remote": {
6 "remotes": {
7 "origin": {
8 "uri": "git://git.openembedded.org/bitbake;protocol=https"
9 }
10 },
11 "branch": "master",
12 "rev": "master"
13 },
14 "path": "bitbake"
15 },
16 "openembedded-core": {
17 "git-remote": {
18 "remotes": {
19 "origin": {
20 "uri": "git://git.openembedded.org/openembedded-core;protocol=https"
21 }
22 },
23 "branch": "master",
24 "rev": "master"
25 },
26 "path": "openembedded-core"
27 },
28 "yocto-docs": {
29 "git-remote": {
30 "remotes": {
31 "origin": {
32 "uri": "git://git.yoctoproject.org/yocto-docs;protocol=https"
33 }
34 },
35 "branch": "master",
36 "rev": "master"
37 },
38 "path": "yocto-docs"
39 }
40 },
41 "bitbake-setup": {
42 "configurations": [
43 {
44 "name": "nodistro",
45 "description": "OpenEmbedded 'nodistro'",
46 "bb-layers": ["openembedded-core/meta"],
47 "oe-fragments-one-of": {
48 "machine": {
49 "description": "Target machines",
50 "options" : ["machine/qemux86-64", "machine/qemuarm64", "machine/qemuriscv64"]
51 }
52 }
53 }
54 ]
55 },
56 "version": "1.0"
57}
diff --git a/bitbake/default-registry/configurations/poky-master.conf.json b/bitbake/default-registry/configurations/poky-master.conf.json
new file mode 100644
index 0000000000..60531ba024
--- /dev/null
+++ b/bitbake/default-registry/configurations/poky-master.conf.json
@@ -0,0 +1,82 @@
1{
2 "description": "Poky - The Yocto Project testing distribution configurations and hardware test platforms",
3 "sources": {
4 "bitbake": {
5 "git-remote": {
6 "remotes": {
7 "origin": {
8 "uri": "git://git.openembedded.org/bitbake;protocol=https"
9 }
10 },
11 "branch": "master",
12 "rev": "master"
13 },
14 "path": "bitbake"
15 },
16 "openembedded-core": {
17 "git-remote": {
18 "remotes": {
19 "origin": {
20 "uri": "git://git.openembedded.org/openembedded-core;protocol=https"
21 }
22 },
23 "branch": "master",
24 "rev": "master"
25 },
26 "path": "openembedded-core"
27 },
28 "meta-yocto": {
29 "git-remote": {
30 "remotes": {
31 "origin": {
32 "uri": "git://git.yoctoproject.org/meta-yocto;protocol=https"
33 }
34 },
35 "branch": "master",
36 "rev": "master"
37 },
38 "path": "meta-yocto"
39 },
40 "yocto-docs": {
41 "git-remote": {
42 "remotes": {
43 "origin": {
44 "uri": "git://git.yoctoproject.org/yocto-docs;protocol=https"
45 }
46 },
47 "branch": "master",
48 "rev": "master"
49 },
50 "path": "yocto-docs"
51 }
52 },
53 "bitbake-setup": {
54 "configurations": [
55 {
56 "bb-layers": ["openembedded-core/meta","meta-yocto/meta-yocto-bsp","meta-yocto/meta-poky"],
57 "oe-fragments-one-of": {
58 "machine": {
59 "description": "Target machines",
60 "options" : ["machine/qemux86-64", "machine/qemuarm64", "machine/qemuriscv64", "machine/genericarm64", "machine/genericx86-64"]
61 },
62 "distro": {
63 "description": "Distribution configuration variants",
64 "options" : ["distro/poky", "distro/poky-altcfg", "distro/poky-tiny"]
65 }
66 },
67 "configurations": [
68 {
69 "name": "poky",
70 "description": "Poky - The Yocto Project testing distribution"
71 },
72 {
73 "name": "poky-with-sstate",
74 "description": "Poky - The Yocto Project testing distribution with internet sstate acceleration. Use with caution as it requires a completely robust local network with sufficient bandwidth.",
75 "oe-fragments": ["core/yocto/sstate-mirror-cdn"]
76 }
77 ]
78 }
79 ]
80 },
81 "version": "1.0"
82}
diff --git a/bitbake/lib/bb/cooker.py b/bitbake/lib/bb/cooker.py
index fb87368f17..03f262ac16 100644
--- a/bitbake/lib/bb/cooker.py
+++ b/bitbake/lib/bb/cooker.py
@@ -320,7 +320,10 @@ class BBCooker:
320 except ImportError as e: 320 except ImportError as e:
321 bb.fatal(""""Unable to use hash equivalence server at '%s' due to missing or incorrect python module: 321 bb.fatal(""""Unable to use hash equivalence server at '%s' due to missing or incorrect python module:
322%s 322%s
323Please install the needed module on the build host, or use an environment containing it (e.g a pip venv or OpenEmbedded's buildtools tarball). 323Please install the needed module on the build host, or use an environment containing it:
324 - if you are using bitbake-setup, run 'bitbake-setup install-buildtools'
325 - openembedded-core layer contains 'scripts/install-buildtools' that can also be used
326 - or set up pip venv
324You can also remove the BB_HASHSERVE_UPSTREAM setting, but this may result in significantly longer build times as bitbake will be unable to reuse prebuilt sstate artefacts.""" 327You can also remove the BB_HASHSERVE_UPSTREAM setting, but this may result in significantly longer build times as bitbake will be unable to reuse prebuilt sstate artefacts."""
325 % (upstream, repr(e))) 328 % (upstream, repr(e)))
326 except ConnectionError as e: 329 except ConnectionError as e:
diff --git a/bitbake/lib/bb/tests/setup.py b/bitbake/lib/bb/tests/setup.py
new file mode 100644
index 0000000000..e320cdf56f
--- /dev/null
+++ b/bitbake/lib/bb/tests/setup.py
@@ -0,0 +1,358 @@
1#
2# Copyright BitBake Contributors
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
7from bb.tests.fetch import FetcherTest
8import json
9
10class BitbakeSetupTest(FetcherTest):
11 def setUp(self):
12 super(BitbakeSetupTest, self).setUp()
13
14 self.registrypath = os.path.join(self.tempdir, "bitbake-setup-configurations")
15
16 os.makedirs(self.registrypath)
17 self.git_init(cwd=self.registrypath)
18 self.git('commit --allow-empty -m "Initial commit"', cwd=self.registrypath)
19
20 self.testrepopath = os.path.join(self.tempdir, "test-repo")
21 os.makedirs(self.testrepopath)
22 self.git_init(cwd=self.testrepopath)
23 self.git('commit --allow-empty -m "Initial commit"', cwd=self.testrepopath)
24
25 oeinitbuildenv = """BBPATH=$1
26export BBPATH
27PATH={}:$PATH
28""".format(os.path.join(self.testrepopath, 'scripts'))
29 self.add_file_to_testrepo('oe-init-build-env',oeinitbuildenv, script=True)
30
31 oesetupbuild = """#!/usr/bin/env python3
32import getopt
33import sys
34import os
35import shutil
36opts, args = getopt.getopt(sys.argv[2:], "c:b:", ["no-shell"])
37for option, value in opts:
38 if option == '-c':
39 template = value
40 if option == '-b':
41 builddir = value
42confdir = os.path.join(builddir, 'conf')
43os.makedirs(confdir, exist_ok=True)
44with open(os.path.join(confdir, 'conf-summary.txt'), 'w') as f:
45 f.write(template)
46shutil.copy(os.path.join(os.path.dirname(__file__), 'test-repo/test-file'), confdir)
47with open(os.path.join(builddir, 'init-build-env'), 'w') as f:
48 f.write("BBPATH={}\\nexport BBPATH\\nPATH={}:$PATH".format(builddir, os.path.join(os.path.dirname(__file__), 'test-repo/scripts')))
49"""
50 self.add_file_to_testrepo('scripts/oe-setup-build', oesetupbuild, script=True)
51
52 installbuildtools = """#!/usr/bin/env python3
53import getopt
54import sys
55import os
56
57opts, args = getopt.getopt(sys.argv[1:], "d:", ["downloads-directory="])
58for option, value in opts:
59 if option == '-d':
60 installdir = value
61
62print("Buildtools installed into {}".format(installdir))
63os.makedirs(installdir)
64"""
65 self.add_file_to_testrepo('scripts/install-buildtools', installbuildtools, script=True)
66
67 bitbakeconfigbuild = """#!/usr/bin/env python3
68import os
69import sys
70confdir = os.path.join(os.environ['BBPATH'], 'conf')
71fragment = sys.argv[2]
72with open(os.path.join(confdir, fragment), 'w') as f:
73 f.write('')
74"""
75 self.add_file_to_testrepo('scripts/bitbake-config-build', bitbakeconfigbuild, script=True)
76
77 sometargetexecutable_template = """#!/usr/bin/env python3
78import os
79print("This is {}")
80print("BBPATH is {{}}".format(os.environ["BBPATH"]))
81"""
82 for e_name in ("some-target-executable-1", "some-target-executable-2"):
83 sometargetexecutable = sometargetexecutable_template.format(e_name)
84 self.add_file_to_testrepo('scripts/{}'.format(e_name), sometargetexecutable, script=True)
85
86 def runbbsetup(self, cmd):
87 bbsetup = os.path.abspath(os.path.dirname(__file__) + "/../../../bin/bitbake-setup")
88 return bb.process.run("{} --global-settings {} {}".format(bbsetup, os.path.join(self.tempdir, 'global-config'), cmd))
89
90 def add_json_config_to_registry(self, name, rev, branch):
91 config = """
92{
93 "sources": {
94 "test-repo": {
95 "git-remote": {
96 "remotes": {
97 "origin": {
98 "uri": "file://%s"
99 }
100 },
101 "branch": "%s",
102 "rev": "%s"
103 },
104 "path": "test-repo"
105 }
106 },
107 "description": "Test configuration",
108 "bitbake-setup": {
109 "configurations": [
110 {
111 "name": "gadget",
112 "description": "Gadget build configuration",
113 "oe-template": "test-configuration-gadget",
114 "oe-fragments": ["test-fragment-1"]
115 },
116 {
117 "name": "gizmo",
118 "description": "Gizmo build configuration",
119 "oe-template": "test-configuration-gizmo",
120 "oe-fragments": ["test-fragment-2"]
121 },
122 {
123 "name": "gizmo-env-passthrough",
124 "description": "Gizmo build configuration with environment-passthrough",
125 "bb-layers": ["layerC","layerD/meta-layer"],
126 "oe-fragments": ["test-fragment-1"],
127 "bb-env-passthrough-additions": [
128 "BUILD_ID",
129 "BUILD_DATE",
130 "BUILD_SERVER"
131 ]
132 },
133 {
134 "name": "gizmo-no-fragment",
135 "description": "Gizmo no-fragment template-only build configuration",
136 "oe-template": "test-configuration-gizmo"
137 },
138 {
139 "name": "gadget-notemplate",
140 "description": "Gadget notemplate build configuration",
141 "bb-layers": ["layerA","layerB/meta-layer"],
142 "oe-fragments": ["test-fragment-1"]
143 },
144 {
145 "name": "gizmo-notemplate",
146 "description": "Gizmo notemplate build configuration",
147 "bb-layers": ["layerC","layerD/meta-layer"],
148 "oe-fragments": ["test-fragment-2"]
149 },
150 {
151 "name": "gizmo-notemplate-with-thisdir",
152 "description": "Gizmo notemplate build configuration using THISDIR",
153 "bb-layers": ["layerC","layerD/meta-layer","{THISDIR}/layerE/meta-layer"],
154 "oe-fragments": ["test-fragment-2"]
155 }
156 ]
157 },
158 "version": "1.0"
159}
160""" % (self.testrepopath, branch, rev)
161 os.makedirs(os.path.join(self.registrypath, os.path.dirname(name)), exist_ok=True)
162 with open(os.path.join(self.registrypath, name), 'w') as f:
163 f.write(config)
164 self.git('add {}'.format(name), cwd=self.registrypath)
165 self.git('commit -m "Adding {}"'.format(name), cwd=self.registrypath)
166 return json.loads(config)
167
168 def add_file_to_testrepo(self, name, content, script=False):
169 fullname = os.path.join(self.testrepopath, name)
170 os.makedirs(os.path.join(self.testrepopath, os.path.dirname(name)), exist_ok=True)
171 with open(fullname, 'w') as f:
172 f.write(content)
173 if script:
174 import stat
175 st = os.stat(fullname)
176 os.chmod(fullname, st.st_mode | stat.S_IEXEC)
177 self.git('add {}'.format(name), cwd=self.testrepopath)
178 self.git('commit -m "Adding {}"'.format(name), cwd=self.testrepopath)
179
180 def check_builddir_files(self, buildpath, test_file_content, json_config):
181 with open(os.path.join(buildpath, 'layers', 'test-repo', 'test-file')) as f:
182 self.assertEqual(f.read(), test_file_content)
183 bitbake_config = json_config["bitbake-config"]
184 bb_build_path = os.path.join(buildpath, 'build')
185 bb_conf_path = os.path.join(bb_build_path, 'conf')
186 self.assertTrue(os.path.exists(os.path.join(bb_build_path, 'init-build-env')))
187
188 if "oe-template" in bitbake_config:
189 with open(os.path.join(bb_conf_path, 'conf-summary.txt')) as f:
190 self.assertEqual(f.read(), bitbake_config["oe-template"])
191 with open(os.path.join(bb_conf_path, 'test-file')) as f:
192 self.assertEqual(f.read(), test_file_content)
193 else:
194 with open(os.path.join(bb_conf_path, 'conf-summary.txt')) as f:
195 self.assertIn(bitbake_config["description"], f.read())
196 with open(os.path.join(bb_conf_path, 'bblayers.conf')) as f:
197 bblayers = f.read()
198 for l in bitbake_config["bb-layers"]:
199 if l.startswith('{THISDIR}/'):
200 thisdir_layer = os.path.join(
201 os.path.dirname(json_config["path"]),
202 l.removeprefix("{THISDIR}/"),
203 )
204 self.assertIn(thisdir_layer, bblayers)
205 else:
206 self.assertIn(os.path.join(buildpath, "layers", l), bblayers)
207
208 if 'oe-fragment' in bitbake_config.keys():
209 for f in bitbake_config["oe-fragments"]:
210 self.assertTrue(os.path.exists(os.path.join(bb_conf_path, f)))
211
212 if 'bb-environment-passthrough' in bitbake_config.keys():
213 with open(os.path.join(bb_build_path, 'init-build-env'), 'r') as f:
214 init_build_env = f.read()
215 self.assertTrue('BB_ENV_PASSTHROUGH_ADDITIONS' in init_build_env)
216 self.assertTrue('BUILD_ID' in init_build_env)
217 self.assertTrue('BUILD_DATE' in init_build_env)
218 self.assertTrue('BUILD_SERVER' in init_build_env)
219 # a more throrough test could be to initialize a bitbake build-env, export FOO to the shell environment, set the env-passthrough on it and finally check against 'bitbake-getvar FOO'
220
221
222 def test_setup(self):
223 # unset BBPATH to ensure tests run in isolation from the existing bitbake environment
224 import os
225 if 'BBPATH' in os.environ:
226 del os.environ['BBPATH']
227
228 # check that no arguments works
229 self.runbbsetup("")
230
231 # check that --help works
232 self.runbbsetup("--help")
233
234 # set up global location for top-dir-prefix
235 out = self.runbbsetup("settings set --global default top-dir-prefix {}".format(self.tempdir))
236 settings_path = "{}/global-config".format(self.tempdir)
237 self.assertIn(settings_path, out[0])
238 self.assertIn("From section 'default' the setting 'top-dir-prefix' was changed to", out[0])
239 self.assertIn("Settings written to".format(settings_path), out[0])
240 out = self.runbbsetup("settings set --global default dl-dir {}".format(os.path.join(self.tempdir, 'downloads')))
241 self.assertIn("From section 'default' the setting 'dl-dir' was changed to", out[0])
242 self.assertIn("Settings written to".format(settings_path), out[0])
243
244 # check that writing settings works and then adjust them to point to
245 # test registry repo
246 out = self.runbbsetup("settings set default registry 'git://{};protocol=file;branch=master;rev=master'".format(self.registrypath))
247 settings_path = "{}/bitbake-builds/settings.conf".format(self.tempdir)
248 self.assertIn(settings_path, out[0])
249 self.assertIn("From section 'default' the setting 'registry' was changed to", out[0])
250 self.assertIn("Settings written to".format(settings_path), out[0])
251
252 # check that listing settings works
253 out = self.runbbsetup("settings list")
254 self.assertIn("default top-dir-prefix {}".format(self.tempdir), out[0])
255 self.assertIn("default dl-dir {}".format(os.path.join(self.tempdir, 'downloads')), out[0])
256 self.assertIn("default registry {}".format('git://{};protocol=file;branch=master;rev=master'.format(self.registrypath)), out[0])
257
258 # check that 'list' produces correct output with no configs, one config and two configs
259 out = self.runbbsetup("list")
260 self.assertNotIn("test-config-1", out[0])
261 self.assertNotIn("test-config-2", out[0])
262
263 json_1 = self.add_json_config_to_registry('test-config-1.conf.json', 'master', 'master')
264 out = self.runbbsetup("list")
265 self.assertIn("test-config-1", out[0])
266 self.assertNotIn("test-config-2", out[0])
267
268 json_2 = self.add_json_config_to_registry('config-2/test-config-2.conf.json', 'master', 'master')
269 out = self.runbbsetup("list --write-json={}".format(os.path.join(self.tempdir, "test-configs.json")))
270 self.assertIn("test-config-1", out[0])
271 self.assertIn("test-config-2", out[0])
272 with open(os.path.join(self.tempdir, "test-configs.json")) as f:
273 json_configs = json.load(f)
274 self.assertIn("test-config-1", json_configs)
275 self.assertIn("test-config-2", json_configs)
276
277 # check that init/status/update work
278 # (the latter two should do nothing and say that config hasn't changed)
279 test_file_content = 'initial\n'
280 self.add_file_to_testrepo('test-file', test_file_content)
281
282 # test-config-1 is tested as a registry config, test-config-2 as a local file
283 test_configurations = {'test-config-1': {'cmdline': 'test-config-1',
284 'buildconfigs':('gadget','gizmo',
285 'gizmo-env-passthrough',
286 'gizmo-no-fragment',
287 'gadget-notemplate','gizmo-notemplate')},
288 'test-config-2': {'cmdline': os.path.join(self.registrypath,'config-2/test-config-2.conf.json'),
289 'buildconfigs': ('gadget','gizmo',
290 'gizmo-env-passthrough',
291 'gizmo-no-fragment',
292 'gadget-notemplate','gizmo-notemplate',
293 'gizmo-notemplate-with-thisdir')}
294 }
295 for cf, v in test_configurations.items():
296 for c in v['buildconfigs']:
297 out = self.runbbsetup("init --non-interactive {} {}".format(v['cmdline'], c))
298 buildpath = os.path.join(self.tempdir, 'bitbake-builds', '{}-{}'.format(cf, c))
299 with open(os.path.join(buildpath, 'config', "config-upstream.json")) as f:
300 config_upstream = json.load(f)
301 self.check_builddir_files(buildpath, test_file_content, config_upstream)
302 os.environ['BBPATH'] = os.path.join(buildpath, 'build')
303 out = self.runbbsetup("status")
304 self.assertIn("Configuration in {} has not changed".format(buildpath), out[0])
305 out = self.runbbsetup("update")
306 self.assertIn("Configuration in {} has not changed".format(buildpath), out[0])
307
308 # install buildtools
309 out = self.runbbsetup("install-buildtools")
310 self.assertIn("Buildtools installed into", out[0])
311 self.assertTrue(os.path.exists(os.path.join(buildpath, 'buildtools')))
312
313 # change a file in the test layer repo, make a new commit and
314 # test that status/update correctly report the change and update the config
315 prev_test_file_content = test_file_content
316 test_file_content = 'modified\n'
317 self.add_file_to_testrepo('test-file', test_file_content)
318 for c in ('gadget', 'gizmo',
319 'gizmo-env-passthrough',
320 'gizmo-no-fragment',
321 'gadget-notemplate', 'gizmo-notemplate'):
322 buildpath = os.path.join(self.tempdir, 'bitbake-builds', 'test-config-1-{}'.format(c))
323 os.environ['BBPATH'] = os.path.join(buildpath, 'build')
324 out = self.runbbsetup("status")
325 self.assertIn("Layer repository file://{} checked out into {}/layers/test-repo updated revision master from".format(self.testrepopath, buildpath), out[0])
326 out = self.runbbsetup("update")
327 if c in ('gadget', 'gizmo'):
328 self.assertIn("Existing bitbake configuration directory renamed to {}/build/conf-backup.".format(buildpath), out[0])
329 self.assertIn('-{}+{}'.format(prev_test_file_content, test_file_content), out[0])
330 with open(os.path.join(buildpath, 'config', "config-upstream.json")) as f:
331 config_upstream = json.load(f)
332 self.check_builddir_files(buildpath, test_file_content, config_upstream)
333
334 # make a new branch in the test layer repo, change a file on that branch,
335 # make a new commit, update the top level json config to refer to that branch,
336 # and test that status/update correctly report the change and update the config
337 prev_test_file_content = test_file_content
338 test_file_content = 'modified-in-branch\n'
339 branch = "another-branch"
340 self.git('checkout -b {}'.format(branch), cwd=self.testrepopath)
341 self.add_file_to_testrepo('test-file', test_file_content)
342 json_1 = self.add_json_config_to_registry('test-config-1.conf.json', branch, branch)
343 for c in ('gadget', 'gizmo',
344 'gizmo-env-passthrough',
345 'gizmo-no-fragment',
346 'gadget-notemplate', 'gizmo-notemplate'):
347 buildpath = os.path.join(self.tempdir, 'bitbake-builds', 'test-config-1-{}'.format(c))
348 os.environ['BBPATH'] = os.path.join(buildpath, 'build')
349 out = self.runbbsetup("status")
350 self.assertIn("Configuration in {} has changed:".format(buildpath), out[0])
351 self.assertIn('- "rev": "master"\n+ "rev": "another-branch"', out[0])
352 out = self.runbbsetup("update")
353 if c in ('gadget', 'gizmo'):
354 self.assertIn("Existing bitbake configuration directory renamed to {}/build/conf-backup.".format(buildpath), out[0])
355 self.assertIn('-{}+{}'.format(prev_test_file_content, test_file_content), out[0])
356 with open(os.path.join(buildpath, 'config', "config-upstream.json")) as f:
357 config_upstream = json.load(f)
358 self.check_builddir_files(buildpath, test_file_content, config_upstream)
diff --git a/bitbake/lib/bb/ui/knotty.py b/bitbake/lib/bb/ui/knotty.py
index 492ea20763..00258c80ff 100644
--- a/bitbake/lib/bb/ui/knotty.py
+++ b/bitbake/lib/bb/ui/knotty.py
@@ -169,7 +169,6 @@ class TerminalFilter(object):
169 self.stdinbackup = None 169 self.stdinbackup = None
170 self.interactive = sys.stdout.isatty() 170 self.interactive = sys.stdout.isatty()
171 self.footer_present = False 171 self.footer_present = False
172 self.lastpids = []
173 self.lasttime = time.time() 172 self.lasttime = time.time()
174 self.quiet = quiet 173 self.quiet = quiet
175 174
@@ -254,7 +253,6 @@ class TerminalFilter(object):
254 return 253 return
255 activetasks = self.helper.running_tasks 254 activetasks = self.helper.running_tasks
256 failedtasks = self.helper.failed_tasks 255 failedtasks = self.helper.failed_tasks
257 runningpids = self.helper.running_pids
258 currenttime = time.time() 256 currenttime = time.time()
259 deltatime = currenttime - self.lasttime 257 deltatime = currenttime - self.lasttime
260 258
@@ -283,7 +281,7 @@ class TerminalFilter(object):
283 self._footer_buf.seek(0) 281 self._footer_buf.seek(0)
284 282
285 tasks = [] 283 tasks = []
286 for t in runningpids: 284 for t in activetasks.keys():
287 start_time = activetasks[t].get("starttime", None) 285 start_time = activetasks[t].get("starttime", None)
288 if start_time: 286 if start_time:
289 msg = "%s - %s (pid %s)" % (activetasks[t]["title"], self.elapsed(currenttime - start_time), activetasks[t]["pid"]) 287 msg = "%s - %s (pid %s)" % (activetasks[t]["title"], self.elapsed(currenttime - start_time), activetasks[t]["pid"])
@@ -358,7 +356,6 @@ class TerminalFilter(object):
358 content = "%s: %s" % (tasknum, task) 356 content = "%s: %s" % (tasknum, task)
359 print(content, file=self._footer_buf) 357 print(content, file=self._footer_buf)
360 lines = lines + self.getlines(content) 358 lines = lines + self.getlines(content)
361 self.lastpids = runningpids[:]
362 self.lastcount = self.helper.tasknumber_current 359 self.lastcount = self.helper.tasknumber_current
363 360
364 # Clear footer and Print buffer. 361 # Clear footer and Print buffer.
diff --git a/bitbake/lib/bb/ui/uihelper.py b/bitbake/lib/bb/ui/uihelper.py
index e6983bd559..a223632471 100644
--- a/bitbake/lib/bb/ui/uihelper.py
+++ b/bitbake/lib/bb/ui/uihelper.py
@@ -13,7 +13,6 @@ class BBUIHelper:
13 self.needUpdate = False 13 self.needUpdate = False
14 self.running_tasks = {} 14 self.running_tasks = {}
15 # Running PIDs preserves the order tasks were executed in 15 # Running PIDs preserves the order tasks were executed in
16 self.running_pids = []
17 self.failed_tasks = [] 16 self.failed_tasks = []
18 self.pidmap = {} 17 self.pidmap = {}
19 self.tasknumber_current = 0 18 self.tasknumber_current = 0
@@ -23,7 +22,6 @@ class BBUIHelper:
23 # PIDs are a bad idea as they can be reused before we process all UI events. 22 # PIDs are a bad idea as they can be reused before we process all UI events.
24 # We maintain a 'fuzzy' match for TaskProgress since there is no other way to match 23 # We maintain a 'fuzzy' match for TaskProgress since there is no other way to match
25 def removetid(pid, tid): 24 def removetid(pid, tid):
26 self.running_pids.remove(tid)
27 del self.running_tasks[tid] 25 del self.running_tasks[tid]
28 if self.pidmap[pid] == tid: 26 if self.pidmap[pid] == tid:
29 del self.pidmap[pid] 27 del self.pidmap[pid]
@@ -35,7 +33,6 @@ class BBUIHelper:
35 self.running_tasks[tid] = { 'title' : "mc:%s:%s %s" % (event._mc, event._package, event._task), 'starttime' : time.time(), 'pid' : event.pid } 33 self.running_tasks[tid] = { 'title' : "mc:%s:%s %s" % (event._mc, event._package, event._task), 'starttime' : time.time(), 'pid' : event.pid }
36 else: 34 else:
37 self.running_tasks[tid] = { 'title' : "%s %s" % (event._package, event._task), 'starttime' : time.time(), 'pid' : event.pid } 35 self.running_tasks[tid] = { 'title' : "%s %s" % (event._package, event._task), 'starttime' : time.time(), 'pid' : event.pid }
38 self.running_pids.append(tid)
39 self.pidmap[event.pid] = tid 36 self.pidmap[event.pid] = tid
40 self.needUpdate = True 37 self.needUpdate = True
41 elif isinstance(event, bb.build.TaskSucceeded): 38 elif isinstance(event, bb.build.TaskSucceeded):