Merge pull request #130 from MaximeChretien/database_update

Database update
This commit is contained in:
Michael Opdenacker 2020-05-22 18:28:06 +02:00 committed by GitHub
commit 4781abde05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 563 additions and 200 deletions

View file

@ -10,8 +10,10 @@ python:
- "3.6"
before_install:
- sudo apt-get -y install libdb-dev exuberant-ctags python3-pytest
- sudo apt-get -y install libdb-dev python3-pytest
- pip install jinja2 pygments bsddb3 falcon
- wget https://bootlin.com/pub/elixir/universal-ctags_0+git20221222-0ubuntu1_amd64.deb
- sudo dpkg -i universal-ctags_0+git20221222-0ubuntu1_amd64.deb
script:
- prove
- prove

View file

@ -79,7 +79,7 @@ For Debian
____
----
sudo apt install python3 python3-jinja2 python3-pygments python3-bsddb3 python3-falcon python3-pytest exuberant-ctags perl git apache2 libapache2-mod-wsgi-py3
sudo apt install python3 python3-jinja2 python3-pygments python3-bsddb3 python3-falcon python3-pytest universal-ctags perl git apache2 libapache2-mod-wsgi-py3
----
To enable the REST API, follow the installation instructions on https://github.com/GrahamDumpleton/mod_wsgi[`mod_wsgi`]
@ -106,6 +106,22 @@ and install it as follows:
sudo pip3 install Pygments-2.6.1.elixir-py3-none-any.whl
----
=== Kconfig identifiers support
The service on https://elixir.bootlin.com relies on a modified version of https://ctags.io/[Universal-ctags],
to enable indexation of Kconfig identifiers.
The changes have been sent upstream and should appear in future distributions.
In the meantime, you can either rebuild this package from its source on
https://github.com/MaximeChretien/ctags/tree/kconfig-parser[GitHub], or download this
https://bootlin.com/pub/elixir/universal-ctags_0+git20221222-0ubuntu1_amd64.deb[binary module]
and install it as follows:
----
sudo dpkg -i universal-ctags_0+git20221222-0ubuntu1_amd64.deb
----
== Download Elixir Project
----
@ -176,7 +192,7 @@ ____
Verify that the queries work:
$ ./query.py v4.10 ident raw_spin_unlock_irq
$ ./query.py v4.10 ident raw_spin_unlock_irq C
$ ./query.py v4.10 file /kernel/sched/clock.c
NOTE: `v4.10` can be replaced with any other tag.
@ -272,10 +288,10 @@ After configuring httpd, you can test the API usage:
== ident query
Send a get request to `/api/ident/<Project>/<Ident>?version=<version>`.
Send a get request to `/api/ident/<Project>/<Ident>?version=<version>&family=<family>`.
For example:
curl http://127.0.0.1/api/ident/barebox/cdev?version=latest
curl http://127.0.0.1/api/ident/barebox/cdev?version=latest&family=C
The response body is of the following structure:

View file

