summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbitbake/bin/bitbake-setup809
1 files changed, 809 insertions, 0 deletions
diff --git a/bitbake/bin/bitbake-setup b/bitbake/bin/bitbake-setup
new file mode 100755
index 0000000000..739474003f
--- /dev/null
+++ b/bitbake/bin/bitbake-setup
@@ -0,0 +1,809 @@
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
19
20default_registry = 'git://github.com/kanavin/bitbake-setup-configurations.git;protocol=https;branch=main;rev=main'
21
22bindir = os.path.abspath(os.path.dirname(__file__))
23sys.path[0:0] = [os.path.join(os.path.dirname(bindir), 'lib')]
24
25import bb.msg
26import bb.process
27
28logger = bb.msg.logger_create('bitbake-setup', sys.stdout)
29
30def cache_dir(top_dir):
31 return os.path.join(top_dir, '.bitbake-setup-cache')
32
33def init_bb_cache(settings, args):
34 dldir = settings["default"]["dl-dir"]
35 bb_cachedir = os.path.join(cache_dir(args.top_dir), 'bitbake-cache')
36
37 d = bb.data.init()
38 d.setVar("DL_DIR", dldir)
39 d.setVar("BB_CACHEDIR", bb_cachedir)
40 d.setVar("__BBSRCREV_SEEN", "1")
41 if args.no_network:
42 d.setVar("BB_SRCREV_POLICY", "cache")
43 bb.fetch.fetcher_init(d)
44 return d
45
46def save_bb_cache():
47 bb.fetch2.fetcher_parse_save()
48 bb.fetch2.fetcher_parse_done()
49
50def get_config_name(config):
51 suffix = '.conf.json'
52 config_file = os.path.basename(config)
53 if config_file.endswith(suffix):
54 return config_file[:-len(suffix)]
55 else:
56 raise Exception("Config file {} does not end with {}, please rename the file.".format(config, suffix))
57
58def write_config(config, config_dir):
59 with open(os.path.join(config_dir, "config-upstream.json"),'w') as s:
60 json.dump(config, s, sort_keys=True, indent=4)
61
62def commit_config(config_dir):
63 bb.process.run("git -C {} add .".format(config_dir))
64 bb.process.run("git -C {} commit --no-verify -a -m 'Configuration at {}'".format(config_dir, time.asctime()))
65
66def _write_layer_list(dest, repodirs):
67 layers = []
68 for r in repodirs:
69 for root, dirs, files in os.walk(os.path.join(dest,r)):
70 if os.path.basename(root) == 'conf' and 'layer.conf' in files:
71 layers.append(os.path.relpath(os.path.dirname(root), dest))
72 layers_f = os.path.join(dest, ".oe-layers.json")
73 with open(layers_f, 'w') as f:
74 json.dump({"version":"1.0","layers":layers}, f, sort_keys=True, indent=4)
75
76def checkout_layers(layers, layerdir, d):
77 repodirs = []
78 oesetupbuild = None
79 print("Fetching layer/tool repositories into {}".format(layerdir))
80 for r_name in layers:
81 r_data = layers[r_name]
82 repodir = r_data["path"]
83 repodirs.append(repodir)
84
85 r_remote = r_data['git-remote']
86 rev = r_remote['rev']
87 remotes = r_remote['remotes']
88
89 for remote in remotes:
90 type,host,path,user,pswd,params = bb.fetch.decodeurl(remotes[remote]["uri"])
91 fetchuri = bb.fetch.encodeurl(('git',host,path,user,pswd,params))
92 print(" {}".format(r_name))
93 fetcher = bb.fetch.Fetch(["{};protocol={};rev={};nobranch=1;destsuffix={}".format(fetchuri,type,rev,repodir)], d)
94 do_fetch(fetcher, layerdir)
95
96 if os.path.exists(os.path.join(layerdir, repodir, 'scripts/oe-setup-build')):
97 oesetupbuild = os.path.join(layerdir, repodir, 'scripts/oe-setup-build')
98 oeinitbuildenv = os.path.join(layerdir, repodir, 'oe-init-build-env')
99
100 print(" ")
101 _write_layer_list(layerdir, repodirs)
102
103 if oesetupbuild:
104 links = {'setup-build': oesetupbuild, 'oe-scripts': os.path.dirname(oesetupbuild), 'init-build-env': oeinitbuildenv}
105 for l,t in links.items():
106 symlink = os.path.join(layerdir, l)
107 if os.path.lexists(symlink):
108 os.remove(symlink)
109 os.symlink(os.path.relpath(t,layerdir),symlink)
110
111def setup_bitbake_build(bitbake_config, layerdir, builddir):
112 def _setup_build_conf(layers, build_conf_dir):
113 os.makedirs(build_conf_dir)
114 layers_s = "\n".join([" {} \\".format(os.path.join(layerdir,l)) for l in layers])
115 bblayers_conf = """BBLAYERS ?= " \\
116{}
117 "
118""".format(layers_s)
119 with open(os.path.join(build_conf_dir, "bblayers.conf"), 'w') as f:
120 f.write(bblayers_conf)
121
122 local_conf = """#
123# This file is intended for local configuration tweaks.
124#
125# If you would like to publish and share changes made to this file,
126# it is recommended to put them into a distro config, or to create
127# layer fragments from changes made here.
128#
129"""
130 with open(os.path.join(build_conf_dir, "local.conf"), 'w') as f:
131 f.write(local_conf)
132
133 with open(os.path.join(build_conf_dir, "templateconf.cfg"), 'w') as f:
134 f.write("")
135
136 with open(os.path.join(build_conf_dir, "conf-summary.txt"), 'w') as f:
137 f.write(bitbake_config["description"] + "\n")
138
139 with open(os.path.join(build_conf_dir, "conf-notes.txt"), 'w') as f:
140 f.write("")
141
142 def _make_init_build_env(builddir, initbuildenv):
143 cmd = ". {} {}".format(initbuildenv, builddir)
144 initbuild_in_builddir = os.path.join(builddir, 'init-build-env')
145 with open(initbuild_in_builddir, 'w') as f:
146 f.write(cmd)
147
148 bitbake_builddir = os.path.join(builddir, "build")
149 print("Setting up bitbake configuration in\n {}\n".format(bitbake_builddir))
150
151 template = bitbake_config.get("oe-template")
152 layers = bitbake_config.get("bb-layers")
153 if not template and not layers:
154 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.")
155 return
156 oesetupbuild = os.path.join(layerdir, 'setup-build')
157 if template and not os.path.exists(oesetupbuild):
158 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))
159
160 bitbake_confdir = os.path.join(bitbake_builddir, 'conf')
161 backup_bitbake_confdir = bitbake_confdir + "-backup.{}".format(time.strftime("%Y%m%d%H%M%S"))
162 if os.path.exists(bitbake_confdir):
163 os.rename(bitbake_confdir, backup_bitbake_confdir)
164
165 if layers:
166 _setup_build_conf(layers, bitbake_confdir)
167
168 if template:
169 bb.process.run("{} setup -c {} -b {} --no-shell".format(oesetupbuild, template, bitbake_builddir))
170 else:
171 initbuildenv = os.path.join(layerdir, 'init-build-env')
172 if not os.path.exists(initbuildenv):
173 print("Could not find oe-init-build-env in any of the layers; please use another mechanism to initialize the bitbake environment")
174 return
175 _make_init_build_env(bitbake_builddir, os.path.realpath(initbuildenv))
176
177 siteconf_symlink = os.path.join(bitbake_confdir, "site.conf")
178 siteconf = os.path.normpath(os.path.join(builddir, '..', "site.conf"))
179 if os.path.lexists(siteconf_symlink):
180 os.remove(symlink)
181 os.symlink(os.path.relpath(siteconf, bitbake_confdir) ,siteconf_symlink)
182
183
184 init_script = os.path.join(bitbake_builddir, "init-build-env")
185 shell = "bash"
186 fragments = bitbake_config.get("oe-fragments", []) + sorted(bitbake_config.get("oe-fragment-choices",{}).values())
187 if fragments:
188 bb.process.run("{} -c '. {} && bitbake-config-build enable-fragment {}'".format(shell, init_script, " ".join(fragments)))
189
190 if os.path.exists(backup_bitbake_confdir):
191 bitbake_config_diff = get_diff(backup_bitbake_confdir, bitbake_confdir)
192 if bitbake_config_diff:
193 print("Existing bitbake configuration directory renamed to {}".format(backup_bitbake_confdir))
194 print("The bitbake configuration has changed:")
195 print(bitbake_config_diff)
196 else:
197 shutil.rmtree(backup_bitbake_confdir)
198
199 print("This bitbake configuration provides:\n {}\n".format(bitbake_config["description"]))
200
201 readme = """{}\n\nAdditional information is in {} and {}\n
202Source the environment using '. {}' to run builds from the command line.
203The bitbake configuration files (local.conf, bblayers.conf and more) can be found in {}/conf
204""".format(
205 bitbake_config["description"],
206 os.path.join(bitbake_builddir,'conf/conf-summary.txt'),
207 os.path.join(bitbake_builddir,'conf/conf-notes.txt'),
208 init_script,
209 bitbake_builddir
210 )
211 readme_file = os.path.join(bitbake_builddir, "README")
212 with open(readme_file, 'w') as f:
213 f.write(readme)
214 print("Usage instructions and additional information are in\n {}\n".format(readme_file))
215 print("The bitbake configuration files (local.conf, bblayers.conf and more) can be found in\n {}/conf\n".format(bitbake_builddir))
216 print("To run builds, source the environment using\n source {}".format(init_script))
217
218def get_registry_config(registry_path, id):
219 for root, dirs, files in os.walk(registry_path):
220 for f in files:
221 if f.endswith('.conf.json') and id == get_config_name(f):
222 return os.path.join(root, f)
223 raise Exception("Unable to find {} in available configurations; use 'list' sub-command to see what is available".format(id))
224
225def update_build(config, confdir, builddir, layerdir, d):
226 layer_config = config["data"]["sources"]
227 layer_overrides = config["source-overrides"]["sources"]
228 for k,v in layer_overrides.items():
229 if k in layer_config:
230 layer_config[k]["git-remote"] = v["git-remote"]
231 checkout_layers(layer_config, layerdir, d)
232 bitbake_config = config["bitbake-config"]
233 setup_bitbake_build(bitbake_config, layerdir, builddir)
234
235def int_input(allowed_values):
236 n = None
237 while n is None:
238 try:
239 n = int(input())
240 except ValueError:
241 print('Not a valid number, please try again:')
242 continue
243 if n not in allowed_values:
244 print('Number {} not one of {}, please try again:'.format(n, allowed_values))
245 n = None
246 return n
247
248def flatten_bitbake_configs(configs):
249 def merge_configs(c1,c2):
250 c_merged = {}
251 for k,v in c2.items():
252 if k not in c1.keys():
253 c_merged[k] = v
254 for k,v in c1.items():
255 if k not in c2.keys():
256 c_merged[k] = v
257 else:
258 c_merged[k] = c1[k] + c2[k]
259 del c_merged['configurations']
260 return c_merged
261
262 flattened_configs = []
263 for c in configs:
264 if 'configurations' not in c:
265 flattened_configs.append(c)
266 else:
267 for sub_c in flatten_bitbake_configs(c['configurations']):
268 flattened_configs.append(merge_configs(c, sub_c))
269 return flattened_configs
270
271def choose_bitbake_config(configs, parameters, non_interactive):
272 flattened_configs = flatten_bitbake_configs(configs)
273 configs_dict = {i["name"]:i for i in flattened_configs}
274
275 if parameters:
276 config_id = parameters[0]
277 if config_id not in configs_dict:
278 raise Exception("Bitbake configuration {} not found; replace with one of {}".format(config_id, configs_dict))
279 return configs_dict[config_id]
280
281 enumerated_configs = list(enumerate(flattened_configs))
282 if len(enumerated_configs) == 1:
283 only_config = flattened_configs[0]
284 print("\nSelecting the only available bitbake configuration {}".format(only_config["name"]))
285 return only_config
286
287 if non_interactive:
288 raise Exception("Unable to choose from bitbake configurations in non-interactive mode: {}".format(configs_dict))
289
290 print("\nAvailable bitbake configurations:")
291 for n, config_data in enumerated_configs:
292 print("{}. {}\t{}".format(n, config_data["name"], config_data["description"]))
293 print("\nPlease select one of the above bitbake configurations by its number:")
294 config_n = int_input([i[0] for i in enumerated_configs])
295 return flattened_configs[config_n]
296
297def choose_config(configs, non_interactive):
298 not_expired_configs = [k for k in configs.keys() if not has_expired(configs[k].get("expires", None))]
299 config_list = list(enumerate(not_expired_configs))
300 if len(config_list) == 1:
301 only_config = config_list[0][1]
302 print("\nSelecting the only available configuration {}\n".format(only_config))
303 return only_config
304
305 if non_interactive:
306 raise Exception("Unable to choose from configurations in non-interactive mode: {}".format(not_expired_configs))
307
308 print("\nAvailable configurations:")
309 for n, config_name in config_list:
310 config_data = configs[config_name]
311 expiry_date = config_data.get("expires", None)
312 config_desc = config_data["description"]
313 if expiry_date:
314 print("{}. {}\t{} (supported until {})".format(n, config_name, config_desc, expiry_date))
315 else:
316 print("{}. {}\t{}".format(n, config_name, config_desc))
317 print("\nPlease select one of the above configurations by its number:")
318 config_n = int_input([i[0] for i in config_list])
319 return config_list[config_n][1]
320
321def choose_fragments(possibilities, parameters, non_interactive):
322 choices = {}
323 for k,v in possibilities.items():
324 choice = [o for o in v["options"] if o in parameters]
325 if len(choice) > 1:
326 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))
327 if len(choice) == 1:
328 choices[k] = choice[0]
329 continue
330
331 if non_interactive:
332 raise Exception("Unable to choose from options in non-interactive mode: {}".format(v["options"]))
333
334 print("\n" + v["description"] + ":")
335 options_enumerated = list(enumerate(v["options"]))
336 for n,o in options_enumerated:
337 print("{}. {}".format(n, o))
338 print("\nPlease select one of the above options by its number:")
339 option_n = int_input([i[0] for i in options_enumerated])
340 choices[k] = options_enumerated[option_n][1]
341 return choices
342
343def obtain_config(settings, args, source_overrides, d):
344 if args.config:
345 config_id = args.config[0]
346 config_parameters = args.config[1:]
347 if os.path.exists(config_id):
348 print("Reading configuration from local file\n {}".format(config_id))
349 upstream_config = {'type':'local',
350 'path':os.path.abspath(config_id),
351 'name':get_config_name(config_id),
352 'data':json.load(open(config_id))
353 }
354 elif config_id.startswith("http://") or config_id.startswith("https://"):
355 print("Reading configuration from network URI\n {}".format(config_id))
356 import urllib.request
357 with urllib.request.urlopen(config_id) as f:
358 upstream_config = {'type':'network','uri':config_id,'name':get_config_name(config_id),'data':json.load(f)}
359 else:
360 print("Looking up config {} in configuration registry".format(config_id))
361 registry_path = update_registry(settings["default"]["registry"], cache_dir(args.top_dir), d)
362 registry_configs = list_registry(registry_path, with_expired=True)
363 if config_id not in registry_configs:
364 raise Exception("Config {} not found in configuration registry, re-run 'init' without parameters to choose from available configurations.".format(config_id))
365 upstream_config = {'type':'registry','registry':settings["default"]["registry"],'name':config_id,'data':json.load(open(get_registry_config(registry_path,config_id)))}
366 expiry_date = upstream_config['data'].get("expires", None)
367 if has_expired(expiry_date):
368 print("This configuration is no longer supported after {}. Please consider changing to a supported configuration.".format(expiry_date))
369 else:
370 registry_path = update_registry(settings["default"]["registry"], cache_dir(args.top_dir), d)
371 registry_configs = list_registry(registry_path, with_expired=True)
372 config_id = choose_config(registry_configs, args.non_interactive)
373 config_parameters = []
374 upstream_config = {'type':'registry','registry':settings["default"]["registry"],'name':config_id,'data':json.load(open(get_registry_config(registry_path,config_id)))}
375
376 upstream_config['bitbake-config'] = choose_bitbake_config(upstream_config['data']['bitbake-setup']['configurations'], config_parameters, args.non_interactive)
377 upstream_config['bitbake-config']['oe-fragment-choices'] = choose_fragments(upstream_config['bitbake-config'].get('oe-fragments-one-of',{}), config_parameters[1:], args.non_interactive)
378 upstream_config['non-interactive-cmdline-options'] = [config_id, upstream_config['bitbake-config']['name']] + sorted(upstream_config['bitbake-config']['oe-fragment-choices'].values())
379 upstream_config['source-overrides'] = source_overrides
380 return upstream_config
381
382def init_config(settings, args, d):
383 stdout = sys.stdout
384 def handle_task_progress(event, d):
385 rate = event.rate if event.rate else ''
386 progress = event.progress if event.progress > 0 else 0
387 print("{}% {} ".format(progress, rate), file=stdout, end='\r')
388
389 source_overrides = json.load(open(args.source_overrides)) if args.source_overrides else {'sources':{}}
390 upstream_config = obtain_config(settings, args, source_overrides, d)
391 print("\nRun 'bitbake-setup init --non-interactive {}' to select this configuration non-interactively.\n".format(" ".join(upstream_config['non-interactive-cmdline-options'])))
392
393 builddir = os.path.join(os.path.abspath(args.top_dir), args.build_dir_name or "{}-{}".format(upstream_config['name']," ".join(upstream_config['non-interactive-cmdline-options'][1:]).replace(" ","-").replace("/","_")))
394 if os.path.exists(builddir):
395 print("Build already initialized in {}\nUse 'bitbake-setup status' to check if it needs to be updated or 'bitbake-setup update' to perform the update.".format(builddir))
396 return
397
398 print("Initializing a build in\n {}".format(builddir))
399 if not args.non_interactive:
400 y_or_n = input('Continue? y/n: ')
401 if y_or_n != 'y':
402 exit()
403 print()
404
405 os.makedirs(builddir)
406
407 confdir = os.path.join(builddir, "config")
408 layerdir = os.path.join(builddir, "layers")
409
410 os.makedirs(confdir)
411 os.makedirs(layerdir)
412
413 bb.process.run("git -C {} init -b main".format(confdir))
414 # Make sure commiting doesn't fail if no default git user is configured on the machine
415 bb.process.run("git -C {} config user.name bitbake-setup".format(confdir))
416 bb.process.run("git -C {} config user.email bitbake-setup@not.set".format(confdir))
417 bb.process.run("git -C {} commit --no-verify --allow-empty -m 'Initial commit'".format(confdir))
418
419 bb.event.register("bb.build.TaskProgress", handle_task_progress, data=d)
420
421 write_config(upstream_config, confdir)
422 commit_config(confdir)
423 update_build(upstream_config, confdir, builddir, layerdir, d)
424
425 bb.event.remove("bb.build.TaskProgress", None)
426
427def get_diff(file1, file2):
428 try:
429 bb.process.run('diff -uNr {} {}'.format(file1, file2))
430 except bb.process.ExecutionError as e:
431 if e.exitcode == 1:
432 return e.stdout
433 else:
434 raise e
435 return None
436
437def are_layers_changed(layers, layerdir, d):
438 changed = False
439 for r_name in layers:
440 r_data = layers[r_name]
441 repodir = r_data["path"]
442
443 r_remote = r_data['git-remote']
444 rev = r_remote['rev']
445 remotes = r_remote['remotes']
446
447 for remote in remotes:
448 type,host,path,user,pswd,params = bb.fetch.decodeurl(remotes[remote]["uri"])
449 fetchuri = bb.fetch.encodeurl(('git',host,path,user,pswd,params))
450 fetcher = bb.fetch.FetchData("{};protocol={};rev={};nobranch=1;destsuffix={}".format(fetchuri,type,rev,repodir), d)
451 upstream_revision = fetcher.method.latest_revision(fetcher, d, 'default')
452 rev_parse_result = bb.process.run('git -C {} rev-parse HEAD'.format(os.path.join(layerdir, repodir)))
453 local_revision = rev_parse_result[0].strip()
454 if upstream_revision != local_revision:
455 changed = True
456 print('Layer repository {} checked out into {} updated revision {} from {} to {}'.format(remotes[remote]["uri"], os.path.join(layerdir, repodir), rev, local_revision, upstream_revision))
457
458 return changed
459
460def build_status(settings, args, d, update=False):
461 builddir = args.build_dir
462
463 confdir = os.path.join(builddir, "config")
464 layerdir = os.path.join(builddir, "layers")
465
466 current_upstream_config = json.load(open(os.path.join(confdir, "config-upstream.json")))
467
468 args.config = current_upstream_config['non-interactive-cmdline-options']
469 args.non_interactive = True
470 source_overrides = current_upstream_config["source-overrides"]
471 new_upstream_config = obtain_config(settings, args, source_overrides, d)
472
473 write_config(new_upstream_config, confdir)
474 config_diff = bb.process.run('git -C {} diff'.format(confdir))[0]
475
476 if config_diff:
477 print('\nConfiguration in {} has changed:\n{}'.format(builddir, config_diff))
478 if update:
479 commit_config(confdir)
480 update_build(new_upstream_config, confdir, builddir, layerdir, d)
481 else:
482 bb.process.run('git -C {} restore config-upstream.json'.format(confdir))
483 return
484
485 if are_layers_changed(current_upstream_config["data"]["sources"], layerdir, d):
486 if update:
487 update_build(current_upstream_config, confdir, builddir, layerdir, d)
488 return
489
490 print("\nConfiguration in {} has not changed.".format(builddir))
491
492def build_update(settings, args, d):
493 build_status(settings, args, d, update=True)
494
495def do_fetch(fetcher, dir):
496 # git fetcher simply dumps git output to stdout; in bitbake context that is redirected to temp/log.do_fetch
497 # and we need to set up smth similar here
498 fetchlogdir = os.path.join(dir, 'logs')
499 os.makedirs(fetchlogdir, exist_ok=True)
500 fetchlog = os.path.join(fetchlogdir, 'fetch_log.{}'.format(datetime.datetime.now().strftime("%Y%m%d%H%M%S")))
501 with open(fetchlog, 'a') as f:
502 oldstdout = sys.stdout
503 sys.stdout = f
504 fetcher.download()
505 fetcher.unpack(dir)
506 sys.stdout = oldstdout
507
508def update_registry(registry, cachedir, d):
509 registrydir = 'configurations'
510 full_registrydir = os.path.join(cachedir, registrydir)
511 print("Fetching configuration registry\n {}\ninto\n {}".format(registry, full_registrydir))
512 fetcher = bb.fetch.Fetch(["{};destsuffix={}".format(registry, registrydir)], d)
513 do_fetch(fetcher, cachedir)
514 return full_registrydir
515
516def has_expired(expiry_date):
517 if expiry_date:
518 return datetime.datetime.now() > datetime.datetime.fromisoformat(expiry_date)
519 return False
520
521def list_registry(registry_path, with_expired):
522 json_data = {}
523
524 for root, dirs, files in os.walk(registry_path):
525 for f in files:
526 if f.endswith('.conf.json'):
527 config_name = get_config_name(f)
528 config_data = json.load(open(os.path.join(root, f)))
529 config_desc = config_data["description"]
530 expiry_date = config_data.get("expires", None)
531 if expiry_date:
532 if with_expired or not has_expired(expiry_date):
533 json_data[config_name] = {"description": config_desc, "expires": expiry_date}
534 else:
535 json_data[config_name] = {"description": config_desc}
536 return json_data
537
538def list_configs(settings, args, d):
539 registry_path = update_registry(settings["default"]["registry"], cache_dir(args.top_dir), d)
540 json_data = list_registry(registry_path, args.with_expired)
541 print("\nAvailable configurations:")
542 for config_name, config_data in json_data.items():
543 expiry_date = config_data.get("expires", None)
544 config_desc = config_data["description"]
545 if expiry_date:
546 if args.with_expired or not has_expired(expiry_date):
547 print("{}\t{} (supported until {})".format(config_name, config_desc, expiry_date))
548 else:
549 print("{}\t{}".format(config_name, config_desc))
550 print("\nRun 'init' with one of the above configuration identifiers to set up a build.")
551
552 if args.write_json:
553 with open(args.write_json, 'w') as f:
554 json.dump(json_data, f, sort_keys=True, indent=4)
555 print("Available configurations written into {}".format(args.write_json))
556
557def default_settings_path(top_dir):
558 return os.path.join(top_dir, 'bitbake-setup.conf')
559
560def write_settings(top_dir, force_replace, non_interactive=True):
561 settings_path = default_settings_path(top_dir)
562 if not os.path.exists(settings_path) or force_replace:
563
564 settings = configparser.ConfigParser()
565 settings['default'] = {
566 'registry':default_registry,
567 'dl-dir':os.path.join(top_dir, '.bitbake-setup-downloads'),
568 }
569 os.makedirs(os.path.dirname(settings_path), exist_ok=True)
570
571 siteconfpath = os.path.join(top_dir, 'site.conf')
572 print('Configuration registry set to\n {}\n'.format(settings['default']['registry']))
573 print('Bitbake-setup download cache (DL_DIR) set to\n {}\n'.format(settings['default']['dl-dir']))
574 print('A new settings file will be created in\n {}\n'.format(settings_path))
575 print('A common site.conf file will be created, please edit or replace before running builds\n {}\n'.format(siteconfpath))
576 if not non_interactive:
577 y_or_n = input('Bitbake-setup will be configured with the above settings in {}, y/n: '.format(top_dir))
578 if y_or_n != 'y':
579 print("\nYou can run 'bitbake-setup install-settings' to edit them before setting up builds")
580 exit()
581 print()
582
583 if os.path.exists(settings_path):
584 backup_conf = settings_path + "-backup.{}".format(time.strftime("%Y%m%d%H%M%S"))
585 os.rename(settings_path, backup_conf)
586 print("Previous settings are in {}".format(backup_conf))
587 with open(settings_path, 'w') as settingsfile:
588 settings.write(settingsfile)
589
590 if os.path.exists(siteconfpath):
591 backup_siteconf = siteconfpath + "-backup.{}".format(time.strftime("%Y%m%d%H%M%S"))
592 os.rename(siteconfpath, backup_siteconf)
593 print("Previous settings are in {}".format(backup_siteconf))
594 with open(siteconfpath, 'w') as siteconffile:
595 siteconffile.write('# This file is intended for build host-specific bitbake settings\n')
596
597def load_settings(top_dir, non_interactive):
598 # This creates a new settings file if it does not yet exist
599 write_settings(top_dir, force_replace=False, non_interactive=non_interactive)
600
601 settings_path = default_settings_path(top_dir)
602 settings = configparser.ConfigParser()
603 print('Loading settings from\n {}\n'.format(settings_path))
604 settings.read([settings_path])
605 return settings
606
607def global_settings_path(args):
608 return args.global_settings if args.global_settings else os.path.join(os.path.expanduser('~'), '.config', 'bitbake-setup', 'config')
609
610def write_global_settings(settings_path, force_replace, non_interactive=True):
611 if not os.path.exists(settings_path) or force_replace:
612
613 settings = configparser.ConfigParser()
614 settings['default'] = {
615 'top-dir-prefix':os.path.expanduser('~'),
616 'top-dir-name':'bitbake-builds'
617 }
618 os.makedirs(os.path.dirname(settings_path), exist_ok=True)
619 print('Configuring global settings in\n {}\n'.format(settings_path))
620 print('Top directory prefix (where all top level directories are created) set to\n {}\n'.format(settings['default']['top-dir-prefix']))
621 print('Top directory name (this is added to the top directory prefix to form a top directory where builds are set up) set to\n {}\n'.format(settings['default']['top-dir-name']))
622 if not non_interactive:
623 y_or_n = input('Write out the global settings as specified above (y/n)? ')
624 if y_or_n != 'y':
625 print("\nYou can run 'bitbake-setup install-global-settings' to edit them before setting up builds")
626 exit()
627 print()
628
629 if os.path.exists(settings_path):
630 backup_conf = settings_path + "-backup.{}".format(time.strftime("%Y%m%d%H%M%S"))
631 os.rename(settings_path, backup_conf)
632 print("Previous global settings are in {}".format(backup_conf))
633 with open(settings_path, 'w') as settingsfile:
634 settings.write(settingsfile)
635
636def load_global_settings(settings_path, non_interactive):
637 # This creates a new settings file if it does not yet exist
638 write_global_settings(settings_path, force_replace=False, non_interactive=non_interactive)
639
640 settings = configparser.ConfigParser()
641 print('Loading global settings from\n {}\n'.format(settings_path))
642 settings.read([settings_path])
643 return settings
644
645def change_settings(top_dir, new_settings):
646 settings = load_settings(top_dir, non_interactive=True)
647 for section, section_settings in new_settings.items():
648 for setting, value in section_settings.items():
649 settings[section][setting] = value
650 print("Setting '{}' in section '{}' is changed to '{}'".format(setting, section, value))
651
652 settings_path = default_settings_path(top_dir)
653 with open(settings_path, 'w') as settingsfile:
654 settings.write(settingsfile)
655 print("New settings written to {}".format(settings_path))
656 return settings
657
658def change_global_settings(settings_path, new_settings):
659 settings = load_global_settings(settings_path, non_interactive=True)
660 for section, section_settings in new_settings.items():
661 for setting, value in section_settings.items():
662 settings[section][setting] = value
663 print("Setting '{}' in section '{}' is changed to '{}'".format(setting, section, value))
664
665 with open(settings_path, 'w') as settingsfile:
666 settings.write(settingsfile)
667 print("New global settings written to {}".format(settings_path))
668 return settings
669
670def get_build_dir_via_bbpath():
671 bbpath = os.environ.get('BBPATH')
672 if bbpath:
673 bitbake_dir = os.path.normpath(bbpath.split(':')[0])
674 if os.path.exists(os.path.join(bitbake_dir,'init-build-env')):
675 build_dir = os.path.dirname(bitbake_dir)
676 return build_dir
677 return None
678
679def get_top_dir(args, global_settings):
680 build_dir_via_bbpath = get_build_dir_via_bbpath()
681 if build_dir_via_bbpath:
682 top_dir = os.path.dirname(build_dir_via_bbpath)
683 if os.path.exists(default_settings_path(top_dir)):
684 return top_dir
685
686 if hasattr(args, 'build_dir'):
687 # commands without --top-dir-prefix/name arguments (status, update) still need to know where
688 # the top dir is, but it should be auto-deduced as parent of args.build_dir
689 top_dir = os.path.dirname(os.path.normpath(args.build_dir))
690 return top_dir
691
692 top_dir_prefix = args.top_dir_prefix if args.top_dir_prefix else global_settings['default']['top-dir-prefix']
693 top_dir_name = args.top_dir_name if args.top_dir_name else global_settings['default']['top-dir-name']
694 return os.path.join(top_dir_prefix, top_dir_name)
695
696def main():
697 def add_top_dir_arg(parser):
698 parser.add_argument('--top-dir-prefix', help='Top level directory prefix. This is where all top level directories are created.')
699 parser.add_argument('--top-dir-name', help='Top level directory name. Together with the top directory prefix this forms a top directory where builds are set up and downloaded configurations and layers are cached for reproducibility and offline builds.')
700
701 def add_build_dir_arg(parser):
702 build_dir = get_build_dir_via_bbpath()
703 if build_dir:
704 parser.add_argument('--build-dir', default=build_dir, help="Path to the build, default is %(default)s via BBPATH")
705 else:
706 parser.add_argument('--build-dir', required=True, help="Path to the build")
707
708 parser = argparse.ArgumentParser(
709 description="BitBake setup utility. Run with 'init' argument to get started.",
710 epilog="Use %(prog)s <subcommand> --help to get help on a specific command"
711 )
712 parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true')
713 parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true')
714 parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR')
715 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.')
716 parser.add_argument('--global-settings', action='store', help='Path to the global settings file where defaults for top directory prefix and name can be specified')
717
718 subparsers = parser.add_subparsers()
719
720 parser_list = subparsers.add_parser('list', help='List available configurations')
721 add_top_dir_arg(parser_list)
722 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.')
723 parser_list.add_argument('--write-json', action='store', help='Write available configurations into a json file so they can be programmatically processed.')
724 parser_list.set_defaults(func=list_configs)
725
726 parser_init = subparsers.add_parser('init', help='Select a configuration and initialize a build from it')
727 add_top_dir_arg(parser_init)
728 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.")
729 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.')
730 parser_init.add_argument('--source-overrides', action='store', help='Override sources information (repositories/revisions) with values from a local json file.')
731 parser_init.add_argument('--build-dir-name', action='store', help='A custom build directory name under the top directory.')
732 parser_init.set_defaults(func=init_config)
733
734 parser_status = subparsers.add_parser('status', help='Check if the build needs to be synchronized with configuration')
735 add_build_dir_arg(parser_status)
736 parser_status.set_defaults(func=build_status)
737
738 parser_update = subparsers.add_parser('update', help='Update a build to be in sync with configuration')
739 add_build_dir_arg(parser_update)
740 parser_update.set_defaults(func=build_update)
741
742 parser_install_settings = subparsers.add_parser('install-settings', help='Write a settings file with default values into the top level directory (contains the location of build configuration registry, downloads directory and other settings specific to a top directory)')
743 add_top_dir_arg(parser_install_settings)
744 parser_install_settings.set_defaults(func=write_settings)
745
746 parser_install_global_settings = subparsers.add_parser('install-global-settings', help='Write a global settings file with default values (contains the default prefix and name of the top directory)')
747 parser_install_global_settings.set_defaults(func=write_global_settings)
748
749 parser_change_setting = subparsers.add_parser('change-setting', help='Change a setting in the settings file')
750 add_top_dir_arg(parser_change_setting)
751 parser_change_setting.add_argument('section', help="Section in a settings file, typically 'default'")
752 parser_change_setting.add_argument('key', help="Name of the setting")
753 parser_change_setting.add_argument('value', help="Value of the setting")
754 parser_change_setting.set_defaults(func=change_settings)
755
756 parser_change_global_setting = subparsers.add_parser('change-global-setting', help='Change a setting in the global settings file')
757 parser_change_global_setting.add_argument('section', help="Section in a global settings file, typically 'default'")
758 parser_change_global_setting.add_argument('key', help="Name of the setting")
759 parser_change_global_setting.add_argument('value', help="Value of the setting")
760 parser_change_global_setting.set_defaults(func=change_global_settings)
761
762 args = parser.parse_args()
763
764 logging.basicConfig(stream=sys.stdout)
765 if args.debug:
766 logger.setLevel(logging.DEBUG)
767 elif args.quiet:
768 logger.setLevel(logging.ERROR)
769
770 # Need to re-run logger_create with color argument
771 # (will be the same logger since it has the same name)
772 bb.msg.logger_create('bitbake-setup', output=sys.stdout,
773 color=args.color,
774 level=logger.getEffectiveLevel())
775
776 if 'func' in args:
777 if args.func == write_global_settings:
778 write_global_settings(global_settings_path(args), force_replace=True)
779 return
780 elif args.func == change_global_settings:
781 change_global_settings(global_settings_path(args), {args.section:{args.key:args.value}})
782 return
783
784 if hasattr(args, 'build_dir'):
785 if not os.path.exists(os.path.join(args.build_dir,'build', 'init-build-env')):
786 print("Not a valid build directory: build/init-build-env does not exist in {}".format(args.build_dir))
787 return
788
789 if not hasattr(args, 'non_interactive'):
790 args.non_interactive = True
791
792 global_settings = load_global_settings(global_settings_path(args), args.non_interactive)
793 args.top_dir = get_top_dir(args, global_settings)
794
795 print('Bitbake-setup is using {} as top directory (can be changed with --top-dir-prefix/name arguments or by setting them in {}).\n'.format(args.top_dir, global_settings_path(args)))
796 if args.func == write_settings:
797 write_settings(args.top_dir, force_replace=True)
798 elif args.func == change_settings:
799 change_settings(args.top_dir, {args.section:{args.key:args.value}})
800 else:
801 settings = load_settings(args.top_dir, args.non_interactive)
802 d = init_bb_cache(settings, args)
803 args.func(settings, args, d)
804 save_bb_cache()
805 else:
806 from argparse import Namespace
807 parser.print_help()
808
809main()