elixir/update.py
Maxime Chretien 72571fbfd9 database: Add support for Kconfig et Devicetree files
This brings the idea of file families, each ident is identified by his
family and can be referenced in compatible families.

For exemple :
- A Kconfig ident can be referenced in a C file but not in a Devicetree file.
- A Devicetree ident is only referenced in Devicetree files.
- A C ident is only referenced in C files.

Kconfig idents are defined without the CONFIG_ at the beginning.
We add it while indexing for an easier processing of other files.

Signed-off-by: Maxime Chretien <maxime.chretien@bootlin.com>
2020-05-22 10:29:24 +02:00

342 lines
9.7 KiB
Python
Executable file

#!/usr/bin/env python3
# This file is part of Elixir, a source code cross-referencer.
#
# Copyright (C) 2017 Mikaël Bouillot
# <mikael.bouillot@bootlin.com>
#
# Elixir is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Elixir is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Elixir. If not, see <http://www.gnu.org/licenses/>.
# Throughout, an "idx" is the sequential number associated with a blob.
# This is different from that blob's Git hash.
from sys import argv
from lib import scriptLines
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)
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
new_idxes = [] # (new idxes, Event idxes ready, Event defs ready)
tags_done = False #True if all tags have been added to new_idxes
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"Tag {tag}: adding #{idx} {path}")
db.vers.put(tag, obj, sync=True)
class UpdateDefs(Thread):
def __init__(self):
Thread.__init__(self, name="UpdateDefsElixir")
def run(self):
global new_idxes, tags_done, tag_ready
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_definitions(new_idxes[self.index][0])
new_idxes[self.index][2].set() #Tell that UpdateDefs processed the tag
self.index += 1
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:
obj = data.DefList()
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)
class UpdateRefs(Thread):
def __init__(self):
Thread.__init__(self, name="UpdateRefsElixir")
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
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))
# Main
tag_buf = []
for tag in scriptLines('list-tags'):
if not db.vers.exists(tag):
tag_buf.append(tag)
num_tags = len(tag_buf)
project = lib.currentProject()
print(project + ' - found ' + str(len(tag_buf)) + ' new tags')
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()