rpm.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. # Copyright (c) 2021, Open Source Robotics Foundation
  2. # All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are met:
  6. #
  7. # * Redistributions of source code must retain the above copyright
  8. # notice, this list of conditions and the following disclaimer.
  9. # * Redistributions in binary form must reproduce the above copyright
  10. # notice, this list of conditions and the following disclaimer in the
  11. # documentation and/or other materials provided with the distribution.
  12. # * Neither the name of the Willow Garage, Inc. nor the names of its
  13. # contributors may be used to endorse or promote products derived from
  14. # this software without specific prior written permission.
  15. #
  16. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  20. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  21. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  22. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  23. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  24. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  25. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  26. # POSSIBILITY OF SUCH DAMAGE.
  27. import os
  28. from xml.etree import ElementTree
  29. from . import open_gz_url
  30. from . import PackageEntry
  31. from . import RepositoryCacheCollection
  32. def replace_tokens(string, os_name, os_code_name, os_arch):
  33. """Replace RPM-specific tokens in the repository base URL."""
  34. for key, value in {
  35. '$basearch': os_arch,
  36. '$distname': os_name,
  37. '$releasever': os_code_name,
  38. }.items():
  39. string = string.replace(key, value)
  40. return string
  41. def get_primary_name(repomd_url):
  42. """Get the URL of the 'primary' metadata from the 'repo' metadata."""
  43. print('Reading RPM repository metadata from ' + repomd_url)
  44. with open_gz_url(repomd_url) as f:
  45. tree = iter(ElementTree.iterparse(f, events=('start', 'end')))
  46. event, root = next(tree)
  47. if root.tag != '{http://linux.duke.edu/metadata/repo}repomd':
  48. raise RuntimeError('Invalid root element in repository metadata: ' + root.tag)
  49. for event, root_child in tree:
  50. if (
  51. root_child.tag != '{http://linux.duke.edu/metadata/repo}data' or
  52. root_child.attrib.get('type', '') != 'primary'
  53. ):
  54. root.clear()
  55. continue
  56. for data_child in root_child:
  57. if (
  58. data_child.tag != '{http://linux.duke.edu/metadata/repo}location' or
  59. 'href' not in data_child.attrib
  60. ):
  61. root.clear()
  62. continue
  63. return data_child.attrib['href']
  64. root.clear()
  65. raise RuntimeError('Failed to determine primary data file name')
  66. def enumerate_rpm_packages(base_url, os_name, os_code_name, os_arch):
  67. """
  68. Enumerate packages in an RPM repository.
  69. :param base_url: the RPM repository base URL.
  70. :param os_name: the name of the OS associated with the repository.
  71. :param os_code_name: the OS version associated with the repository.
  72. :param os_arch: the system architecture associated with the repository.
  73. :returns: an enumeration of package entries.
  74. """
  75. base_url = replace_tokens(base_url, os_name, os_code_name, os_arch)
  76. repomd_url = os.path.join(base_url, 'repodata', 'repomd.xml')
  77. primary_xml_name = get_primary_name(repomd_url)
  78. primary_xml_url = os.path.join(base_url, primary_xml_name)
  79. print('Reading RPM primary metadata from ' + primary_xml_url)
  80. with open_gz_url(primary_xml_url) as f:
  81. tree = ElementTree.iterparse(f)
  82. for event, element in tree:
  83. if (
  84. element.tag != '{http://linux.duke.edu/metadata/common}package' or
  85. element.attrib.get('type', '') != 'rpm'
  86. ):
  87. continue
  88. pkg_name = None
  89. pkg_version = None
  90. pkg_src_name = None
  91. pkg_url = None
  92. pkg_provs = []
  93. for pkg_child in element:
  94. if pkg_child.tag == '{http://linux.duke.edu/metadata/common}name':
  95. pkg_name = pkg_child.text
  96. elif pkg_child.tag == '{http://linux.duke.edu/metadata/common}version':
  97. pkg_version = pkg_child.attrib.get('ver')
  98. if pkg_version:
  99. pkg_epoch = pkg_child.attrib.get('epoch', '0')
  100. if pkg_epoch != '0':
  101. pkg_version = pkg_epoch + ':' + pkg_version
  102. pkg_rel = pkg_child.attrib.get('rel')
  103. if pkg_rel:
  104. pkg_version = pkg_version + '-' + pkg_rel
  105. elif pkg_child.tag == '{http://linux.duke.edu/metadata/common}location':
  106. pkg_href = pkg_child.attrib.get('href')
  107. if pkg_href:
  108. pkg_url = os.path.join(base_url, pkg_href)
  109. elif pkg_child.tag == '{http://linux.duke.edu/metadata/common}format':
  110. for format_child in pkg_child:
  111. if format_child.tag == '{http://linux.duke.edu/metadata/rpm}sourcerpm':
  112. if format_child.text:
  113. pkg_src_name = '-'.join(format_child.text.split('-')[:-2])
  114. if format_child.tag != '{http://linux.duke.edu/metadata/rpm}provides':
  115. continue
  116. for provides in format_child:
  117. if (
  118. provides.tag != '{http://linux.duke.edu/metadata/rpm}entry' or
  119. 'name' not in provides.attrib
  120. ):
  121. continue
  122. prov_version = None
  123. if provides.attrib.get('flags', '') == 'EQ':
  124. prov_version = provides.attrib.get('ver')
  125. if prov_version:
  126. prov_epoch = provides.attrib.get('epoch', '0')
  127. if prov_epoch != '0':
  128. prov_version = prov_epoch + ':' + prov_version
  129. prov_rel = provides.attrib.get('rel')
  130. if prov_rel:
  131. prov_version = prov_version + '-' + prov_rel
  132. pkg_provs.append((provides.attrib['name'], prov_version))
  133. yield PackageEntry(pkg_name, pkg_version, pkg_url, pkg_src_name)
  134. for prov_name, prov_version in pkg_provs:
  135. yield PackageEntry(prov_name, prov_version, pkg_url, pkg_src_name, pkg_name)
  136. element.clear()
  137. def rpm_base_url(base_url):
  138. """
  139. Create an enumerable cache for an RPM repository.
  140. :param base_url: the URL of the RPM repository.
  141. :returns: an enumerable repository cache instance.
  142. """
  143. return RepositoryCacheCollection(
  144. lambda os_name, os_code_name, os_arch:
  145. enumerate_rpm_packages(base_url, os_name, os_code_name, os_arch))