@ -29,7 +29,12 @@ class IdentGetter:
if version == 'latest':
version = query('latest')
symbol_definitions, symbol_references, symbol_doccomments_UNUSED = query('ident', version, ident)
if 'family' in req.params:
family = req.params['family']
else:
family = 'C'
symbol_definitions, symbol_references, symbol_doccomments_UNUSED = query('ident', version, ident, family)
resp.body = json.dumps(
{
'definitions': [sym.__dict__ for sym in symbol_definitions],

47
data.py
View file

@ -29,6 +29,7 @@ import errno
##################################################################################
defTypeR = {
'c': 'config',
'd': 'define',
'e': 'enum',
'E': 'enumerator',
@ -50,31 +51,43 @@ maxId = 999999999
class DefList:
'''Stores associations between a blob ID, a type (e.g., "function"),
and a line number.'''
def __init__(self, data=b''):
self.data = data
a line number and a file family.
Also stores in which families the ident exists for faster tests.'''
def __init__(self, data=b'#'):
self.data, self.families = data.split(b'#')
def iter(self, dummy=False):
for p in self.data.split(b','):
p = re.search(b'(\d*)(\w)(\d*)', p)
id, type, line = p.groups()
p = re.search(b'(\d*)(\w)(\d*)(\w)', p)
id, type, line, family = p.groups()
id = int(id)
type = defTypeR [type.decode()]
line = int(line)
yield(id, type, line)
family = family.decode()
yield(id, type, line, family)
if dummy:
yield(maxId, None, None)
yield(maxId, None, None, None)
def append(self, id, type, line):
def append(self, id, type, line, family):
if type not in defTypeD:
return
p = str(id) + defTypeD[type] + str(line)
p = str(id) + defTypeD[type] + str(line) + family
if self.data != b'':
p = ',' + p
self.data += p.encode()
def pack(self):
return self.data
return self.data + b'#' + self.families
def add_family(self, family):
family = family.encode()
if not family in self.families.split(b','):
if self.families != b'':
family = b',' + family
self.families += family
def get_families(self):
return self.families.decode().split(',')
class PathList:
'''Stores associations between a blob ID and a file path.
@ -100,7 +113,8 @@ class PathList:
return self.data
class RefList:
'''Stores a mapping from blob ID to list of lines.'''
'''Stores a mapping from blob ID to list of lines
and the corresponding family.'''
def __init__(self, data=b''):
self.data = data
@ -110,16 +124,17 @@ class RefList:
while s.tell() < size:
line = s.readline()
line = line [:-1]
b,c = line.split(b':')
b,c,d = line.split(b':')
b = int(b.decode())
c = c.decode()
yield(b, c)
d = d.decode()
yield(b, c, d)
s.close()
if dummy:
yield(maxId, None)
yield(maxId, None, None)
def append(self, id, lines):
p = str(id) + ':' + lines + '\n'
def append(self, id, lines, family):
p = str(id) + ':' + lines + ':' + family + '\n'
self.data += p.encode()
def pack(self):

View file

@ -1,7 +1,7 @@
# Elixir Python definitions for Barebox
exec(open('dtsi.py').read())
exec(open('kconfig.py').read())
exec(open('commonkconfig.py').read())
exec(open('cpppathinc.py').read())
exec(open('makefileo.py').read())
exec(open('makefiledtb.py').read())

View file

@ -0,0 +1,5 @@
# Common filters for Kconfig
exec(open('kconfig.py').read())
exec(open('kconfigidents.py').read())
exec(open('makefilekconfig.py').read())

View file

@ -1,4 +1,4 @@
# Elixir Python definitions for Coreboot
exec(open('dtsi.py').read())
exec(open('kconfig.py').read())
exec(open('commonkconfig.py').read())

View file

@ -8,11 +8,11 @@ def keep_idents(m):
def replace_idents(m):
i = idents[decode_number(m.group(1)) - 1]
return '<a href="'+version+'/ident/'+i+'">'+i+'</a>'
return '<a href="'+version+'/'+family+'/ident/'+i+'">'+i+'</a>'
ident_filters = {
'case': 'any',
'prerex': '\033\[31m(.*?)\033\[0m',
'prerex': '\033\[31m(?!CONFIG_)(.*?)\033\[0m',
'prefunc': keep_idents,
'postrex': '__KEEPIDENTS__([A-J]+)',
'postfunc': replace_idents

View file

@ -13,7 +13,7 @@ def replace_kconfig(m):
kconfig_filters = {
'case': 'filename',
'match': {'Kconfig'},
'prerex': '^(\s*)(source)(\s*)\"(.*?)\"',
'prerex': '^(\s*)(source)(\s*)\"([\w/_\.-]+)\"',
'prefunc': keep_kconfig,
'postrex': '__KEEPKCONFIG__([A-J]+)',
'postfunc': replace_kconfig

View file

@ -0,0 +1,27 @@
# Filter for kconfig identifier links
kconfigidents = []
def keep_kconfigidents(m):
kconfigidents.append(m.group(1))
return '__KEEPKCONFIGIDENTS__' + encode_number(len(kconfigidents))
def replace_kconfigidents(m):
i = kconfigidents[decode_number(m.group(1)) - 1]
n = i
#Remove the CONFIG_ when we are in a Kconfig file
if family == 'K':
n = n[7:]
return '<a href="'+version+'/K/ident/'+i+'">'+n+'</a>'
kconfigident_filters = {
'case': 'any',
'prerex': '\033\[31m(?=CONFIG_)(.*?)\033\[0m',
'prefunc': keep_kconfigidents,
'postrex': '__KEEPKCONFIGIDENTS__([A-J]+)',
'postfunc': replace_kconfigidents
}
filters.append(kconfigident_filters)

View file

@ -1,7 +1,7 @@
# Elixir Python definitions for Linux
exec(open('dtsi.py').read())
exec(open('kconfig.py').read())
exec(open('commonkconfig.py').read())
exec(open('makefileo.py').read())
exec(open('makefiledtb.py').read())
exec(open('makefiledir.py').read())

View file

@ -0,0 +1,22 @@
# Filters for Kconfig used in Makefiles
makefilekconfig = []
def keep_makefilekconfig(m):
makefilekconfig.append(m.group(1))
return '$(__KEEPMAKEFILEKCONFIG__' + encode_number(len(makefilekconfig)) + ')'
def replace_makefilekconfig(m):
i = makefilekconfig[decode_number(m.group(1)) - 1]
return '<a href="'+version+'/K/ident/'+i+'">'+i+'</a>'
makefilekconfig_filters = {
'case': 'filename',
'match': {'Makefile'},
'prerex': '\$\((CONFIG_\w+)\)',
'prefunc': keep_makefilekconfig,
'postrex': '__KEEPMAKEFILEKCONFIG__([A-J]+)',
'postfunc': replace_makefilekconfig
}
filters.append(makefilekconfig_filters)

View file

@ -1,3 +1,3 @@
# Elixir Python definitions for qemu
exec(open('kconfig.py').read())
exec(open('commonkconfig.py').read())

View file

@ -1,7 +1,7 @@
# Elixir Python definitions for U-Boot
exec(open('dtsi.py').read())
exec(open('kconfig.py').read())
exec(open('commonkconfig.py').read())
exec(open('cpppathinc.py').read())
exec(open('makefileo.py').read())
exec(open('makefiledtb.py').read())

View file

@ -1,5 +1,5 @@
# Elixir Python definitions for Zephyr
exec(open('dtsi.py').read())
exec(open('kconfig.py').read())
exec(open('commonkconfig.py').read())
exec(open('cpppathinc.py').read())

View file

@ -253,6 +253,10 @@ h2 {
padding: 0.5em;
padding-top: 0;
}
.search form {
display: flex;
flex-direction: row;
}
.search button:focus,
.search button:hover {
color: #000;
@ -262,10 +266,19 @@ h2 {
color: #000;
background: #ddd;
padding-right: 3em;
min-width: 0;
flex: 4;
}
.search input:focus {
background: #eee;
}
.search select {
font-size: 0.9em;
padding: 0.45em;
margin: 0;
min-width: 0;
flex: 1;
}
.filter {
padding: 0.5em;

View file

@ -48,14 +48,18 @@ status = 200
url = os.environ.get('REQUEST_URI') or os.environ.get('SCRIPT_URL')
# Split the URL into its components (project, version, cmd, arg)
m = search('^/([^/]*)/([^/]*)/([^/]*)(.*)$', url)
m = search('^/([^/]*)/([^/]*)(?:/([^/]))?/([^/]*)(.*)$', url)
if m:
project = m.group(1)
version = m.group(2)
version_decoded = parse.unquote(version)
cmd = m.group(3)
arg = m.group(4)
family = m.group(3)
cmd = m.group(4)
arg = m.group(5)
if family == None:
family = 'C'
basedir = os.environ['LXR_PROJ_DIR']
datadir = basedir + '/' + project + '/data'
@ -80,15 +84,16 @@ if m:
ident = arg[1:]
form = cgi.FieldStorage()
ident2 = form.getvalue('i')
family2 = form.getvalue('f')
if ident == '' and ident2:
status = 302
ident2 = parse.quote(ident2.strip())
location = '/'+project+'/'+version+'/ident/'+ident2
location = '/'+project+'/'+version+'/'+family2+'/ident/'+ident2
else:
mode = 'ident'
if not(ident and search('^[A-Za-z0-9_-]*$', ident)):
ident = ''
url = 'ident/'+ident
url = family + '/ident/' + ident
else:
status = 400
else:
@ -132,6 +137,7 @@ data = {
'project': project,
'projects': projects,
'ident': ident,
'family': family,
'breadcrumb': '<a class="project" href="'+version+'/source">/</a>'
}
@ -249,9 +255,11 @@ if mode == 'source':
import pygments.lexers
import pygments.formatters
filename, extension = os.path.splitext(path)
fname = os.path.basename(path)
filename, extension = os.path.splitext(fname)
extension = extension[1:].lower()
filename = os.path.basename(filename)
family = query('family', fname)
data['family'] = family
# Source common filter definitions
os.chdir('filters')
@ -302,7 +310,7 @@ if mode == 'source':
elif mode == 'ident':
data['title'] = ident+' identifier - '+title_suffix
symbol_definitions, symbol_references, symbol_doccomments_UNUSED = query('ident', tag, ident)
symbol_definitions, symbol_references, symbol_doccomments_UNUSED = query('ident', tag, ident, family)
print('<div class="lxrident">')
if len(symbol_definitions):

28
lib.py
View file

@ -183,6 +183,28 @@ def getDataDir():
def currentProject():
return os.path.basename(os.path.dirname(getDataDir()))
def hasSupportedExt(filename):
ext = os.path.splitext(filename)[1]
return ext.lower() in ['.c', '.cc', '.cpp', '.c++', '.cxx', '.h', '.s']
def getFileFamily(filename):
name, ext = os.path.splitext(filename)
if ext.lower() in ['.c', '.cc', '.cpp', '.c++', '.cxx', '.h', '.s'] :
return 'C' # C file family and ASM
elif ext.lower() in ['.dts', '.dtsi'] :
return 'D' # Devicetree files
elif name.lower()[:7] in ['kconfig'] and not ext.lower() in ['.rst']:
# Some files are named like Kconfig-nommu so we only check the first 7 letters
# We also exclude documentation files that can be named kconfig
return 'K' # Kconfig files
else :
return None
compatibility_list = {
'C' : ['C', 'K'],
'K' : ['K'],
'D' : ['D']
}
# Check if families are compatible
# First argument can be a list of different families
# Second argument is the key for chossing the right array in the compatibility list
def compatibleFamily(file_family, requested_family):
return any(item in file_family for item in compatibility_list[requested_family])

View file

@ -146,14 +146,24 @@ def query(cmd, *args):
version = args[0]
path = args[1]
if lib.hasSupportedExt(path):
filename = os.path.basename(path)
family = lib.getFileFamily(filename)
if family != None:
buffer = BytesIO()
tokens = scriptLines('tokenize-file', version, path)
tokens = scriptLines('tokenize-file', version, path, family)
even = True
prefix = b''
if family == 'K':
prefix = b'CONFIG_'
for tok in tokens:
even = not even
if even and db.defs.exists(tok) and lib.isIdent(tok):
tok = b'\033[31m' + tok + b'\033[0m'
tok2 = prefix + tok
if (even and db.defs.exists(tok2) and lib.isIdent(tok2)
and lib.compatibleFamily(db.defs.get(tok2).get_families(), family)):
tok = b'\033[31m' + tok2 + b'\033[0m'
else:
tok = lib.unescape(tok)
buffer.write(tok)
@ -161,12 +171,20 @@ def query(cmd, *args):
else:
return decode(script('get-file', version, path))
elif cmd == 'family':
# Get the family of a given file
filename = args[0]
return lib.getFileFamily(filename)
elif cmd == 'ident':
# Returns identifier search results
version = args[0]
ident = args[1]
family = args[2]
symbol_definitions = []
symbol_references = []
@ -196,9 +214,9 @@ def query(cmd, *args):
# values. Therefore, we can sequentially step through the defs, refs,
# and docs for each file in a version.
def_idx, def_type, def_line = next(defs_this_ident)
ref_idx, ref_lines = next(refs)
doc_idx, doc_line = next(docs)
def_idx, def_type, def_line, def_family = next(defs_this_ident)
ref_idx, ref_lines, ref_family = next(refs)
doc_idx, doc_line, doc_family = next(docs)
dBuf = []
rBuf = []
@ -207,19 +225,21 @@ def query(cmd, *args):
for file_idx, file_path in files_this_version:
# Advance defs, refs, and docs to the current file
while def_idx < file_idx:
def_idx, def_type, def_line = next(defs_this_ident)
def_idx, def_type, def_line, def_family = next(defs_this_ident)
while ref_idx < file_idx:
ref_idx, ref_lines = next(refs)
ref_idx, ref_lines, ref_family = next(refs)
while doc_idx < file_idx:
doc_idx, doc_line = next(docs)
doc_idx, doc_line, doc_family = next(docs)
# Copy information about this identifier into dBuf, rBuf, and docBuf.
while def_idx == file_idx:
dBuf.append((file_path, def_type, def_line))
def_idx, def_type, def_line = next(defs_this_ident)
if def_family == family:
dBuf.append((file_path, def_type, def_line))
def_idx, def_type, def_line, def_family = next(defs_this_ident)
if ref_idx == file_idx:
rBuf.append((file_path, ref_lines))
if lib.compatibleFamily(family, ref_family):
rBuf.append((file_path, ref_lines))
if doc_idx == file_idx: # TODO should this be a `while`?
docBuf.append((file_path, doc_line))
@ -239,8 +259,8 @@ def query(cmd, *args):
else:
return('Unknown subcommand: ' + cmd + '\n')
def cmd_ident(version, ident, **kwargs):
symbol_definitions, symbol_references, symbol_doccomments = query("ident", version, ident)
def cmd_ident(version, ident, family, **kwargs):
symbol_definitions, symbol_references, symbol_doccomments = query("ident", version, ident, family)
print("Symbol Definitions:")
for symbol_definition in symbol_definitions:
print(symbol_definition)
@ -266,6 +286,7 @@ if __name__ == "__main__":
ident_subparser = subparsers.add_parser('ident', help="Get definitions and references of an identifier")
ident_subparser.add_argument('ident', type=str, help="The name of the identifier")
ident_subparser.add_argument('family', type=str, help="The file family requested")
ident_subparser.set_defaults(func=cmd_ident)
file_subparser = subparsers.add_parser('file', help="Get a source file")

View file

@ -101,9 +101,15 @@ tokenize_file()
ref="$v:`denormalize $opt2`"
fi
if [ $opt3 = "D" ]; then #Don't cut around '-' in devicetrees
regex='s%((/\*.*?\*/|//.*?\001|[^'"'"']"(\\.|.)*?"|# *include *<.*?>|[^\w-])+)([\w-]+)?%\1\n\4\n%g'
else
regex='s%((/\*.*?\*/|//.*?\001|[^'"'"']"(\\.|.)*?"|# *include *<.*?>|\W)+)(\w+)?%\1\n\4\n%g'
fi
git cat-file blob $ref 2>/dev/null |
tr '\n' '\1' |
perl -pe 's%((/\*.*?\*/|//.*?\001|[^'"'"']"(\\.|.)*?"|# *include *<.*?>|\W)+)(\w+)?%\1\n\4\n%g' |
perl -pe "$regex" |
head -n -1
}
@ -136,12 +142,49 @@ untokenize()
}
parse_defs()
{
case $opt3 in
"C")
parse_defs_C
;;
"K")
parse_defs_K
;;
"D")
parse_defs_D
;;
esac
}
parse_defs_C()
{
tmp=`mktemp -d`
full_path=$tmp/$opt2
git cat-file blob "$opt1" > "$full_path"
ctags -x --c-kinds=+p-m "$full_path" |
grep -av "^operator " |
ctags -x --kinds-c=+p-m "$full_path" |
grep -avE "^operator |CONFIG_" |
awk '{print $1" "$2" "$3}'
rm "$full_path"
rmdir $tmp
}
parse_defs_K()
{
tmp=`mktemp -d`
full_path=$tmp/$opt2
git cat-file blob "$opt1" > "$full_path"
ctags -x --language-force=kconfig "$full_path" |
awk '{print "CONFIG_"$1" "$2" "$3}'
rm "$full_path"
rmdir $tmp
}
parse_defs_D()
{
tmp=`mktemp -d`
full_path=$tmp/$opt2
git cat-file blob "$opt1" > "$full_path"
ctags -x --language-force=dts "$full_path" |
awk '{print $1" "$2" "$3}'
rm "$full_path"
rmdir $tmp
@ -171,6 +214,7 @@ test $# -gt 0 || set help
cmd=$1
opt1=$2
opt2=$3
opt3=$4
shift
denormalize()

View file

@ -80,12 +80,12 @@ ok( (-r File::Spec->catfile($db_dir, $_)), "$_ exists" )
# Spot-check some identifiers
run_produces_ok('ident query (nonexistent)',
[$query_py, qw(v5.4 ident SOME_NONEXISTENT_IDENTIFIER_XYZZY_PLUGH)],
[$query_py, qw(v5.4 ident SOME_NONEXISTENT_IDENTIFIER_XYZZY_PLUGH C)],
[qr{^Symbol Definitions:}, qr{^Symbol References:}, qr{^\s*$}],
MUST_SUCCEED);
run_produces_ok('ident query (existent)',
[$query_py, qw(v5.4 ident i2c_acpi_notify)],
[$query_py, qw(v5.4 ident i2c_acpi_notify C)],
[qr{^Symbol Definitions:}, qr{^Symbol References:},
qr{drivers/i2c/i2c-core-acpi\.c.+\b402\b.+\bfunction\b}, # def
qr{drivers/i2c/i2c-core-acpi\.c.+\b402,439} # refs

View file

@ -45,7 +45,7 @@ ok_or_die( -d $tenv->lxr_data_dir, 'database dir exists',
# Spot-check some identifiers
run_produces_ok('doc-comment query (nonexistent)',
[$tenv->query_py, qw(v5.4 ident SOME_NONEXISTENT_IDENTIFIER_XYZZY_PLUGH)],
[$tenv->query_py, qw(v5.4 ident SOME_NONEXISTENT_IDENTIFIER_XYZZY_PLUGH C)],
[
qr{^Documented in:},
{doc => { not => qr{/} }}, # No file paths in the doc section
@ -53,7 +53,7 @@ run_produces_ok('doc-comment query (nonexistent)',
MUST_SUCCEED);
run_produces_ok('doc-comment query (existent but not documented)',
[$tenv->query_py, qw(v5.4 ident gsb_buffer)], # in drivers/i2c/i2c-core-acpi.c
[$tenv->query_py, qw(v5.4 ident gsb_buffer C)], # in drivers/i2c/i2c-core-acpi.c
[
qr{^Documented in:},
{doc => { not => qr{/} }}
@ -61,7 +61,7 @@ run_produces_ok('doc-comment query (existent but not documented)',
MUST_SUCCEED);
run_produces_ok('ident query (existent, function, documented in C file)',
[$tenv->query_py, qw(v5.4 ident i2c_acpi_get_i2c_resource)],
[$tenv->query_py, qw(v5.4 ident i2c_acpi_get_i2c_resource C)],
[
qr{^Documented in:},
{doc => qr{drivers/i2c/i2c-core-acpi\.c.+\b45\b}},
@ -69,7 +69,7 @@ run_produces_ok('ident query (existent, function, documented in C file)',
MUST_SUCCEED);
run_produces_ok('ident query (existent, function, documented in C file, #102)',
[$tenv->query_py, qw(v5.4 ident documented_function_XYZZY)],
[$tenv->query_py, qw(v5.4 ident documented_function_XYZZY C)],
[
qr{^Documented in:},
{doc => qr{issue102\.c.+\b6\b}},

View file

@ -17,7 +17,7 @@ class APITest(testing.TestCase):
self.app = create_ident_getter()
def test_identifier_not_found(self):
result = self.simulate_get('/ident/tree/SOME_NONEXISTENT_IDENTIFIER', query_string="version=latest")
result = self.simulate_get('/ident/tree/SOME_NONEXISTENT_IDENTIFIER', query_string="version=latest&family=C")
self.assertEqual(result.status_code, 200)
self.assertEqual(result.json, {'definitions': [], 'references':[]})
@ -33,14 +33,13 @@ class APITest(testing.TestCase):
self.assertEqual(result.json["description"], required_response.description)
def test_existing_identifier(self):
result_for_specific_version = self.simulate_get('/ident/tree/of_i2c_get_board_info', query_string="version=v5.4")
result_for_latest_version = self.simulate_get('/ident/tree/of_i2c_get_board_info', query_string="version=latest")
result_for_specific_version = self.simulate_get('/ident/tree/of_i2c_get_board_info', query_string="version=v5.4&family=C")
result_for_latest_version = self.simulate_get('/ident/tree/of_i2c_get_board_info', query_string="version=latest&family=C")
expected_json = {
'definitions':
[
{'path': 'drivers/i2c/i2c-core-of.c', 'line': 22, 'type': 'function'},
{'path': 'drivers/i2c/i2c-core-of.c', 'line': 62, 'type': 'variable'},
{'path': 'include/linux/i2c.h', 'line': 968, 'type': 'function'},
{'path': 'include/linux/i2c.h', 'line': 941, 'type': 'prototype'}
],
@ -55,4 +54,4 @@ class APITest(testing.TestCase):
self.assertEqual(result_for_latest_version.status_code, 200)
self.assertEqual(result_for_specific_version.json, expected_json)
self.assertEqual(result_for_latest_version.json, expected_json)
self.assertEqual(result_for_latest_version.json, expected_json)

View file

@ -23,7 +23,13 @@
{{breadcrumb}}
</div>
<div class="search">
<form method="post" action="{{version}}/ident">
<form method="post" action="{{version}}/ident">
<select name="f">
<option value="C" {% if family=="C" %} selected="selected"{% endif %}>C/CPP/ASM</option>
<option value="K" {% if family=="K" %} selected="selected"{% endif %}>Kconfig</option>
<option value="D" {% if family=="D" %} selected="selected"{% endif %}>Devicetree</option>
</select>
<input placeholder="Search Identifier" type="text" name="i" value="{{ident}}"/>
<button class="icon-search"><span class="screenreader">Go get it</span></button>
</form>

388
update.py
View file

@ -27,158 +27,316 @@ import lib
import data
import os
from data import PathList
from threading import Thread, Lock, Event, Condition
verbose = False
db = data.DB(lib.getDataDir(), readonly=False)
# Store new blobs hashed and file names (without path) for new tag
def updateBlobIDs(tag):
hash_file_lock = Lock() #Lock for db.hash and db.file
defs_lock = Lock() #Lock for db.defs
tag_ready = Condition() #Waiting for new tags
if db.vars.exists('numBlobs'):
idx = db.vars.get('numBlobs')
else:
idx = 0
new_idxes = [] # (new idxes, Event idxes ready, Event defs ready)
# Get blob hashes and associated file names (without path)
blobs = scriptLines('list-blobs', '-f', tag)
tags_done = False #True if all tags have been added to new_idxes
newIdxes = []
for blob in blobs:
hash, filename = blob.split(b' ',maxsplit=1)
if not db.blob.exists(hash):
db.blob.put(hash, idx)
db.hash.put(idx, hash)
db.file.put(idx, filename)
newIdxes.append(idx)
class UpdateIdVersion(Thread):
def __init__(self, tag_buf):
Thread.__init__(self, name="UpdateIdVersionElixir")
self.tag_buf = tag_buf
def run(self):
global new_idxes, tags_done, tag_ready
self.index = 0
for tag in self.tag_buf:
new_idxes.append((self.update_blob_ids(tag), Event(), Event()))
progress(tag.decode() + ': ' + str(len(new_idxes[self.index][0])) +
' new blobs', self.index+1)
self.update_versions(tag)
new_idxes[self.index][1].set() #Tell that the tag is ready
self.index += 1
#Wake up waiting threads
with tag_ready:
tag_ready.notify_all()
tags_done = True
def update_blob_ids(self, tag):
global hash_file_lock
if db.vars.exists('numBlobs'):
idx = db.vars.get('numBlobs')
else:
idx = 0
# Get blob hashes and associated file names (without path)
blobs = scriptLines('list-blobs', '-f', tag)
new_idxes = []
for blob in blobs:
hash, filename = blob.split(b' ',maxsplit=1)
if not db.blob.exists(hash):
db.blob.put(hash, idx)
with hash_file_lock:
db.hash.put(idx, hash)
db.file.put(idx, filename)
new_idxes.append(idx)
if verbose:
print(f"New blob #{idx} {hash}:{filename}")
idx += 1
db.vars.put('numBlobs', idx)
return new_idxes
def update_versions(self, tag):
# Get blob hashes and associated file paths
blobs = scriptLines('list-blobs', '-p', tag)
buf = []
for blob in blobs:
hash, path = blob.split(b' ', maxsplit=1)
idx = db.blob.get(hash)
buf.append((idx, path))
buf = sorted(buf)
obj = PathList()
for idx, path in buf:
obj.append(idx, path)
if verbose:
print(f"New blob #{idx} {hash}:{filename}")
idx += 1
db.vars.put('numBlobs', idx)
return newIdxes
print(f"Tag {tag}: adding #{idx} {path}")
db.vers.put(tag, obj, sync=True)
def updateVersions(tag):
# Get blob hashes and associated file paths
blobs = scriptLines('list-blobs', '-p', tag)
buf = []
for blob in blobs:
hash, path = blob.split(b' ', maxsplit=1)
idx = db.blob.get(hash)
buf.append((idx, path))
class UpdateDefs(Thread):
def __init__(self):
Thread.__init__(self, name="UpdateDefsElixir")
buf = sorted(buf)
obj = PathList()
for idx, path in buf:
obj.append(idx, path)
if verbose:
print(f"Tag {tag}: adding #{idx} {path}")
db.vers.put(tag, obj, sync=True)
def run(self):
global new_idxes, tags_done, tag_ready
def updateDefinitions(idxes):
for idx in idxes:
if (idx % 1000 == 0): progress('defs: ' + str(idx))
hash = db.hash.get(idx)
filename = db.file.get(idx)
self.index = 0
if not lib.hasSupportedExt(filename): continue
while(not (tags_done and self.index == len(new_idxes))):
if(self.index == len(new_idxes)):
#Wait for new tags
with tag_ready:
tag_ready.wait()
continue
lines = scriptLines('parse-defs', hash, filename)
for l in lines:
ident, type, line = l.split(b' ')
type = type.decode()
line = int(line.decode())
new_idxes[self.index][1].wait() #Make sure the tag is ready
if db.defs.exists(ident):
obj = db.defs.get(ident)
else:
obj = data.DefList()
self.update_definitions(new_idxes[self.index][0])
obj.append(idx, type, line)
if verbose:
print(f"def {type} {ident} in #{idx} @ {line}")
db.defs.put(ident, obj)
new_idxes[self.index][2].set() #Tell that UpdateDefs processed the tag
def updateReferences(idxes):
for idx in idxes:
if (idx % 1000 == 0): progress('refs: ' + str(idx))
hash = db.hash.get(idx)
filename = db.file.get(idx)
self.index += 1
if not lib.hasSupportedExt(filename): continue
tokens = scriptLines('tokenize-file', '-b', hash)
even = True
lineNum = 1
idents = {}
for tok in tokens:
even = not even
if even:
if db.defs.exists(tok) and lib.isIdent(tok):
if tok in idents:
idents[tok] += ',' + str(lineNum)
def update_definitions(self, idxes):
global hash_file_lock, defs_lock
for idx in idxes:
if (idx % 1000 == 0): progress('defs: ' + str(idx), self.index+1)
with hash_file_lock:
hash = db.hash.get(idx)
filename = db.file.get(idx)
family = lib.getFileFamily(filename);
if family == None: continue
lines = scriptLines('parse-defs', hash, filename, family)
for l in lines:
ident, type, line = l.split(b' ')
type = type.decode()
line = int(line.decode())
with defs_lock:
if db.defs.exists(ident):
obj = db.defs.get(ident)
else:
idents[tok] = str(lineNum)
else:
lineNum += tok.count(b'\1')
obj = data.DefList()
for ident, lines in idents.items():
if db.refs.exists(ident):
obj = db.refs.get(ident)
else:
obj = data.RefList()
obj.add_family(family)
obj.append(idx, type, line, family)
if verbose:
print(f"def {type} {ident} in #{idx} @ {line}")
with defs_lock:
db.defs.put(ident, obj)
obj.append(idx, lines)
if verbose:
print(f"ref: {ident} in #{idx} @ {lines}")
db.refs.put(ident, obj)
def updateDocComments(idxes):
for idx in idxes:
if (idx % 1000 == 0): progress('docs: ' + str(idx))
hash = db.hash.get(idx)
filename = db.file.get(idx)
class UpdateRefs(Thread):
def __init__(self):
Thread.__init__(self, name="UpdateRefsElixir")
if not lib.hasSupportedExt(filename): continue
def run(self):
global new_idxes, tags_done
lines = scriptLines('parse-docs', hash, filename)
for l in lines:
ident, line = l.split(b' ')
line = int(line.decode())
self.index = 0
if db.docs.exists(ident):
obj = db.docs.get(ident)
else:
obj = data.RefList()
while(not (tags_done and self.index == len(new_idxes))):
if(self.index == len(new_idxes)):
#Wait for new tags
with tag_ready:
tag_ready.wait()
continue
obj.append(idx, str(line))
if verbose:
print(f"doc: {ident} in #{idx} @ {line}")
db.docs.put(ident, obj)
new_idxes[self.index][1].wait() #Make sure the tag is ready
new_idxes[self.index][2].wait() #Make sure UpdateDefs processed the tag
self.update_references(new_idxes[self.index][0])
self.index += 1
def update_references(self, idxes):
global hash_file_lock, defs_lock
for idx in idxes:
if (idx % 1000 == 0): progress('refs: ' + str(idx), self.index+1)
with hash_file_lock:
hash = db.hash.get(idx)
filename = db.file.get(idx)
family = lib.getFileFamily(filename)
if family == None: continue
prefix = b''
# Kconfig values are saved as CONFIG_<value>
if family == 'K':
prefix = b'CONFIG_'
tokens = scriptLines('tokenize-file', '-b', hash, family)
even = True
line_num = 1
idents = {}
for tok in tokens:
even = not even
if even:
tok = prefix + tok
with defs_lock:
if db.defs.exists(tok) and lib.isIdent(tok):
if tok in idents:
idents[tok] += ',' + str(line_num)
else:
idents[tok] = str(line_num)
else:
line_num += tok.count(b'\1')
for ident, lines in idents.items():
if db.refs.exists(ident):
obj = db.refs.get(ident)
else:
obj = data.RefList()
obj.append(idx, lines, family)
if verbose:
print(f"ref: {ident} in #{idx} @ {lines}")
db.refs.put(ident, obj)
class UpdateDocs(Thread):
def __init__(self):
Thread.__init__(self, name="UpdateDocsElixir")
def run(self):
global new_idxes, tags_done
self.index = 0
while(not (tags_done and self.index == len(new_idxes))):
if(self.index == len(new_idxes)):
#Wait for new tags
with tag_ready:
tag_ready.wait()
continue
new_idxes[self.index][1].wait() #Make sure the tag is ready
self.update_doc_comments(new_idxes[self.index][0])
self.index += 1
def update_doc_comments(self, idxes):
global hash_file_lock
for idx in idxes:
if (idx % 1000 == 0): progress('docs: ' + str(idx), self.index+1)
with hash_file_lock:
hash = db.hash.get(idx)
filename = db.file.get(idx)
family = lib.getFileFamily(filename)
if family == None: continue
lines = scriptLines('parse-docs', hash, filename)
for l in lines:
ident, line = l.split(b' ')
line = int(line.decode())
if db.docs.exists(ident):
obj = db.docs.get(ident)
else:
obj = data.RefList()
obj.append(idx, str(line), family)
if verbose:
print(f"doc: {ident} in #{idx} @ {line}")
db.docs.put(ident, obj)
def progress(msg, current):
print('{} - {} ({:.0%})'.format(project, msg, current/num_tags))
def progress(msg):
print('{} - {} ({:.0%})'.format(project, msg, tagCount/numTags))
# Main
tagBuf = []
tag_buf = []
for tag in scriptLines('list-tags'):
if not db.vers.exists(tag):
tagBuf.append(tag)
tag_buf.append(tag)
numTags = len(tagBuf)
tagCount = 0
num_tags = len(tag_buf)
project = lib.currentProject()
print(project + ' - found ' + str(len(tagBuf)) + ' new tags')
print(project + ' - found ' + str(len(tag_buf)) + ' new tags')
for tag in tagBuf:
tagCount +=1
newIdxes = updateBlobIDs(tag)
progress(tag.decode() + ': ' + str(len(newIdxes)) + ' new blobs')
updateVersions(tag)
updateDefinitions(newIdxes)
updateReferences(newIdxes)
updateDocComments(newIdxes)
id_version_thread = UpdateIdVersion(tag_buf)
defs_thread = UpdateDefs()
refs_thread = UpdateRefs()
docs_thread = UpdateDocs()
#Start to process tags
id_version_thread.start()
#Wait until the first tag is ready
with tag_ready:
tag_ready.wait()
#Start remaining threads
defs_thread.start()
refs_thread.start()
docs_thread.start()
#Make sure all threads finished
id_version_thread.join()
defs_thread.join()
refs_thread.join()
docs_thread.join()

View file

@ -16,16 +16,16 @@ verbose = False
#List of test elements
project = 'linux'
versions = ['latest', 'v5.6.2']
idents = [ 'loopback',
'devm_register_reboot_notifier',
'notrace',
'arch_local_irq_restore',
'blk_queue_dma_alignment',
'spinlock_t',
'max',
'task_struct',
'eth_header',
'sk_buff' ]
idents = [ ('loopback', 'C'),
('devm_register_reboot_notifier', 'C'),
('notrace', 'C'),
('arch_local_irq_restore', 'C'),
('blk_queue_dma_alignment', 'C'),
('spinlock_t', 'C'),
('max', 'C'),
('task_struct', 'C'),
('eth_header', 'C'),
('sk_buff', 'C') ]
files = [ '/block/partitions/osf.c',
'/mm/kasan/quarantine.c',
@ -68,7 +68,7 @@ def get_ident(ident, version):
if version == 'latest':
version = query('latest')
return query('ident', version, ident)
return query('ident', version, ident[0], ident[1])
def get_file(path, version):
if version == 'latest':
@ -146,4 +146,4 @@ for version in versions:
print((BOLD + "Min:" + NORMAL + " {0:.6f} ms\n"
+ BOLD + "Max:" + NORMAL + " {1:.6f} ms\n"
+ BOLD + "Average:" + NORMAL + " {2:.6f} ms\n"
).format(files_min, files_max, files_average))
).format(files_min, files_max, files_average))