xref: /aosp_15_r20/external/autotest/client/bin/package.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li"""
3*9c5db199SXin LiFunctions to handle software packages. The functions covered here aim to be
4*9c5db199SXin Ligeneric, with implementations that deal with different package managers, such
5*9c5db199SXin Lias dpkg and rpm.
6*9c5db199SXin Li"""
7*9c5db199SXin Li
8*9c5db199SXin Lifrom __future__ import absolute_import
9*9c5db199SXin Lifrom __future__ import division
10*9c5db199SXin Lifrom __future__ import print_function
11*9c5db199SXin Li
12*9c5db199SXin Li__author__ = '[email protected] (Lucas Meneghel Rodrigues)'
13*9c5db199SXin Li
14*9c5db199SXin Liimport os, re
15*9c5db199SXin Lifrom autotest_lib.client.bin import os_dep, utils
16*9c5db199SXin Lifrom autotest_lib.client.common_lib import error
17*9c5db199SXin Li
18*9c5db199SXin Li# As more package methods are implemented, this list grows up
19*9c5db199SXin LiKNOWN_PACKAGE_MANAGERS = ['rpm', 'dpkg']
20*9c5db199SXin Li
21*9c5db199SXin Li
22*9c5db199SXin Lidef _rpm_info(rpm_package):
23*9c5db199SXin Li    """\
24*9c5db199SXin Li    Private function that returns a dictionary with information about an
25*9c5db199SXin Li    RPM package file
26*9c5db199SXin Li    - type: Package management program that handles the file
27*9c5db199SXin Li    - system_support: If the package management program is installed on the
28*9c5db199SXin Li    system or not
29*9c5db199SXin Li    - source: If it is a source (True) our binary (False) package
30*9c5db199SXin Li    - version: The package version (or name), that is used to check against the
31*9c5db199SXin Li    package manager if the package is installed
32*9c5db199SXin Li    - arch: The architecture for which a binary package was built
33*9c5db199SXin Li    - installed: Whether the package is installed (True) on the system or not
34*9c5db199SXin Li    (False)
35*9c5db199SXin Li    """
36*9c5db199SXin Li    # We will make good use of what the file command has to tell us about the
37*9c5db199SXin Li    # package :)
38*9c5db199SXin Li    file_result = utils.system_output('file ' + rpm_package)
39*9c5db199SXin Li    package_info = {}
40*9c5db199SXin Li    package_info['type'] = 'rpm'
41*9c5db199SXin Li    try:
42*9c5db199SXin Li        os_dep.command('rpm')
43*9c5db199SXin Li        # Build the command strings that will be used to get package info
44*9c5db199SXin Li        # s_cmd - Command to determine if package is a source package
45*9c5db199SXin Li        # a_cmd - Command to determine package architecture
46*9c5db199SXin Li        # v_cmd - Command to determine package version
47*9c5db199SXin Li        # i_cmd - Command to determiine if package is installed
48*9c5db199SXin Li        s_cmd = 'rpm -qp --qf %{SOURCE} ' + rpm_package + ' 2>/dev/null'
49*9c5db199SXin Li        a_cmd = 'rpm -qp --qf %{ARCH} ' + rpm_package + ' 2>/dev/null'
50*9c5db199SXin Li        v_cmd = 'rpm -qp ' + rpm_package + ' 2>/dev/null'
51*9c5db199SXin Li        i_cmd = 'rpm -q ' + utils.system_output(v_cmd) + ' 2>&1 >/dev/null'
52*9c5db199SXin Li
53*9c5db199SXin Li        package_info['system_support'] = True
54*9c5db199SXin Li        # Checking whether this is a source or src package
55*9c5db199SXin Li        source = utils.system_output(s_cmd)
56*9c5db199SXin Li        if source == '(none)':
57*9c5db199SXin Li            package_info['source'] = False
58*9c5db199SXin Li        else:
59*9c5db199SXin Li            package_info['source'] = True
60*9c5db199SXin Li        package_info['version'] = utils.system_output(v_cmd)
61*9c5db199SXin Li        package_info['arch'] = utils.system_output(a_cmd)
62*9c5db199SXin Li        # Checking if package is installed
63*9c5db199SXin Li        try:
64*9c5db199SXin Li            utils.system(i_cmd)
65*9c5db199SXin Li            package_info['installed'] = True
66*9c5db199SXin Li        except:
67*9c5db199SXin Li            package_info['installed'] = False
68*9c5db199SXin Li
69*9c5db199SXin Li    except:
70*9c5db199SXin Li        package_info['system_support'] = False
71*9c5db199SXin Li        package_info['installed'] = False
72*9c5db199SXin Li        # File gives a wealth of information about rpm packages.
73*9c5db199SXin Li        # However, we can't trust all this info, as incorrectly
74*9c5db199SXin Li        # packaged rpms can report some wrong values.
75*9c5db199SXin Li        # It's better than nothing though :)
76*9c5db199SXin Li        if len(file_result.split(' ')) == 6:
77*9c5db199SXin Li            # Figure if package is a source package
78*9c5db199SXin Li            if file_result.split(' ')[3] == 'src':
79*9c5db199SXin Li                package_info['source'] = True
80*9c5db199SXin Li            elif file_result.split(' ')[3] == 'bin':
81*9c5db199SXin Li                package_info['source'] = False
82*9c5db199SXin Li            else:
83*9c5db199SXin Li                package_info['source'] = False
84*9c5db199SXin Li            # Get architecture
85*9c5db199SXin Li            package_info['arch'] = file_result.split(' ')[4]
86*9c5db199SXin Li            # Get version
87*9c5db199SXin Li            package_info['version'] = file_result.split(' ')[5]
88*9c5db199SXin Li        elif len(file_result.split(' ')) == 5:
89*9c5db199SXin Li            # Figure if package is a source package
90*9c5db199SXin Li            if file_result.split(' ')[3] == 'src':
91*9c5db199SXin Li                package_info['source'] = True
92*9c5db199SXin Li            elif file_result.split(' ')[3] == 'bin':
93*9c5db199SXin Li                package_info['source'] = False
94*9c5db199SXin Li            else:
95*9c5db199SXin Li                package_info['source'] = False
96*9c5db199SXin Li            # When the arch param is missing on file, we assume noarch
97*9c5db199SXin Li            package_info['arch'] = 'noarch'
98*9c5db199SXin Li            # Get version
99*9c5db199SXin Li            package_info['version'] = file_result.split(' ')[4]
100*9c5db199SXin Li        else:
101*9c5db199SXin Li            # If everything else fails...
102*9c5db199SXin Li            package_info['source'] =  False
103*9c5db199SXin Li            package_info['arch'] = 'Not Available'
104*9c5db199SXin Li            package_info['version'] = 'Not Available'
105*9c5db199SXin Li    return package_info
106*9c5db199SXin Li
107*9c5db199SXin Li
108*9c5db199SXin Lidef _dpkg_info(dpkg_package):
109*9c5db199SXin Li    """\
110*9c5db199SXin Li    Private function that returns a dictionary with information about a
111*9c5db199SXin Li    dpkg package file
112*9c5db199SXin Li    - type: Package management program that handles the file
113*9c5db199SXin Li    - system_support: If the package management program is installed on the
114*9c5db199SXin Li    system or not
115*9c5db199SXin Li    - source: If it is a source (True) our binary (False) package
116*9c5db199SXin Li    - version: The package version (or name), that is used to check against the
117*9c5db199SXin Li    package manager if the package is installed
118*9c5db199SXin Li    - arch: The architecture for which a binary package was built
119*9c5db199SXin Li    - installed: Whether the package is installed (True) on the system or not
120*9c5db199SXin Li    (False)
121*9c5db199SXin Li    """
122*9c5db199SXin Li    # We will make good use of what the file command has to tell us about the
123*9c5db199SXin Li    # package :)
124*9c5db199SXin Li    file_result = utils.system_output('file ' + dpkg_package)
125*9c5db199SXin Li    package_info = {}
126*9c5db199SXin Li    package_info['type'] = 'dpkg'
127*9c5db199SXin Li    # There's no single debian source package as is the case
128*9c5db199SXin Li    # with RPM
129*9c5db199SXin Li    package_info['source'] = False
130*9c5db199SXin Li    try:
131*9c5db199SXin Li        os_dep.command('dpkg')
132*9c5db199SXin Li        # Build the command strings that will be used to get package info
133*9c5db199SXin Li        # a_cmd - Command to determine package architecture
134*9c5db199SXin Li        # v_cmd - Command to determine package version
135*9c5db199SXin Li        # i_cmd - Command to determiine if package is installed
136*9c5db199SXin Li        a_cmd = 'dpkg -f ' + dpkg_package + ' Architecture 2>/dev/null'
137*9c5db199SXin Li        v_cmd = 'dpkg -f ' + dpkg_package + ' Package 2>/dev/null'
138*9c5db199SXin Li        i_cmd = 'dpkg -s ' + utils.system_output(v_cmd) + ' 2>/dev/null'
139*9c5db199SXin Li
140*9c5db199SXin Li        package_info['system_support'] = True
141*9c5db199SXin Li        package_info['version'] = utils.system_output(v_cmd)
142*9c5db199SXin Li        package_info['arch'] = utils.system_output(a_cmd)
143*9c5db199SXin Li        # Checking if package is installed
144*9c5db199SXin Li        package_status = utils.system_output(i_cmd, ignore_status=True)
145*9c5db199SXin Li        not_inst_pattern = re.compile('not-installed', re.IGNORECASE)
146*9c5db199SXin Li        dpkg_not_installed = re.search(not_inst_pattern, package_status)
147*9c5db199SXin Li        if dpkg_not_installed:
148*9c5db199SXin Li            package_info['installed'] = False
149*9c5db199SXin Li        else:
150*9c5db199SXin Li            package_info['installed'] = True
151*9c5db199SXin Li
152*9c5db199SXin Li    except:
153*9c5db199SXin Li        package_info['system_support'] = False
154*9c5db199SXin Li        package_info['installed'] = False
155*9c5db199SXin Li        # The output of file is not as generous for dpkg files as
156*9c5db199SXin Li        # it is with rpm files
157*9c5db199SXin Li        package_info['arch'] = 'Not Available'
158*9c5db199SXin Li        package_info['version'] = 'Not Available'
159*9c5db199SXin Li
160*9c5db199SXin Li    return package_info
161*9c5db199SXin Li
162*9c5db199SXin Li
163*9c5db199SXin Lidef list_all():
164*9c5db199SXin Li    """Returns a list with the names of all currently installed packages."""
165*9c5db199SXin Li    support_info = os_support()
166*9c5db199SXin Li    installed_packages = []
167*9c5db199SXin Li
168*9c5db199SXin Li    if support_info['rpm']:
169*9c5db199SXin Li        installed_packages += utils.system_output('rpm -qa').splitlines()
170*9c5db199SXin Li
171*9c5db199SXin Li    if support_info['dpkg']:
172*9c5db199SXin Li        raw_list = utils.system_output('dpkg -l').splitlines()[5:]
173*9c5db199SXin Li        for line in raw_list:
174*9c5db199SXin Li            parts = line.split()
175*9c5db199SXin Li            if parts[0] == "ii":  # only grab "installed" packages
176*9c5db199SXin Li                installed_packages.append("%s-%s" % (parts[1], parts[2]))
177*9c5db199SXin Li
178*9c5db199SXin Li    return installed_packages
179*9c5db199SXin Li
180*9c5db199SXin Li
181*9c5db199SXin Lidef info(package):
182*9c5db199SXin Li    """\
183*9c5db199SXin Li    Returns a dictionary with package information about a given package file:
184*9c5db199SXin Li    - type: Package management program that handles the file
185*9c5db199SXin Li    - system_support: If the package management program is installed on the
186*9c5db199SXin Li    system or not
187*9c5db199SXin Li    - source: If it is a source (True) our binary (False) package
188*9c5db199SXin Li    - version: The package version (or name), that is used to check against the
189*9c5db199SXin Li    package manager if the package is installed
190*9c5db199SXin Li    - arch: The architecture for which a binary package was built
191*9c5db199SXin Li    - installed: Whether the package is installed (True) on the system or not
192*9c5db199SXin Li    (False)
193*9c5db199SXin Li
194*9c5db199SXin Li    Implemented package types:
195*9c5db199SXin Li    - 'dpkg' - dpkg (debian, ubuntu) package files
196*9c5db199SXin Li    - 'rpm' - rpm (red hat, suse) package files
197*9c5db199SXin Li    Raises an exception if the package type is not one of the implemented
198*9c5db199SXin Li    package types.
199*9c5db199SXin Li    """
200*9c5db199SXin Li    if not os.path.isfile(package):
201*9c5db199SXin Li        raise ValueError('invalid file %s to verify' % package)
202*9c5db199SXin Li    # Use file and libmagic to determine the actual package file type.
203*9c5db199SXin Li    file_result = utils.system_output('file ' + package)
204*9c5db199SXin Li    for package_manager in KNOWN_PACKAGE_MANAGERS:
205*9c5db199SXin Li        if package_manager == 'rpm':
206*9c5db199SXin Li            package_pattern = re.compile('RPM', re.IGNORECASE)
207*9c5db199SXin Li        elif package_manager == 'dpkg':
208*9c5db199SXin Li            package_pattern = re.compile('Debian', re.IGNORECASE)
209*9c5db199SXin Li
210*9c5db199SXin Li        result = re.search(package_pattern, file_result)
211*9c5db199SXin Li
212*9c5db199SXin Li        if result and package_manager == 'rpm':
213*9c5db199SXin Li            return _rpm_info(package)
214*9c5db199SXin Li        elif result and package_manager == 'dpkg':
215*9c5db199SXin Li            return _dpkg_info(package)
216*9c5db199SXin Li
217*9c5db199SXin Li    # If it's not one of the implemented package manager methods, there's
218*9c5db199SXin Li    # not much that can be done, hence we throw an exception.
219*9c5db199SXin Li    raise error.PackageError('Unknown package type %s' % file_result)
220*9c5db199SXin Li
221*9c5db199SXin Li
222*9c5db199SXin Lidef install(package, nodeps = False):
223*9c5db199SXin Li    """\
224*9c5db199SXin Li    Tries to install a package file. If the package is already installed,
225*9c5db199SXin Li    it prints a message to the user and ends gracefully. If nodeps is set to
226*9c5db199SXin Li    true, it will ignore package dependencies.
227*9c5db199SXin Li    """
228*9c5db199SXin Li    my_package_info = info(package)
229*9c5db199SXin Li    type = my_package_info['type']
230*9c5db199SXin Li    system_support = my_package_info['system_support']
231*9c5db199SXin Li    source = my_package_info['source']
232*9c5db199SXin Li    installed = my_package_info['installed']
233*9c5db199SXin Li
234*9c5db199SXin Li    if not system_support:
235*9c5db199SXin Li        e_msg = ('Client does not have package manager %s to handle %s install'
236*9c5db199SXin Li                 % (type, package))
237*9c5db199SXin Li        raise error.PackageError(e_msg)
238*9c5db199SXin Li
239*9c5db199SXin Li    opt_args = ''
240*9c5db199SXin Li    if type == 'rpm':
241*9c5db199SXin Li        if nodeps:
242*9c5db199SXin Li            opt_args = opt_args + '--nodeps'
243*9c5db199SXin Li        install_command = 'rpm %s -U %s' % (opt_args, package)
244*9c5db199SXin Li    if type == 'dpkg':
245*9c5db199SXin Li        if nodeps:
246*9c5db199SXin Li            opt_args = opt_args + '--force-depends'
247*9c5db199SXin Li        install_command = 'dpkg %s -i %s' % (opt_args, package)
248*9c5db199SXin Li
249*9c5db199SXin Li    # RPM source packages can be installed along with the binary versions
250*9c5db199SXin Li    # with this check
251*9c5db199SXin Li    if installed and not source:
252*9c5db199SXin Li        return 'Package %s is already installed' % package
253*9c5db199SXin Li
254*9c5db199SXin Li    # At this point, the most likely thing to go wrong is that there are
255*9c5db199SXin Li    # unmet dependencies for the package. We won't cover this case, at
256*9c5db199SXin Li    # least for now.
257*9c5db199SXin Li    utils.system(install_command)
258*9c5db199SXin Li    return 'Package %s was installed successfuly' % package
259*9c5db199SXin Li
260*9c5db199SXin Li
261*9c5db199SXin Lidef convert(package, destination_format):
262*9c5db199SXin Li    """\
263*9c5db199SXin Li    Convert packages with the 'alien' utility. If alien is not installed, it
264*9c5db199SXin Li    throws a NotImplementedError exception.
265*9c5db199SXin Li    returns: filename of the package generated.
266*9c5db199SXin Li    """
267*9c5db199SXin Li    try:
268*9c5db199SXin Li        os_dep.command('alien')
269*9c5db199SXin Li    except:
270*9c5db199SXin Li        e_msg = 'Cannot convert to %s, alien not installed' % destination_format
271*9c5db199SXin Li        raise error.TestError(e_msg)
272*9c5db199SXin Li
273*9c5db199SXin Li    # alien supports converting to many formats, but its interesting to map
274*9c5db199SXin Li    # convertions only for the implemented package types.
275*9c5db199SXin Li    if destination_format == 'dpkg':
276*9c5db199SXin Li        deb_pattern = re.compile('[A-Za-z0-9_.-]*[.][d][e][b]')
277*9c5db199SXin Li        conv_output = utils.system_output('alien --to-deb %s 2>/dev/null'
278*9c5db199SXin Li                                          % package)
279*9c5db199SXin Li        converted_package = re.findall(deb_pattern, conv_output)[0]
280*9c5db199SXin Li    elif destination_format == 'rpm':
281*9c5db199SXin Li        rpm_pattern = re.compile('[A-Za-z0-9_.-]*[.][r][p][m]')
282*9c5db199SXin Li        conv_output = utils.system_output('alien --to-rpm %s 2>/dev/null'
283*9c5db199SXin Li                                          % package)
284*9c5db199SXin Li        converted_package = re.findall(rpm_pattern, conv_output)[0]
285*9c5db199SXin Li    else:
286*9c5db199SXin Li        e_msg = 'Convertion to format %s not implemented' % destination_format
287*9c5db199SXin Li        raise NotImplementedError(e_msg)
288*9c5db199SXin Li
289*9c5db199SXin Li    print('Package %s successfuly converted to %s' % \
290*9c5db199SXin Li            (os.path.basename(package), os.path.basename(converted_package)))
291*9c5db199SXin Li    return os.path.abspath(converted_package)
292*9c5db199SXin Li
293*9c5db199SXin Li
294*9c5db199SXin Lidef os_support():
295*9c5db199SXin Li    """\
296*9c5db199SXin Li    Returns a dictionary with host os package support info:
297*9c5db199SXin Li    - rpm: True if system supports rpm packages, False otherwise
298*9c5db199SXin Li    - dpkg: True if system supports dpkg packages, False otherwise
299*9c5db199SXin Li    - conversion: True if the system can convert packages (alien installed),
300*9c5db199SXin Li    or False otherwise
301*9c5db199SXin Li    """
302*9c5db199SXin Li    support_info = {}
303*9c5db199SXin Li    for package_manager in KNOWN_PACKAGE_MANAGERS:
304*9c5db199SXin Li        try:
305*9c5db199SXin Li            os_dep.command(package_manager)
306*9c5db199SXin Li            support_info[package_manager] = True
307*9c5db199SXin Li        except:
308*9c5db199SXin Li            support_info[package_manager] = False
309*9c5db199SXin Li
310*9c5db199SXin Li    try:
311*9c5db199SXin Li        os_dep.command('alien')
312*9c5db199SXin Li        support_info['conversion'] = True
313*9c5db199SXin Li    except:
314*9c5db199SXin Li        support_info['conversion'] = False
315*9c5db199SXin Li
316*9c5db199SXin Li    return support_info
317