aboutsummaryrefslogtreecommitdiff
path: root/tools/buildman/boards.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/buildman/boards.py')
-rw-r--r--tools/buildman/boards.py259
1 files changed, 183 insertions, 76 deletions
diff --git a/tools/buildman/boards.py b/tools/buildman/boards.py
index 0bb0723..83adbf1 100644
--- a/tools/buildman/boards.py
+++ b/tools/buildman/boards.py
@@ -50,7 +50,7 @@ def try_remove(fname):
raise
-def output_is_new(output):
+def output_is_new(output, config_dir, srcdir):
"""Check if the output file is up to date.
Looks at defconfig and Kconfig files to make sure none is newer than the
@@ -59,6 +59,8 @@ def output_is_new(output):
Args:
output (str): Filename to check
+ config_dir (str): Directory containing defconfig files
+ srcdir (str): Directory containing Kconfig and MAINTAINERS files
Returns:
True if the given output file exists and is newer than any of
@@ -76,7 +78,7 @@ def output_is_new(output):
return False
raise
- for (dirpath, _, filenames) in os.walk(CONFIG_DIR):
+ for (dirpath, _, filenames) in os.walk(config_dir):
for filename in fnmatch.filter(filenames, '*_defconfig'):
if fnmatch.fnmatch(filename, '.*'):
continue
@@ -84,7 +86,7 @@ def output_is_new(output):
if ctime < os.path.getctime(filepath):
return False
- for (dirpath, _, filenames) in os.walk('.'):
+ for (dirpath, _, filenames) in os.walk(srcdir):
for filename in filenames:
if (fnmatch.fnmatch(filename, '*~') or
not fnmatch.fnmatch(filename, 'Kconfig*') and
@@ -103,7 +105,7 @@ def output_is_new(output):
if line[0] == '#' or line == '\n':
continue
defconfig = line.split()[6] + '_defconfig'
- if not os.path.exists(os.path.join(CONFIG_DIR, defconfig)):
+ if not os.path.exists(os.path.join(config_dir, defconfig)):
return False
return True
@@ -191,10 +193,10 @@ class KconfigScanner:
# 'target' is added later
}
- def __init__(self):
+ def __init__(self, srctree):
"""Scan all the Kconfig files and create a Kconfig object."""
# Define environment variables referenced from Kconfig
- os.environ['srctree'] = os.getcwd()
+ os.environ['srctree'] = srctree
os.environ['UBOOTVERSION'] = 'dummy'
os.environ['KCONFIG_OBJDIR'] = ''
self._tmpfile = None
@@ -211,40 +213,36 @@ class KconfigScanner:
if self._tmpfile:
try_remove(self._tmpfile)
- def scan(self, defconfig):
+ def scan(self, defconfig, warn_targets):
"""Load a defconfig file to obtain board parameters.
Args:
defconfig (str): path to the defconfig file to be processed
+ warn_targets (bool): True to warn about missing or duplicate
+ CONFIG_TARGET options
Returns:
- A dictionary of board parameters. It has a form of:
- {
- 'arch': <arch_name>,
- 'cpu': <cpu_name>,
- 'soc': <soc_name>,
- 'vendor': <vendor_name>,
- 'board': <board_name>,
- 'target': <target_name>,
- 'config': <config_header_name>,
- }
+ tuple: dictionary of board parameters. It has a form of:
+ {
+ 'arch': <arch_name>,
+ 'cpu': <cpu_name>,
+ 'soc': <soc_name>,
+ 'vendor': <vendor_name>,
+ 'board': <board_name>,
+ 'target': <target_name>,
+ 'config': <config_header_name>,
+ }
+ warnings (list of str): list of warnings found
"""
- # strip special prefixes and save it in a temporary file
- outfd, self._tmpfile = tempfile.mkstemp()
- with os.fdopen(outfd, 'w') as outf:
- with open(defconfig, encoding='utf-8') as inf:
- for line in inf:
- colon = line.find(':CONFIG_')
- if colon == -1:
- outf.write(line)
- else:
- outf.write(line[colon + 1:])
+ leaf = os.path.basename(defconfig)
+ expect_target, match, rear = leaf.partition('_defconfig')
+ assert match and not rear, f'{leaf} : invalid defconfig'
- self._conf.load_config(self._tmpfile)
- try_remove(self._tmpfile)
+ self._conf.load_config(defconfig)
self._tmpfile = None
params = {}
+ warnings = []
# Get the value of CONFIG_SYS_ARCH, CONFIG_SYS_CPU, ... etc.
# Set '-' if the value is empty.
@@ -255,9 +253,23 @@ class KconfigScanner:
else:
params[key] = '-'
- defconfig = os.path.basename(defconfig)
- params['target'], match, rear = defconfig.partition('_defconfig')
- assert match and not rear, f'{defconfig} : invalid defconfig'
+ # Check there is exactly one TARGET_xxx set
+ if warn_targets:
+ target = None
+ for name, sym in self._conf.syms.items():
+ if name.startswith('TARGET_') and sym.str_value == 'y':
+ tname = name[7:].lower()
+ if target:
+ warnings.append(
+ f'WARNING: {leaf}: Duplicate TARGET_xxx: {target} and {tname}')
+ else:
+ target = tname
+
+ if not target:
+ cfg_name = expect_target.replace('-', '_').upper()
+ warnings.append(f'WARNING: {leaf}: No TARGET_{cfg_name} enabled')
+
+ params['target'] = expect_target
# fix-up for aarch64
if params['arch'] == 'arm' and params['cpu'] == 'armv8':
@@ -274,7 +286,7 @@ class KconfigScanner:
else:
params['arch'] = 'riscv64'
- return params
+ return params, warnings
class MaintainersDatabase:
@@ -332,26 +344,55 @@ class MaintainersDatabase:
str: Maintainers of the board. If the board has two or more
maintainers, they are separated with colons.
"""
- if not target in self.database:
- self.warnings.append(f"WARNING: no maintainers for '{target}'")
- return ''
+ entry = self.database.get(target)
+ if entry:
+ status, maint_list = entry
+ if not status.startswith('Orphan'):
+ if len(maint_list) > 1 or (maint_list and maint_list[0] != '-'):
+ return ':'.join(maint_list)
- return ':'.join(self.database[target][1])
+ self.warnings.append(f"WARNING: no maintainers for '{target}'")
+ return ''
- def parse_file(self, fname):
+ def parse_file(self, srcdir, fname):
"""Parse a MAINTAINERS file.
Parse a MAINTAINERS file and accumulate board status and maintainers
information in the self.database dict.
+ defconfig files are used to specify the target, e.g. xxx_defconfig is
+ used for target 'xxx'. If there is no defconfig file mentioned in the
+ MAINTAINERS file F: entries, then this function does nothing.
+
+ The N: name entries can be used to specify a defconfig file using
+ wildcards.
+
Args:
+ srcdir (str): Directory containing source code (Kconfig files)
fname (str): MAINTAINERS file to be parsed
"""
+ def add_targets(linenum):
+ """Add any new targets
+
+ Args:
+ linenum (int): Current line number
+ """
+ added = False
+ if targets:
+ for target in targets:
+ self.database[target] = (status, maintainers)
+ added = True
+ if not added and (status != '-' and maintainers):
+ leaf = fname[len(srcdir) + 1:]
+ if leaf != 'MAINTAINERS':
+ self.warnings.append(
+ f'WARNING: orphaned defconfig in {leaf} ending at line {linenum + 1}')
+
targets = []
maintainers = []
status = '-'
with open(fname, encoding="utf-8") as inf:
- for line in inf:
+ for linenum, line in enumerate(inf):
# Check also commented maintainers
if line[:3] == '#M:':
line = line[1:]
@@ -360,9 +401,12 @@ class MaintainersDatabase:
maintainers.append(rest)
elif tag == 'F:':
# expand wildcard and filter by 'configs/*_defconfig'
- for item in glob.glob(rest):
+ glob_path = os.path.join(srcdir, rest)
+ for item in glob.glob(glob_path):
front, match, rear = item.partition('configs/')
- if not front and match:
+ if front.endswith('/'):
+ front = front[:-1]
+ if front == srcdir and match:
front, match, rear = rear.rpartition('_defconfig')
if match and not rear:
targets.append(front)
@@ -371,23 +415,26 @@ class MaintainersDatabase:
elif tag == 'N:':
# Just scan the configs directory since that's all we care
# about
- for dirpath, _, fnames in os.walk('configs'):
- for fname in fnames:
- path = os.path.join(dirpath, fname)
+ walk_path = os.walk(os.path.join(srcdir, 'configs'))
+ for dirpath, _, fnames in walk_path:
+ for cfg in fnames:
+ path = os.path.join(dirpath, cfg)[len(srcdir) + 1:]
front, match, rear = path.partition('configs/')
- if not front and match:
- front, match, rear = rear.rpartition('_defconfig')
- if match and not rear:
- targets.append(front)
+ if front or not match:
+ continue
+ front, match, rear = rear.rpartition('_defconfig')
+
+ # Use this entry if it matches the defconfig file
+ # without the _defconfig suffix. For example
+ # 'am335x.*' matches am335x_guardian_defconfig
+ if match and not rear and re.search(rest, front):
+ targets.append(front)
elif line == '\n':
- for target in targets:
- self.database[target] = (status, maintainers)
+ add_targets(linenum)
targets = []
maintainers = []
status = '-'
- if targets:
- for target in targets:
- self.database[target] = (status, maintainers)
+ add_targets(linenum)
class Boards:
@@ -622,39 +669,63 @@ class Boards:
return result, warnings
@classmethod
- def scan_defconfigs_for_multiprocess(cls, queue, defconfigs):
+ def scan_defconfigs_for_multiprocess(cls, srcdir, queue, defconfigs,
+ warn_targets):
"""Scan defconfig files and queue their board parameters
This function is intended to be passed to multiprocessing.Process()
constructor.
Args:
+ srcdir (str): Directory containing source code
queue (multiprocessing.Queue): The resulting board parameters are
written into this.
defconfigs (sequence of str): A sequence of defconfig files to be
scanned.
+ warn_targets (bool): True to warn about missing or duplicate
+ CONFIG_TARGET options
"""
- kconf_scanner = KconfigScanner()
+ kconf_scanner = KconfigScanner(srcdir)
for defconfig in defconfigs:
- queue.put(kconf_scanner.scan(defconfig))
+ queue.put(kconf_scanner.scan(defconfig, warn_targets))
@classmethod
- def read_queues(cls, queues, params_list):
- """Read the queues and append the data to the paramers list"""
+ def read_queues(cls, queues, params_list, warnings):
+ """Read the queues and append the data to the paramers list
+
+ Args:
+ queues (list of multiprocessing.Queue): Queues to read
+ params_list (list of dict): List to add params too
+ warnings (set of str): Set to add warnings to
+ """
for que in queues:
while not que.empty():
- params_list.append(que.get())
+ params, warn = que.get()
+ params_list.append(params)
+ warnings.update(warn)
- def scan_defconfigs(self, jobs=1):
+ def scan_defconfigs(self, config_dir, srcdir, jobs=1, warn_targets=False):
"""Collect board parameters for all defconfig files.
This function invokes multiple processes for faster processing.
Args:
+ config_dir (str): Directory containing the defconfig files
+ srcdir (str): Directory containing source code (Kconfig files)
jobs (int): The number of jobs to run simultaneously
+ warn_targets (bool): True to warn about missing or duplicate
+ CONFIG_TARGET options
+
+ Returns:
+ tuple:
+ list of dict: List of board parameters, each a dict:
+ key: 'arch', 'cpu', 'soc', 'vendor', 'board', 'target',
+ 'config'
+ value: string value of the key
+ list of str: List of warnings recorded
"""
all_defconfigs = []
- for (dirpath, _, filenames) in os.walk(CONFIG_DIR):
+ for (dirpath, _, filenames) in os.walk(config_dir):
for filename in fnmatch.filter(filenames, '*_defconfig'):
if fnmatch.fnmatch(filename, '.*'):
continue
@@ -669,18 +740,19 @@ class Boards:
que = multiprocessing.Queue(maxsize=-1)
proc = multiprocessing.Process(
target=self.scan_defconfigs_for_multiprocess,
- args=(que, defconfigs))
+ args=(srcdir, que, defconfigs, warn_targets))
proc.start()
processes.append(proc)
queues.append(que)
- # The resulting data should be accumulated to this list
+ # The resulting data should be accumulated to these lists
params_list = []
+ warnings = set()
# Data in the queues should be retrieved preriodically.
# Otherwise, the queues would become full and subprocesses would get stuck.
while any(p.is_alive() for p in processes):
- self.read_queues(queues, params_list)
+ self.read_queues(queues, params_list, warnings)
# sleep for a while until the queues are filled
time.sleep(SLEEP_TIME)
@@ -690,12 +762,12 @@ class Boards:
proc.join()
# retrieve leftover data
- self.read_queues(queues, params_list)
+ self.read_queues(queues, params_list, warnings)
- return params_list
+ return params_list, sorted(list(warnings))
@classmethod
- def insert_maintainers_info(cls, params_list):
+ def insert_maintainers_info(cls, srcdir, params_list):
"""Add Status and Maintainers information to the board parameters list.
Args:
@@ -705,16 +777,21 @@ class Boards:
list of str: List of warnings collected due to missing status, etc.
"""
database = MaintainersDatabase()
- for (dirpath, _, filenames) in os.walk('.'):
- if 'MAINTAINERS' in filenames:
- database.parse_file(os.path.join(dirpath, 'MAINTAINERS'))
+ for (dirpath, _, filenames) in os.walk(srcdir):
+ if 'MAINTAINERS' in filenames and 'tools/buildman' not in dirpath:
+ database.parse_file(srcdir,
+ os.path.join(dirpath, 'MAINTAINERS'))
for i, params in enumerate(params_list):
target = params['target']
- params['status'] = database.get_status(target)
- params['maintainers'] = database.get_maintainers(target)
+ maintainers = database.get_maintainers(target)
+ params['maintainers'] = maintainers
+ if maintainers:
+ params['status'] = database.get_status(target)
+ else:
+ params['status'] = '-'
params_list[i] = params
- return database.warnings
+ return sorted(database.warnings)
@classmethod
def format_and_output(cls, params_list, output):
@@ -750,9 +827,40 @@ class Boards:
with open(output, 'w', encoding="utf-8") as outf:
outf.write(COMMENT_BLOCK + '\n'.join(output_lines) + '\n')
+ def build_board_list(self, config_dir=CONFIG_DIR, srcdir='.', jobs=1,
+ warn_targets=False):
+ """Generate a board-database file
+
+ This works by reading the Kconfig, then loading each board's defconfig
+ in to get the setting for each option. In particular, CONFIG_TARGET_xxx
+ is typically set by the defconfig, where xxx is the target to build.
+
+ Args:
+ config_dir (str): Directory containing the defconfig files
+ srcdir (str): Directory containing source code (Kconfig files)
+ jobs (int): The number of jobs to run simultaneously
+ warn_targets (bool): True to warn about missing or duplicate
+ CONFIG_TARGET options
+
+ Returns:
+ tuple:
+ list of dict: List of board parameters, each a dict:
+ key: 'arch', 'cpu', 'soc', 'vendor', 'board', 'config',
+ 'target'
+ value: string value of the key
+ list of str: Warnings that came up
+ """
+ params_list, warnings = self.scan_defconfigs(config_dir, srcdir, jobs,
+ warn_targets)
+ m_warnings = self.insert_maintainers_info(srcdir, params_list)
+ return params_list, warnings + m_warnings
+
def ensure_board_list(self, output, jobs=1, force=False, quiet=False):
"""Generate a board database file if needed.
+ This is intended to check if Kconfig has changed since the boards.cfg
+ files was generated.
+
Args:
output (str): The name of the output file
jobs (int): The number of jobs to run simultaneously
@@ -762,12 +870,11 @@ class Boards:
Returns:
bool: True if all is well, False if there were warnings
"""
- if not force and output_is_new(output):
+ if not force and output_is_new(output, CONFIG_DIR, '.'):
if not quiet:
print(f'{output} is up to date. Nothing to do.')
return True
- params_list = self.scan_defconfigs(jobs)
- warnings = self.insert_maintainers_info(params_list)
+ params_list, warnings = self.build_board_list(CONFIG_DIR, '.', jobs)
for warn in warnings:
print(warn, file=sys.stderr)
self.format_and_output(params_list, output)