On 01/08/2016 08:22 AM, Jan Cholasta wrote:
On 8.1.2016 14:13, Martin Basti wrote:


On 08.01.2016 14:14, Jan Cholasta wrote:
On 8.1.2016 14:09, Martin Basti wrote:


On 08.01.2016 14:00, Martin Kosek wrote:
On 01/08/2016 01:45 PM, Martin Basti wrote:
Hello all,

fix for ticket https://fedorahosted.org/freeipa/ticket/5535
requires to import rpm module

This import somehow breaks nsslib in IPA
https://fedorahosted.org/freeipa/ticket/5572


We have 2 ways how to fix it:

1) move import rpm to body of methods (attached patch)
We are not sure how stable is this solution.

2) use solution with rpmdevtools proposed here:
https://www.redhat.com/archives/freeipa-devel/2016-January/msg00092.html


This should be rock stable but it needs many dependencies (rpm-python
too, perl)

The second way looks safer, so I would like to reimplement it, do you
all agree
or do you have better idea?
Feedback welcome, please ASAP.

Martin^2
Since it's Friday, I invested 15 minutes to practice my C skills and
use the
python-cffi library to call rpm rpmvercmp library call directly
(attached):

$ python rpm.py 4.2.0-15.el7 4.2.0-15.el7_2.3
4.2.0-15.el7 < 4.2.0-15.el7_2.3

This would not introduce any additional dependency besides rpm-devel,
right? :-)

Not rpm-devel, but rpm-libs (you should dlopen "librpm.so.3").

I'm afraid that this can cause the same issue as import rpm, because
the
nsslib is used from C library

I would be surprised if NSS was used in this particular function.

I will try it

No NSS here:
<https://github.com/rpm-software-management/rpm/blob/master/lib/rpmvercmp.c>


Anyway, the function looks simple, so it might be safer to just rewrite
it to Python, with no new dependencies.


Leaving aside the whole question of whether re-implementing rpmvercmp in Python is a good idea or not because of possible divergence from RPM I offer to you an implementation of rpmvercmp written in Python I did years ago. It was written based on the published documentation of how RPM version comparison is implemented (as close to a spec as I was able to find). I believe I also used the C implementation as a guide but my memory is fuzzy on that point. I've used it a lot and I've also cross checked it's results with librpm and I've never seen a differing result.

Use at your pleasure or displeasure :-)

HTH,

John


--
John
#!/usr/bin/python

import re
rpm_name_re = re.compile(r'^(.+)-([^-]+)-([^-]+)$')

def split_rpm_name(rpm_name):
    '''
    Split an RPM's NVR returning 
    [name, version, release]
    '''
    match = rpm_name_re.match(rpm_name)
    if match:
        name    = match.group(1)
        version = match.group(2)
        release = match.group(3)
        return name, version, release
    else:
        raise ValueError("cannot split rpm NVR for '%s'" % rpm_name)

def split_rpm_label(label):
    '''
    Each label is separated into a list of maximal alphabetic or numeric
    components, with separators (non-alphanumeric characters) ignored. 
    Alphbetic components are inserted into the list as a Python str object.
    Numeric components are inserted into the list as either Python int
    or long objects depending on the numeric magnitude of the component.

    For example:
    '2.0.1' => [2, 0, 1]
    '2xFg33.+f.5' => [2, 'xFg', 33, 'f', 5]
    '''
    components = []
    component = None
    for c in label:
        if c.isalpha():
            if component is None:
                component = c
            else:
                if component.isalpha():
                    component += c
                else:
                    components.append(int(component))
                    component = c
        elif c.isdigit():
            if component is None:
                component = c
            else:
                if component.isdigit():
                    component += c
                else:
                    components.append(component)
                    component = c
        else:
            if component is not None:
                if component.isdigit():
                    component = int(component)
                components.append(component)
                component = None

    if component is not None:
        if component.isdigit():
            component = int(component)
        components.append(component)
        component = None
        
    return components

def rpm_label_cmp(label1, label2):
    '''
    The version and release components of a rpm NVR are considered labels.
    To compare a label we split the label into components, see split_rpm_label()
    for an explanation of how the components are split.

    The components in the list are compared one by one using the following
    algorithm. If two components are considered to be different, the label with
    the newer component wins as the newer label. If the components are
    considered to be equal, the next components are compared until we either
    reach different components or one of the lists runs out. In case one of the
    lists run out, the other label wins as the newer label. So, for example, [1,
    2] is newer than [1, 1], and [1, 2, 0] is newer than [1, 2].

    Components are compared thusly:

    1. If one of the components is a number, while the other is alphabetic, the
    numeric components is considered newer. So 10 is newer than 'abc', and 0 is
    newer than 'Z'.

    2. If both the components are numbers, the larger number is considered
    newer. So 5 is newer than 4 and 10 is newer than 2. If the numbers are
    equal, the components are considered equal.

    3. If both the components are alphabetic, they are compared using the strcmp
    function, with the greater string resulting in a newer component. So 'b' is
    newer than 'a', 'add' is newer than 'ZULU' (because lowercase characters win
    in strcmp comparisons), and 'aba' is newer than 'ab'. If the strings are
    identical, the components are considered equal.
    '''

    result = 0
    # Get the components in this label
    components1 = split_rpm_label(label1)
    components2 = split_rpm_label(label2)

    len1 = len(components1)
    len2 = len(components2)

    min_len = min(len1, len2)

    # Iterate over the components
    i = 0
    while i < min_len:
        component1 = components1[i]
        component2 = components2[i]

        if type(component1) in (int, long):
            if type(component2) in (int, long):
                # both components were numeric
                result = cmp(component1, component2)
                if result != 0:
                    return result
            else:
                # 1st was numeric, 2nd was alphabetic, thus 1st is newer
                return 1
        else:
            if type(component2) is str:
                # both components were alphabetic
                result = cmp(component1, component2)
                if result != 0:
                    return result
            else:
                # 1st was alphabetic, 2nd was numeric, thus 1st is older
                return -1

        i += 1
    return cmp(len1, len2)


def rpm_ver_cmp_nvr(rpm1, rpm2):
    '''
    The version and release components of a rpm NVR are considered labels.

    The package's version label is compared according to the algorithm described
    in rpm_label_cmp(). The larger version wins. If the versions are considered
    equal then the package's release label is compared according to the algorithm
    described in rpm_label_cmp(). The larger release wins. If the releases are
    decided equal, the packages are considered equal.
    '''

    # Get the labels for both RPM's
    n1, v1, r1 = split_rpm_name(rpm1)
    n2, v2, r2 = split_rpm_name(rpm2)

    result = rpm_label_cmp(v1, v2)
    if result == 0:
        # versions were equal, compare the releases
        result = rpm_label_cmp(r1, r2)

    return result

def main():
    rpm1 = 'ipa-python-1.91-0.git.200912162104.fc13.x86_64'
    rpm2 = 'ipa-python-1.91-0.git.200912162104.fc13.x86_64'

    result = rpm_ver_cmp_nvr(rpm1, rpm2)
    print "%d %s %s" % (result, rpm1, rpm2)

    #print '%s => %s' % (rpm1, split_rpm_name(rpm1))
    #val = '2xFg33.+f.5'
    #components = split_rpm_label(val)
    #print "2xFg33.+f.5 => ('2', 'xFg', '33', 'f', '5')"
    #print "%s => %s" % (val, components)

#-------------------------------------------------------------------------------

if __name__ == "__main__":
    main()
-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to