apk.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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. import tarfile
  29. from . import open_gz_url
  30. from . import PackageEntry
  31. from . import RepositoryCacheCollection
  32. def parse_apkindex(f):
  33. # An example of an APKINDEX entry. Entries are divided by a blank line.
  34. # V:2.1.1-r0
  35. # A:x86_64
  36. # S:6645
  37. # I:28672
  38. # T:RTP proxy (documentation)
  39. # U:https://www.rtpproxy.org/
  40. # L:BSD-2-CLause
  41. # o:rtpproxy
  42. # m:Natanael Copa <ncopa@alpinelinux.org>
  43. # t:1602354892
  44. # c:183e99f73bb1223768aa7231a836a3e98e94c03e
  45. # i:docs rtpproxy=2.1.1-r0
  46. # p:alias-of-rtpproxy=2.1.1-r0
  47. while True:
  48. entry = {}
  49. while (l := f.readline().decode('utf-8')) not in ['', '\n']:
  50. k, v = l.strip().split(':', 1)
  51. entry[k] = v
  52. if entry:
  53. yield entry
  54. else:
  55. break
  56. class Dependency:
  57. """
  58. Dependency class represents apk (Alpine Package) dependency information.
  59. """
  60. type = None
  61. """
  62. :ivar: the type of the Dependency.
  63. e.g.
  64. - None: package
  65. - 'cmd': command
  66. - 'so': shared object
  67. """
  68. name = None
  69. """
  70. :ivar: the name of the Dependency.
  71. """
  72. version = None
  73. """
  74. :ivar: the version of the Dependency.
  75. """
  76. def __init__(self, item):
  77. try:
  78. self.type, self.name = item.split(':', 1)
  79. except (ValueError):
  80. self.name = item
  81. try:
  82. self.name, self.version = self.name.split('=', 1)
  83. except (ValueError):
  84. pass
  85. def parse_deps(text):
  86. return [Dependency(item) for item in text.split(' ')]
  87. def enumerate_apk_packages(base_url, os_name, os_code_name, os_arch):
  88. """
  89. Enumerate packages in an apk (Alpine Package) repository.
  90. :param base_url: the apk repository base URL.
  91. :param os_name: the name of the OS associated with the repository.
  92. :param os_code_name: the OS version associated with the repository.
  93. :param os_arch: the system architecture associated with the repository.
  94. :returns: an enumeration of package entries.
  95. """
  96. base_url = base_url.replace('$releasever', os_code_name)
  97. apkindex_url = os.path.join(base_url, os_arch, 'APKINDEX.tar.gz')
  98. print('Reading apk package metadata from ' + apkindex_url)
  99. with open_gz_url(apkindex_url) as f:
  100. with tarfile.open(mode='r|', fileobj=f) as tf:
  101. index = None
  102. for ti in tf:
  103. if ti.name == 'APKINDEX':
  104. index = tf.extractfile(ti)
  105. break
  106. if index is None:
  107. raise RuntimeError('APKINDEX url did not contain an APKINDEX file')
  108. for index_entry in parse_apkindex(index):
  109. pkg_name, pkg_version, source_name = index_entry['P'], index_entry['V'], index_entry['o']
  110. pkg_filename = '%s-%s.apk' % (pkg_name, pkg_version)
  111. pkg_url = os.path.join(base_url, pkg_filename)
  112. yield PackageEntry(pkg_name, pkg_version, pkg_url, source_name=source_name)
  113. if 'p' in index_entry:
  114. for d in parse_deps(index_entry['p']):
  115. if d.type is None:
  116. yield PackageEntry(d.name, pkg_version, pkg_url, source_name=source_name, binary_name=pkg_name)
  117. def apk_base_url(base_url):
  118. """
  119. Create an enumerable cache for an apk (Alpine Package) repository.
  120. :param base_url: the URL of the apk repository.
  121. :returns: an enumerable repository cache instance.
  122. """
  123. return RepositoryCacheCollection(
  124. lambda os_name, os_code_name, os_arch:
  125. enumerate_apk_packages(base_url, os_name, os_code_name, os_arch))