cruft.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. """
  2. helper functions for cruft-report
  3. @contact: Debian FTPMaster <ftpmaster@debian.org>
  4. @copyright 2011 Torsten Werner <twerner@debian.org>
  5. """
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # (at your option) any later version.
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17. ################################################################################
  18. from daklib.dbconn import *
  19. from sqlalchemy import func
  20. from sqlalchemy.orm import object_session, aliased
  21. def newer_version(lowersuite_name: str, highersuite_name: str, session, include_equal=False) -> list[tuple[str, str, str]]:
  22. '''
  23. Finds newer versions in lowersuite_name than in highersuite_name. Returns a
  24. list of tuples (source, higherversion, lowerversion) where higherversion is
  25. the newest version from highersuite_name and lowerversion is the newest
  26. version from lowersuite_name.
  27. '''
  28. lowersuite = get_suite(lowersuite_name, session)
  29. highersuite = get_suite(highersuite_name, session)
  30. def get_suite_sources(suite):
  31. q1 = session.query(DBSource.source,
  32. func.max(DBSource.version).label('version')). \
  33. with_parent(suite). \
  34. group_by(DBSource.source). \
  35. subquery()
  36. return aliased(q1)
  37. def get_suite_binaries(suite):
  38. q1 = session.query(DBBinary.package,
  39. DBSource.source,
  40. func.max(DBSource.version).label('version'),
  41. Architecture.arch_string,
  42. func.max(DBBinary.version).label('binversion')). \
  43. filter(DBBinary.suites.contains(suite)). \
  44. join(DBBinary.source). \
  45. join(DBBinary.architecture). \
  46. group_by(
  47. DBBinary.package,
  48. DBSource.source,
  49. Architecture.arch_string,
  50. ). \
  51. subquery()
  52. return aliased(q1)
  53. highq = get_suite_sources(highersuite)
  54. lowq = get_suite_sources(lowersuite)
  55. query = session.query(
  56. highq.c.source,
  57. highq.c.version.label('higherversion'),
  58. lowq.c.version.label('lowerversion')
  59. ). \
  60. join(lowq, highq.c.source == lowq.c.source)
  61. if include_equal:
  62. query = query.filter(highq.c.version <= lowq.c.version)
  63. else:
  64. query = query.filter(highq.c.version < lowq.c.version)
  65. list = []
  66. # get all sources that have a higher version in lowersuite than in
  67. # highersuite
  68. for (source, higherversion, lowerversion) in query:
  69. q1 = session.query(DBBinary.package,
  70. DBSource.source,
  71. DBSource.version,
  72. Architecture.arch_string
  73. ). \
  74. filter(DBBinary.suites.contains(highersuite)). \
  75. join(DBBinary.source). \
  76. join(DBBinary.architecture). \
  77. filter(DBSource.source == source). \
  78. subquery()
  79. q2 = session.query(q1.c.arch_string).group_by(q1.c.arch_string)
  80. # all architectures for which source has binaries in highersuite
  81. archs_high = set(x[0] for x in q2.all())
  82. highq = get_suite_binaries(highersuite)
  83. lowq = get_suite_binaries(lowersuite)
  84. query = session.query(highq.c.arch_string). \
  85. join(lowq, highq.c.source == lowq.c.source). \
  86. filter(highq.c.arch_string == lowq.c.arch_string). \
  87. filter(highq.c.package == lowq.c.package). \
  88. filter(highq.c.source == source)
  89. if include_equal:
  90. query = query. \
  91. filter(highq.c.binversion <= lowq.c.binversion). \
  92. filter(highq.c.version <= lowq.c.version)
  93. else:
  94. query = query. \
  95. filter(highq.c.binversion < lowq.c.binversion). \
  96. filter(highq.c.version < lowq.c.version)
  97. query = query.group_by(highq.c.arch_string)
  98. # all architectures for which source has a newer binary in lowersuite
  99. archs_newer = set(x[0] for x in query.all())
  100. # if has at least one binary in lowersuite which is newer than the one
  101. # in highersuite on each architecture for which source has binaries in
  102. # highersuite, we know that the builds for all relevant architecture
  103. # are done, so we can remove the old source with it's binaries
  104. if (archs_newer >= archs_high):
  105. list.append((source, higherversion, lowerversion))
  106. list.sort()
  107. return list
  108. def get_package_names(suite: Suite):
  109. '''
  110. Returns a query that selects all distinct package names from suite ordered
  111. by package name.
  112. '''
  113. session = object_session(suite)
  114. return session.query(DBBinary.package).with_parent(suite). \
  115. group_by(DBBinary.package).order_by(DBBinary.package)
  116. class NamedSource:
  117. '''
  118. A source package identified by its name with all of its versions in a
  119. suite.
  120. '''
  121. def __init__(self, suite: Suite, source: str):
  122. self.source = source
  123. query = suite.sources.filter_by(source=source). \
  124. order_by(DBSource.version)
  125. self.versions = [src.version for src in query]
  126. def __str__(self):
  127. return "%s(%s)" % (self.source, ", ".join(self.versions))
  128. class DejavuBinary:
  129. '''
  130. A binary package identified by its name which gets built by multiple source
  131. packages in a suite. The architecture is ignored which leads to the
  132. following corner case, e.g.:
  133. If a source package 'foo-mips' that builds a binary package 'foo' on mips
  134. and another source package 'foo-mipsel' builds a binary package with the
  135. same name 'foo' on mipsel then the binary package 'foo' will be reported as
  136. built from multiple source packages.
  137. '''
  138. def __init__(self, suite: Suite, package: str):
  139. self.package = package
  140. session = object_session(suite)
  141. # We need a subquery to make sure that both binary and source packages
  142. # are in the right suite.
  143. bin_query = suite.binaries.filter_by(package=package).subquery()
  144. src_query = session.query(DBSource.source).with_parent(suite). \
  145. join(bin_query).order_by(DBSource.source).group_by(DBSource.source)
  146. self.sources = []
  147. if src_query.count() > 1:
  148. for source, in src_query:
  149. self.sources.append(str(NamedSource(suite, source)))
  150. def has_multiple_sources(self) -> bool:
  151. 'Has the package been built by multiple sources?'
  152. return len(self.sources) > 1
  153. def __str__(self):
  154. return "%s built by: %s" % (self.package, ", ".join(self.sources))
  155. def report_multiple_source(suite: Suite) -> None:
  156. '''
  157. Reports binary packages built from multiple source package with different
  158. names.
  159. '''
  160. print("Built from multiple source packages")
  161. print("-----------------------------------")
  162. print()
  163. for package, in get_package_names(suite):
  164. binary = DejavuBinary(suite, package)
  165. if binary.has_multiple_sources():
  166. print(binary)
  167. print()
  168. def query_without_source(suite_id: int, session):
  169. """searches for arch: all packages from suite that do no longer
  170. reference a source package in the same suite
  171. subquery unique_binaries: selects all packages with only 1 version
  172. in suite since 'dak rm' does not allow to specify version numbers"""
  173. query = """
  174. with unique_binaries as
  175. (select package, max(version) as version, max(source) as source
  176. from bin_associations_binaries
  177. where architecture = 2 and suite = :suite_id
  178. group by package having count(*) = 1)
  179. select ub.package, ub.version
  180. from unique_binaries ub
  181. left join src_associations_src sas
  182. on ub.source = sas.src and sas.suite = :suite_id
  183. where sas.id is null
  184. order by ub.package"""
  185. return session.execute(query, {'suite_id': suite_id})
  186. def queryNBS(suite_id: int, session):
  187. """This one is really complex. It searches arch != all packages that
  188. are no longer built from current source packages in suite.
  189. temp table unique_binaries: will be populated with packages that
  190. have only one version in suite because 'dak rm' does not allow
  191. specifying version numbers
  192. temp table newest_binaries: will be populated with packages that are
  193. built from current sources
  194. subquery uptodate_arch: returns all architectures built from current
  195. sources
  196. subquery unique_binaries_uptodate_arch: returns all packages in
  197. architectures from uptodate_arch
  198. subquery unique_binaries_uptodate_arch_agg: same as
  199. unique_binaries_uptodate_arch but with column architecture
  200. aggregated to array
  201. subquery uptodate_packages: similar to uptodate_arch but returns all
  202. packages built from current sources
  203. subquery outdated_packages: returns all packages with architectures
  204. no longer built from current source
  205. """
  206. query = """
  207. with
  208. unique_binaries as
  209. (select
  210. bab.package,
  211. bab.architecture,
  212. max(bab.source) as source
  213. from bin_associations_binaries bab
  214. where bab.suite = :suite_id and bab.architecture > 2
  215. group by package, architecture having count(*) = 1),
  216. newest_binaries as
  217. (select ub.package, ub.architecture, nsa.source, nsa.version
  218. from unique_binaries ub
  219. join newest_src_association nsa
  220. on ub.source = nsa.src and nsa.suite = :suite_id),
  221. uptodate_arch as
  222. (select architecture, source, version
  223. from newest_binaries
  224. group by architecture, source, version),
  225. unique_binaries_uptodate_arch as
  226. (select ub.package, (select a.arch_string from architecture a where a.id = ub.architecture) as arch_string, ua.source, ua.version
  227. from unique_binaries ub
  228. join source s
  229. on ub.source = s.id
  230. join uptodate_arch ua
  231. on ub.architecture = ua.architecture and s.source = ua.source),
  232. unique_binaries_uptodate_arch_agg as
  233. (select ubua.package,
  234. array_agg(ubua.arch_string order by ubua.arch_string) as arch_list,
  235. ubua.source, ubua.version
  236. from unique_binaries_uptodate_arch ubua
  237. group by ubua.source, ubua.version, ubua.package),
  238. uptodate_packages as
  239. (select package, source, version
  240. from newest_binaries
  241. group by package, source, version),
  242. outdated_packages as
  243. (select array_agg(package order by package) as pkg_list,
  244. arch_list, source, version
  245. from unique_binaries_uptodate_arch_agg
  246. where package not in
  247. (select package from uptodate_packages)
  248. group by arch_list, source, version)
  249. select * from outdated_packages order by source"""
  250. return session.execute(query, {'suite_id': suite_id})
  251. def queryNBS_metadata(suite_id: int, session):
  252. """searches for NBS packages based on metadata extraction of the
  253. newest source for a given suite"""
  254. query = """
  255. select string_agg(bin.package, ' ' order by bin.package), (
  256. select arch_string
  257. from architecture
  258. where id = bin.architecture) as architecture, src.source, newsrc.version
  259. from bin_associations_binaries bin
  260. join src_associations_src src
  261. on src.src = bin.source
  262. and src.suite = bin.suite
  263. join newest_src_association newsrc
  264. on newsrc.source = src.source
  265. and newsrc.version != src.version
  266. and newsrc.suite = bin.suite
  267. where bin.suite = :suite_id
  268. and bin.package not in (
  269. select trim(' \n' from unnest(string_to_array(meta.value, ',')))
  270. from source_metadata meta
  271. where meta.src_id = (
  272. select newsrc.src
  273. from newest_src_association newsrc
  274. where newsrc.source = (
  275. select s.source
  276. from source s
  277. where s.id = bin.source)
  278. and newsrc.suite = bin.suite)
  279. and key_id = (
  280. select key_id
  281. from metadata_keys
  282. where key = 'Binary'))
  283. group by src.source, newsrc.version, architecture
  284. order by src.source, newsrc.version, bin.architecture"""
  285. return session.execute(query, {'suite_id': suite_id})