port-compare/port_stats/rpm_ffi.py
Ivan A. Melnikov c84192c52a repos: New tool in the box
We try to model repository as collection of source and
binary packages with their dependencies, and do some
stuff with it.

The initial version can calculate a list of unmets.
2021-09-02 16:33:43 +04:00

115 lines
3.8 KiB
Python

import cffi
_CDEF = """
void
parseEVR(const char* evr,
const char **e, const char **v, const char **r);
int
rpmEVRcmp(const char * const aE, const char * const aV, const char * const aR,
const char * const aDepend,
const char * const bE, const char * const bV, const char * const bR,
const char * const bDepend);
int rpmRangesOverlap(const char * AName, const char * AEVR, int AFlags,
const char * BName, const char * BEVR, int BFlags);
"""
_FFI = cffi.FFI()
_FFI.cdef(_CDEF)
_LIBRPM = _FFI.dlopen('librpm-4.0.4.so')
def _pp_str(p_str):
"""Convert FFI's char** to Python string.
Assumes that pointer points to a zero-terminated C-string.
Returns None if the pointer is NULL.
"""
if p_str and p_str[0]:
return _FFI.string(p_str[0])
return None
def parse_evr(evr):
"""Returns 3-tuple (epoch, version, release)"""
p_evr = _FFI.new('char[]', evr)
e = _FFI.new('char**')
v = _FFI.new('char**')
r = _FFI.new('char**')
_LIBRPM.parseEVR(p_evr, e, v, r)
epoch = _pp_str(e)
if epoch:
epoch = int(epoch)
return epoch, _pp_str(v), _pp_str(r)
def ranges_overlap(aname, aevr, aflags, bname, bevr, bflags):
return _LIBRPM.rpmRangesOverlap(
_FFI.new('char[]', aname), _FFI.new('char[]', aevr), aflags,
_FFI.new('char[]', bname), _FFI.new('char[]', bevr), bflags)
def _epoch_to_pchar(epoch, mode):
if mode not in ('pkg', 'deps'):
raise ValueError("Epoch mode should be one of "
"'pkg', 'deps' -- not " + mode)
if epoch is not None:
return _FFI.new('char[]', str(epoch).encode())
elif mode == 'pkg':
# for packages no epoch is the same as zero epoch
return _FFI.new('char[]', b'0')
else:
return _FFI.NULL
def evr_cmp(evr1, evr2, mode):
p_e1 = _epoch_to_pchar(evr1[0], mode)
p_v1 = _FFI.new('char[]', evr1[1]) if evr1[1] else _FFI.NULL
p_r1 = _FFI.new('char[]', evr1[2]) if evr1[2] else _FFI.NULL
p_e2 = _epoch_to_pchar(evr2[0], mode)
p_v2 = _FFI.new('char[]', evr2[1]) if evr2[1] else _FFI.NULL
p_r2 = _FFI.new('char[]', evr2[2]) if evr2[2] else _FFI.NULL
dep = _FFI.new('char[]', b'')
return _LIBRPM.rpmEVRcmp(p_e1, p_v1, p_r1, dep,
p_e2, p_v2, p_r2, dep)
def ver_cmp(ver1, ver2):
return evr_cmp((None, ver1, b'1'), (None, ver2, b'1'), 'pkg')
def _tests():
assert parse_evr(b'3:42.8.4-alt1.mipsel0') == (3, b'42.8.4', b'alt1.mipsel0')
assert parse_evr(b'0:42.8.4-alt1.mipsel0') == (0, b'42.8.4', b'alt1.mipsel0')
assert parse_evr(b'42.8.4-alt1.mipsel0') == (None, b'42.8.4', b'alt1.mipsel0')
assert parse_evr(b'1.20.0-alt1_1') == (None, b'1.20.0', b'alt1_1')
assert parse_evr(b'1:1.20.1-alt1') == (1, b'1.20.1', b'alt1')
assert evr_cmp((3, b'42.8.4', b'alt1'), (3, b'42.8.4', b'alt2'), 'pkg') < 0
assert evr_cmp((3, b'42.9.4', b'alt1'), (3, b'42.8.4', b'alt2'), 'pkg') > 0
assert evr_cmp((3, b'42.8.4', b'alt1'), (3, b'42.8.4', b'alt1'), 'pkg') == 0
assert evr_cmp((3, b'42.9.4', b'alt1'), (5, b'42.8.4', b'alt1'), 'pkg') < 0
assert evr_cmp((1, b'1.2.0', b'alt1'), (None, b'1.2.0', b'alt1_1'), 'pkg') > 0
assert evr_cmp((None, b'1.2.0', b'alt1_1'), (1, b'1.2.0', b'alt1'), 'pkg') < 0
# 'deps' mode means that first argument satisfies requirement
# specified as second argument; here, if epoch is absent on the right
# side, it's ignored
assert evr_cmp((1, b'1.2.0', b'alt1'), (None, b'1.2.0', b'alt1_1'), 'deps') < 0
assert evr_cmp((None, b'1.2.0', b'alt1_1'), (1, b'1.2.0', b'alt1'), 'deps') < 0
assert evr_cmp((1, b'1.3.0', b'alt1'), (None, b'1.2.0', b'alt1_1'), 'deps') > 0
assert evr_cmp((None, b'1.3.0', b'alt1_1'), (1, b'1.2.0', b'alt1'), 'deps') < 0
if __name__ == '__main__':
_tests()