mirror of
git://git.yoctoproject.org/poky.git
synced 2025-07-19 12:29:01 +02:00

Simplify the importone() hook: - to make it independent from the "history" mode which is client specific. - remove the "history" parameter - we want all values to be imported for binary reproducibility purposes. - using the store_value() function (which warrants you don't save the same value twice and doesn't write when you're using a read-only server) is enough. (Bitbake rev: 000704a53470ab1ead840403b5531f22ebf1fd49) Signed-off-by: Michael Opdenacker <michael.opdenacker@bootlin.com> Cc: Joshua Watt <JPEWhacker@gmail.com> Cc: Tim Orling <ticotimo@gmail.com> Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
302 lines
12 KiB
Python
302 lines
12 KiB
Python
#
|
|
# Copyright BitBake Contributors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-only
|
|
#
|
|
|
|
import logging
|
|
import os.path
|
|
import errno
|
|
import prserv
|
|
import sqlite3
|
|
|
|
from contextlib import closing
|
|
from . import increase_revision, revision_greater, revision_smaller
|
|
|
|
logger = logging.getLogger("BitBake.PRserv")
|
|
|
|
#
|
|
# "No History" mode - for a given query tuple (version, pkgarch, checksum),
|
|
# the returned value will be the largest among all the values of the same
|
|
# (version, pkgarch). This means the PR value returned can NOT be decremented.
|
|
#
|
|
# "History" mode - Return a new higher value for previously unseen query
|
|
# tuple (version, pkgarch, checksum), otherwise return historical value.
|
|
# Value can decrement if returning to a previous build.
|
|
|
|
class PRTable(object):
|
|
def __init__(self, conn, table, read_only):
|
|
self.conn = conn
|
|
self.read_only = read_only
|
|
self.table = table
|
|
|
|
# Creating the table even if the server is read-only.
|
|
# This avoids a race condition if a shared database
|
|
# is accessed by a read-only server first.
|
|
|
|
with closing(self.conn.cursor()) as cursor:
|
|
cursor.execute("CREATE TABLE IF NOT EXISTS %s \
|
|
(version TEXT NOT NULL, \
|
|
pkgarch TEXT NOT NULL, \
|
|
checksum TEXT NOT NULL, \
|
|
value TEXT, \
|
|
PRIMARY KEY (version, pkgarch, checksum, value));" % self.table)
|
|
self.conn.commit()
|
|
|
|
def _extremum_value(self, rows, is_max):
|
|
value = None
|
|
|
|
for row in rows:
|
|
current_value = row[0]
|
|
if value is None:
|
|
value = current_value
|
|
else:
|
|
if is_max:
|
|
is_new_extremum = revision_greater(current_value, value)
|
|
else:
|
|
is_new_extremum = revision_smaller(current_value, value)
|
|
if is_new_extremum:
|
|
value = current_value
|
|
return value
|
|
|
|
def _max_value(self, rows):
|
|
return self._extremum_value(rows, True)
|
|
|
|
def _min_value(self, rows):
|
|
return self._extremum_value(rows, False)
|
|
|
|
def test_package(self, version, pkgarch):
|
|
"""Returns whether the specified package version is found in the database for the specified architecture"""
|
|
|
|
# Just returns the value if found or None otherwise
|
|
with closing(self.conn.cursor()) as cursor:
|
|
data=cursor.execute("SELECT value FROM %s WHERE version=? AND pkgarch=?;" % self.table,
|
|
(version, pkgarch))
|
|
row=data.fetchone()
|
|
if row is not None:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def test_checksum_value(self, version, pkgarch, checksum, value):
|
|
"""Returns whether the specified value is found in the database for the specified package, architecture and checksum"""
|
|
|
|
with closing(self.conn.cursor()) as cursor:
|
|
data=cursor.execute("SELECT value FROM %s WHERE version=? AND pkgarch=? and checksum=? and value=?;" % self.table,
|
|
(version, pkgarch, checksum, value))
|
|
row=data.fetchone()
|
|
if row is not None:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def test_value(self, version, pkgarch, value):
|
|
"""Returns whether the specified value is found in the database for the specified package and architecture"""
|
|
|
|
# Just returns the value if found or None otherwise
|
|
with closing(self.conn.cursor()) as cursor:
|
|
data=cursor.execute("SELECT value FROM %s WHERE version=? AND pkgarch=? and value=?;" % self.table,
|
|
(version, pkgarch, value))
|
|
row=data.fetchone()
|
|
if row is not None:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def find_package_max_value(self, version, pkgarch):
|
|
"""Returns the greatest value for (version, pkgarch), or None if not found. Doesn't create a new value"""
|
|
|
|
with closing(self.conn.cursor()) as cursor:
|
|
data = cursor.execute("SELECT value FROM %s where version=? AND pkgarch=?;" % (self.table),
|
|
(version, pkgarch))
|
|
rows = data.fetchall()
|
|
value = self._max_value(rows)
|
|
return value
|
|
|
|
def find_value(self, version, pkgarch, checksum, history=False):
|
|
"""Returns the value for the specified checksum if found or None otherwise."""
|
|
|
|
if history:
|
|
return self.find_min_value(version, pkgarch, checksum)
|
|
else:
|
|
return self.find_max_value(version, pkgarch, checksum)
|
|
|
|
|
|
def _find_extremum_value(self, version, pkgarch, checksum, is_max):
|
|
"""Returns the maximum (if is_max is True) or minimum (if is_max is False) value
|
|
for (version, pkgarch, checksum), or None if not found. Doesn't create a new value"""
|
|
|
|
with closing(self.conn.cursor()) as cursor:
|
|
data = cursor.execute("SELECT value FROM %s where version=? AND pkgarch=? AND checksum=?;" % (self.table),
|
|
(version, pkgarch, checksum))
|
|
rows = data.fetchall()
|
|
return self._extremum_value(rows, is_max)
|
|
|
|
def find_max_value(self, version, pkgarch, checksum):
|
|
return self._find_extremum_value(version, pkgarch, checksum, True)
|
|
|
|
def find_min_value(self, version, pkgarch, checksum):
|
|
return self._find_extremum_value(version, pkgarch, checksum, False)
|
|
|
|
def find_new_subvalue(self, version, pkgarch, base):
|
|
"""Take and increase the greatest "<base>.y" value for (version, pkgarch), or return "<base>.0" if not found.
|
|
This doesn't store a new value."""
|
|
|
|
with closing(self.conn.cursor()) as cursor:
|
|
data = cursor.execute("SELECT value FROM %s where version=? AND pkgarch=? AND value LIKE '%s.%%';" % (self.table, base),
|
|
(version, pkgarch))
|
|
rows = data.fetchall()
|
|
value = self._max_value(rows)
|
|
|
|
if value is not None:
|
|
return increase_revision(value)
|
|
else:
|
|
return base + ".0"
|
|
|
|
def store_value(self, version, pkgarch, checksum, value):
|
|
"""Store value in the database"""
|
|
|
|
if not self.read_only and not self.test_checksum_value(version, pkgarch, checksum, value):
|
|
with closing(self.conn.cursor()) as cursor:
|
|
cursor.execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table),
|
|
(version, pkgarch, checksum, value))
|
|
self.conn.commit()
|
|
|
|
def _get_value(self, version, pkgarch, checksum, history):
|
|
|
|
max_value = self.find_package_max_value(version, pkgarch)
|
|
|
|
if max_value is None:
|
|
# version, pkgarch completely unknown. Return initial value.
|
|
return "0"
|
|
|
|
value = self.find_value(version, pkgarch, checksum, history)
|
|
|
|
if value is None:
|
|
# version, pkgarch found but not checksum. Create a new value from the maximum one
|
|
return increase_revision(max_value)
|
|
|
|
if history:
|
|
return value
|
|
|
|
# "no history" mode - If the value is not the maximum value for the package, need to increase it.
|
|
if max_value > value:
|
|
return increase_revision(max_value)
|
|
else:
|
|
return value
|
|
|
|
def get_value(self, version, pkgarch, checksum, history):
|
|
value = self._get_value(version, pkgarch, checksum, history)
|
|
if not self.read_only:
|
|
self.store_value(version, pkgarch, checksum, value)
|
|
return value
|
|
|
|
def importone(self, version, pkgarch, checksum, value):
|
|
self.store_value(version, pkgarch, checksum, value)
|
|
return value
|
|
|
|
def export(self, version, pkgarch, checksum, colinfo, history=False):
|
|
metainfo = {}
|
|
with closing(self.conn.cursor()) as cursor:
|
|
#column info
|
|
if colinfo:
|
|
metainfo["tbl_name"] = self.table
|
|
metainfo["core_ver"] = prserv.__version__
|
|
metainfo["col_info"] = []
|
|
data = cursor.execute("PRAGMA table_info(%s);" % self.table)
|
|
for row in data:
|
|
col = {}
|
|
col["name"] = row["name"]
|
|
col["type"] = row["type"]
|
|
col["notnull"] = row["notnull"]
|
|
col["dflt_value"] = row["dflt_value"]
|
|
col["pk"] = row["pk"]
|
|
metainfo["col_info"].append(col)
|
|
|
|
#data info
|
|
datainfo = []
|
|
|
|
if history:
|
|
sqlstmt = "SELECT * FROM %s as T1 WHERE 1=1 " % self.table
|
|
else:
|
|
sqlstmt = "SELECT T1.version, T1.pkgarch, T1.checksum, T1.value FROM %s as T1, \
|
|
(SELECT version, pkgarch, max(value) as maxvalue FROM %s GROUP BY version, pkgarch) as T2 \
|
|
WHERE T1.version=T2.version AND T1.pkgarch=T2.pkgarch AND T1.value=T2.maxvalue " % (self.table, self.table)
|
|
sqlarg = []
|
|
where = ""
|
|
if version:
|
|
where += "AND T1.version=? "
|
|
sqlarg.append(str(version))
|
|
if pkgarch:
|
|
where += "AND T1.pkgarch=? "
|
|
sqlarg.append(str(pkgarch))
|
|
if checksum:
|
|
where += "AND T1.checksum=? "
|
|
sqlarg.append(str(checksum))
|
|
|
|
sqlstmt += where + ";"
|
|
|
|
if len(sqlarg):
|
|
data = cursor.execute(sqlstmt, tuple(sqlarg))
|
|
else:
|
|
data = cursor.execute(sqlstmt)
|
|
for row in data:
|
|
if row["version"]:
|
|
col = {}
|
|
col["version"] = row["version"]
|
|
col["pkgarch"] = row["pkgarch"]
|
|
col["checksum"] = row["checksum"]
|
|
col["value"] = row["value"]
|
|
datainfo.append(col)
|
|
return (metainfo, datainfo)
|
|
|
|
def dump_db(self, fd):
|
|
writeCount = 0
|
|
for line in self.conn.iterdump():
|
|
writeCount = writeCount + len(line) + 1
|
|
fd.write(line)
|
|
fd.write("\n")
|
|
return writeCount
|
|
|
|
class PRData(object):
|
|
"""Object representing the PR database"""
|
|
def __init__(self, filename, read_only=False):
|
|
self.filename=os.path.abspath(filename)
|
|
self.read_only = read_only
|
|
#build directory hierarchy
|
|
try:
|
|
os.makedirs(os.path.dirname(self.filename))
|
|
except OSError as e:
|
|
if e.errno != errno.EEXIST:
|
|
raise e
|
|
uri = "file:%s%s" % (self.filename, "?mode=ro" if self.read_only else "")
|
|
logger.debug("Opening PRServ database '%s'" % (uri))
|
|
self.connection=sqlite3.connect(uri, uri=True)
|
|
self.connection.row_factory=sqlite3.Row
|
|
self.connection.execute("PRAGMA synchronous = OFF;")
|
|
self.connection.execute("PRAGMA journal_mode = WAL;")
|
|
self.connection.commit()
|
|
self._tables={}
|
|
|
|
def disconnect(self):
|
|
self.connection.commit()
|
|
self.connection.close()
|
|
|
|
def __getitem__(self, tblname):
|
|
if not isinstance(tblname, str):
|
|
raise TypeError("tblname argument must be a string, not '%s'" %
|
|
type(tblname))
|
|
if tblname in self._tables:
|
|
return self._tables[tblname]
|
|
else:
|
|
tableobj = self._tables[tblname] = PRTable(self.connection, tblname, self.read_only)
|
|
return tableobj
|
|
|
|
def __delitem__(self, tblname):
|
|
if tblname in self._tables:
|
|
del self._tables[tblname]
|
|
logger.info("drop table %s" % (tblname))
|
|
self.connection.execute("DROP TABLE IF EXISTS %s;" % tblname)
|
|
self.connection.commit()
|