blob: a77f8a54eea7eae3c3c64d7f7b4ddfeb73ec2ef9 [file] [log] [blame]
#
# Small helper library to manipulate Kconfig files
#
import os, re
src_line_rel = re.compile(r'^\s*source\s+(?P<src>[^\s"]*)"?\s*$')
tri_line = re.compile(r'^(?P<spc>\s+)tristate')
bool_line = re.compile(r'^(?P<spc>\s+)bool')
cfg_line = re.compile(r'^(?P<opt>config|menuconfig)\s+(?P<sym>[^\s]*)')
sel_line = re.compile(r'^(?P<spc>\s+)select\s+(?P<sym>[^\s]*)\s*$')
backport_line = re.compile(r'^\s+#(?P<key>[ch]-file|module-name)\s*(?P<name>.*)')
class ConfigTree(object):
"""
If you suppport kernel integration you should use bpid.kconfig_source_var
and set that to the the variable name you have used on your Kconfig file
for use as a directory prefix. When packaging you only need to set this on
the Kconfig sources part of backports. For integration this variable will
be used to prefix all Kconfig sources taken from the kernel. This variable
is used by Kconfig to expand to bpid.target_dir_name. All kernel Kconfig
source entries use a full path based on bpid.project_dir, when using
integration a prefix is needed on backported Kconfig entries to
help ensure that the base directory used for backported code is
bpid.target_dir and not bpid.project_dir. To help with we provide a
verification / modifier, verify_sources(), of the Kconfig entries, you
run this before working on the ConfigTree with any other helper.
Verification is only needed when integrating and if the top level Kconfig
entries that have a source entry.
"""
def __init__(self, rootfile, bpid):
self.bpid = bpid
self.rootfile = os.path.basename(rootfile)
if self.bpid.kconfig_source_var:
self.src_line = re.compile(r'^\s*source\s+"(?P<kconfig_var>' + self.bpid.kconfig_source_var_resafe + ')?/?(?P<src>[^\s"]*)"?\s*$')
self.kconfig_var_line = re.compile(r'.*(?P<kconfig_var>' + self.bpid.kconfig_source_var_resafe + ')+/+')
else:
self.src_line = re.compile(r'^\s*source\s+"(?P<src>[^\s"]*)"?\s*$')
self.kconfig_var_line = None
self.verified = False
self.need_verification = False
if self.bpid.integrate:
for l in open(os.path.join(self.bpid.target_dir, rootfile), 'r'):
m = self.src_line.match(l)
mrel = src_line_rel.match(l)
if m or mrel:
self.need_verification = True
break
def _check_relative_source(self, f, l):
#
# Although we can support relative kconfig source lines its a lot safer,
# clearer to use full paths; it also makes it easier to support / parse and
# modify kconfig entries. The kernel also uses full paths anyway but if
# a relative path is found we should consider changing that upstream to
# streamline usage of full path.
m = src_line_rel.match(l)
if m:
raise Exception('File: %s uses relative kconfig source entries (line: \'%s\'), use full path with quotes' %
(os.path.join(self.bpid.target_dir, f), l))
def _walk(self, f, first_pass=False):
if self.bpid.integrate:
if not self.bpid.kconfig_source_var and self.need_verification:
raise Exception("You enabled integration but haven't set bpid.kconfig_source_var, this seems incorrect")
if not first_pass and not self.verified and self.need_verification:
raise Exception("You must run verify_sources() first, we don't do that for you as you may want to ignore some source files")
yield f
for l in open(os.path.join(self.bpid.target_dir, f), 'r'):
m = self.src_line.match(l)
if m and os.path.exists(os.path.join(self.bpid.target_dir, m.group('src'))):
for i in self._walk(m.group('src'), first_pass=first_pass):
yield i
else:
self._check_relative_source(f, l)
def _prune_sources(self, f, ignore):
for nf in self._walk(f):
out = ''
for l in open(os.path.join(self.bpid.target_dir, nf), 'r'):
m = self.src_line.match(l)
if not m:
self._check_relative_source(nf, l)
out += l
continue
src = m.group('src')
if src in ignore or os.path.exists(os.path.join(self.bpid.target_dir, src)):
out += l
else:
out += '#' + l
outf = open(os.path.join(self.bpid.target_dir, nf), 'w')
outf.write(out)
outf.close()
def verify_sources(self, ignore=[]):
if not self.need_verification:
return
for nf in self._walk(self.rootfile, first_pass=True):
out = ''
for l in open(os.path.join(self.bpid.target_dir, nf), 'r'):
m = self.src_line.match(l)
if not m:
self._check_relative_source(nf, l)
out += l
continue
src = m.group('src')
k = self.kconfig_var_line.match(l)
if src in ignore or os.path.exists(os.path.join(self.bpid.target_dir, src)):
if k:
out += l
else:
out += 'source "%s/%s"\n' % (self.bpid.kconfig_source_var, m.group('src'))
else:
if k:
out += '# source "' + self.bpid.kconfig_source_var + '/' + src + '"\n'
else:
out += '#' + l
outf = open(os.path.join(self.bpid.target_dir, nf), 'w')
outf.write(out)
outf.close()
# Now the kconfig_var is always required from now on
self.src_line = re.compile(r'^\s*source\s+"(?P<kconfig_var>' + self.bpid.kconfig_source_var_resafe + ')+/+(?P<src>[^\s"]*)"?\s*$')
self.verified = True
def prune_sources(self, ignore=[]):
self._prune_sources(self.rootfile, ignore)
def force_tristate_modular(self):
for nf in self._walk(self.rootfile):
out = ''
for l in open(os.path.join(self.bpid.target_dir, nf), 'r'):
m = tri_line.match(l)
out += l
if m:
out += m.group('spc') + "depends on m\n"
outf = open(os.path.join(self.bpid.target_dir, nf), 'w')
outf.write(out)
outf.close()
def _mod_kconfig_line(self, l, orig_symbols):
if self.bpid.project_prefix != '':
for sym in orig_symbols:
if sym in l:
return re.sub(r' (' + sym + ')', r' ' + self.bpid.project_prefix + '\\1', l)
return l
def adjust_backported_configs(self, ignore=[], orig_symbols=[]):
m = None
old_l = None
for nf in self._walk(self.rootfile):
if os.path.join(self.bpid.target_dir, nf) in ignore:
continue
out = ''
for l in open(os.path.join(self.bpid.target_dir, nf), 'r'):
n = cfg_line.match(l)
if n:
m = n
old_l = l
continue
# We're now on the second line for the config symbol
if m:
built_in_sym = m.group('sym')
if self.bpid.project_prefix != '':
built_in_sym = re.sub(r'' + self.bpid.project_prefix + '(.*)', r'\1', m.group('sym'))
# These are things that we carry as part of our backports
# module or things we automatically copy over into our
# backports module. What we want to do is ensure that we
# always negate a backported symbol we provide with the one
# provided by the old kernel.
#
# Note that this means that since project_prefix is empty
# it likely means the kconfig getenv() trick was used to
# backport and when that's used the same symbol is used,
# that means you can't easily negate an internal symbol
# as kconfig will always apply the kconfig_prefix.
#
# XXX: only do this for symbols that have C files
# depending on it
if self.bpid.project_prefix != '' and self.bpid.project_prefix in m.group('sym'):
out += old_l
out += l
out += "\tdepends on !%s\n" % (built_in_sym)
else:
# First rewrite the upstream symbol with our prefix if
# needed
if self.bpid.project_prefix != '':
out += m.group('opt') + ' ' + self.bpid.project_prefix + m.group('sym') + '\n'
out += l
# Packaging uses the checks.h but that's a reactive
# measure, this is proactive.
if not self.bpid.integrate:
# This doesn't happen right now as packaging
# always uses an empty project prefix.
out += "\tdepends on %s!=y\n" % (built_in_sym)
else:
out += "\tdepends on !%s\n" % (built_in_sym)
else:
out += m.group('opt') + ' ' + m.group('sym') + '\n'
out += l
m = None
else:
out += self._mod_kconfig_line(l, orig_symbols)
outf = open(os.path.join(self.bpid.target_dir, nf), 'w')
outf.write(out)
outf.close()
def symbols(self):
syms = []
for nf in self._walk(self.rootfile):
for l in open(os.path.join(self.bpid.target_dir, nf), 'r'):
m = cfg_line.match(l)
if m:
syms.append(m.group('sym'))
return syms
def all_selects(self):
result = []
for nf in self._walk(self.rootfile):
for l in open(os.path.join(self.bpid.target_dir, nf), 'r'):
m = sel_line.match(l)
if m:
result.append(m.group('sym'))
return result
def modify_selects(self):
syms = self.symbols()
for nf in self._walk(self.rootfile):
out = ''
for l in open(os.path.join(self.bpid.target_dir, nf), 'r'):
m = sel_line.match(l)
if m and not m.group('sym') in syms:
if 'BPAUTO_' + m.group('sym') in syms:
out += m.group('spc') + "select BPAUTO_" + m.group('sym') + '\n'
else:
out += m.group('spc') + "depends on " + m.group('sym') + '\n'
else:
out += l
outf = open(os.path.join(self.bpid.target_dir, nf), 'w')
outf.write(out)
outf.close()
def disable_symbols(self, syms):
for nf in self._walk(self.rootfile):
out = ''
for l in open(os.path.join(self.bpid.target_dir, nf), 'r'):
m = cfg_line.match(l)
out += l
if m and m.group('sym') in syms:
out += "\tdepends on n\n"
outf = open(os.path.join(self.bpid.target_dir, nf), 'w')
outf.write(out)
outf.close()
def add_dependencies(self, deps):
for nf in self._walk(self.rootfile):
out = ''
for l in open(os.path.join(self.bpid.target_dir, nf), 'r'):
m = cfg_line.match(l)
out += l
if m:
for dep in deps.get(m.group('sym'), []):
out += "\tdepends on %s\n" % dep
outf = open(os.path.join(self.bpid.target_dir, nf), 'w')
outf.write(out)
outf.close()
def get_backport_info(filename):
"""
Return a dictionary of
CONFIG_SYMBOL => (type, C-files, H-files)
where type is 'bool' or 'tristate' and the C-files/H-files are lists
"""
f = open(filename, 'r')
result = {}
conf = None
module_name = None
# trick to always have an empty line last
def append_empty(f):
for l in f:
yield l
yield ''
for line in append_empty(f):
m = cfg_line.match(line)
if not line.strip() or m:
if conf and conf_type and (c_files or h_files):
result[conf] = (conf_type, module_name, c_files, h_files)
conf = None
conf_type = None
module_name = None
c_files = []
h_files = []
if m:
conf = m.group('sym')
continue
if not conf:
continue
m = tri_line.match(line)
if m:
conf_type = 'tristate'
continue
m = bool_line.match(line)
if m:
conf_type = 'bool'
continue
m = backport_line.match(line)
if m:
if m.group('key') == 'c-file':
c_files.append(m.group('name'))
elif m.group('key') == 'h-file':
h_files.append(m.group('name'))
elif m.group('key') == 'module-name':
module_name = m.group('name')
return result