Coverage for dak/generate_packages_sources2.py: 86%

142 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2026-01-04 16:18 +0000

1#! /usr/bin/env python3 

2 

3""" 

4Generate Packages/Sources files 

5 

6@contact: Debian FTP Master <ftpmaster@debian.org> 

7@copyright: 2011 Ansgar Burchardt <ansgar@debian.org> 

8@copyright: Based on daklib/lists.py and dak/generate_filelist.py: 

9 2009-2011 Torsten Werner <twerner@debian.org> 

10@copyright: Based on dak/generate_packages_sources.py: 

11 2000, 2001, 2002, 2006 James Troup <james@nocrew.org> 

12 2009 Mark Hymers <mhy@debian.org> 

13 2010 Joerg Jaspert <joerg@debian.org> 

14@license: GNU General Public License version 2 or later 

15""" 

16 

17# This program is free software; you can redistribute it and/or modify 

18# it under the terms of the GNU General Public License as published by 

19# the Free Software Foundation; either version 2 of the License, or 

20# (at your option) any later version. 

21 

22# This program is distributed in the hope that it will be useful, 

23# but WITHOUT ANY WARRANTY; without even the implied warranty of 

24# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

25# GNU General Public License for more details. 

26 

27# You should have received a copy of the GNU General Public License 

28# along with this program; if not, write to the Free Software 

29# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

30 

31import sys 

32from typing import Any, NoReturn 

33 

34import apt_pkg 

35from sqlalchemy import sql 

36 

37 

38def usage() -> NoReturn: 

39 print( 

40 """Usage: dak generate-packages-sources2 [OPTIONS] 

41Generate the Packages/Sources files 

42 

43 -a, --archive=ARCHIVE process suites in ARCHIVE 

44 -s, --suite=SUITE process this suite 

45 Default: All suites not marked 'untouchable' 

46 -f, --force Allow processing of untouchable suites 

47 CAREFUL: Only to be used at point release time! 

48 -h, --help show this help and exit 

49 

50SUITE can be a space separated list, e.g. 

51 --suite=unstable testing 

52""" 

53 ) 

54 sys.exit() 

55 

56 

57############################################################################# 

58 

59 

60# Here be dragons. 

61_sources_query = R""" 

62SELECT 

63 

64 (SELECT 

65 STRING_AGG( 

66 CASE 

67 WHEN key = 'Source' THEN E'Package\: ' 

68 WHEN key = 'Files' AND suite.checksums && array['md5sum'] THEN E'Files\:\n ' || f.md5sum || ' ' || f.size || ' ' || SUBSTRING(f.filename FROM E'/([^/]*)\\Z') 

69 WHEN key = 'Files' THEN NULL 

70 WHEN key = 'Checksums-Sha1' AND suite.checksums && array['sha1'] THEN E'Checksums-Sha1\:\n ' || f.sha1sum || ' ' || f.size || ' ' || SUBSTRING(f.filename FROM E'/([^/]*)\\Z') 

71 WHEN key = 'Checksums-Sha1' THEN NULL 

72 WHEN key = 'Checksums-Sha256' AND suite.checksums && array['sha256'] THEN E'Checksums-Sha256\:\n ' || f.sha256sum || ' ' || f.size || ' ' || SUBSTRING(f.filename FROM E'/([^/]*)\\Z') 

73 WHEN key = 'Checksums-Sha256' THEN NULL 

74 ELSE key || E'\: ' 

75 END || value, E'\n' ORDER BY mk.ordering, mk.key) 

76 FROM 

77 source_metadata sm 

78 JOIN metadata_keys mk ON mk.key_id = sm.key_id 

79 WHERE s.id=sm.src_id 

80 ) 

81 || 

82 CASE 

83 WHEN src_associations_full.extra_source THEN E'\nExtra-Source-Only\: yes' 

84 ELSE '' 

85 END 

86 || 

87 E'\nDirectory\: pool/' || :component_name || '/' || SUBSTRING(f.filename FROM E'\\A(.*)/[^/]*\\Z') 

88 || 

89 E'\nPriority\: ' || COALESCE(pri.priority, 'optional') 

90 || 

91 E'\nSection\: ' || COALESCE(sec.section, 'misc') 

92 

93FROM 

94 

95source s 

96JOIN src_associations_full ON src_associations_full.suite = :suite AND s.id = src_associations_full.source 

97JOIN files f ON s.file=f.id 

98JOIN files_archive_map fam 

99 ON fam.file_id = f.id 

100 AND fam.archive_id = (SELECT archive_id FROM suite WHERE id = :suite) 

101 AND fam.component_id = :component 

102LEFT JOIN override o ON o.package = s.source 

103 AND o.suite = :overridesuite 

104 AND o.component = :component 

105 AND o.type = :dsc_type 

106LEFT JOIN section sec ON o.section = sec.id 

107LEFT JOIN priority pri ON o.priority = pri.id 

108LEFT JOIN suite on suite.id = :suite 

109 

110ORDER BY 

111s.source, s.version 

112""" 

113 

114 

115def generate_sources(suite_id: int, component_id: int) -> tuple[int, list[str]]: 

116 global _sources_query 

117 from daklib.dakmultiprocessing import PROC_STATUS_SUCCESS 

118 from daklib.dbconn import Component, DBConn, OverrideType, Suite 

119 from daklib.filewriter import SourcesFileWriter 

120 

121 session = DBConn().session() 

122 dsc_type = ( 

123 session.query(OverrideType).filter_by(overridetype="dsc").one().overridetype_id 

124 ) 

125 

126 suite = session.get_one(Suite, suite_id) 

127 component = session.get_one(Component, component_id) 

128 

129 overridesuite_id = suite.get_overridesuite().suite_id 

130 

131 writer_args: dict[str, Any] = { 

132 "archive": suite.archive.path, 

133 "suite": suite.suite_name, 

134 "component": component.component_name, 

135 } 

136 if suite.indices_compression is not None: 136 ↛ 138line 136 didn't jump to line 138 because the condition on line 136 was always true

137 writer_args["compression"] = suite.indices_compression 

138 writer = SourcesFileWriter(**writer_args) 

139 output = writer.open() 

140 

141 # run query and write Sources 

142 r = session.execute( 

143 sql.text(_sources_query), 

144 { 

145 "suite": suite_id, 

146 "component": component_id, 

147 "component_name": component.component_name, 

148 "dsc_type": dsc_type, 

149 "overridesuite": overridesuite_id, 

150 }, 

151 ) 

152 for (stanza,) in r: 

153 print(stanza, file=output) 

154 print("", file=output) 

155 

156 writer.close() 

157 

158 message = ["generate sources", suite.suite_name, component.component_name] 

159 session.rollback() 

160 return (PROC_STATUS_SUCCESS, message) 

161 

162 

163############################################################################# 

164 

165 

166# Here be large dragons. 

167_packages_query = R""" 

168WITH 

169 

170 tmp AS ( 

171 SELECT 

172 b.id AS binary_id, 

173 b.package AS package, 

174 b.version AS version, 

175 b.architecture AS architecture, 

176 b.source AS source_id, 

177 s.source AS source, 

178 f.filename AS filename, 

179 f.size AS size, 

180 f.md5sum AS md5sum, 

181 f.sha1sum AS sha1sum, 

182 f.sha256sum AS sha256sum, 

183 (SELECT value FROM binaries_metadata 

184 WHERE bin_id = b.id 

185 AND key_id = (SELECT key_id FROM metadata_keys WHERE key = 'Priority')) 

186 AS fallback_priority, 

187 (SELECT value FROM binaries_metadata 

188 WHERE bin_id = b.id 

189 AND key_id = (SELECT key_id FROM metadata_keys WHERE key = 'Section')) 

190 AS fallback_section 

191 FROM 

192 binaries b 

193 JOIN bin_associations ba ON b.id = ba.bin 

194 JOIN files f ON f.id = b.file 

195 JOIN files_archive_map fam ON f.id = fam.file_id AND fam.archive_id = :archive_id 

196 JOIN source s ON b.source = s.id 

197 WHERE 

198 (b.architecture = :arch_all OR b.architecture = :arch) AND b.type = :type_name 

199 AND ba.suite = :suite 

200 AND fam.component_id = :component 

201 ) 

202 

203SELECT 

204 (SELECT 

205 STRING_AGG(key || E'\: ' || value, E'\n' ORDER BY ordering, key) 

206 FROM 

207 (SELECT key, ordering, 

208 CASE WHEN :include_long_description = 'false' AND key = 'Description' 

209 THEN SUBSTRING(value FROM E'\\A[^\n]*') 

210 ELSE value 

211 END AS value 

212 FROM 

213 binaries_metadata bm 

214 JOIN metadata_keys mk ON mk.key_id = bm.key_id 

215 WHERE 

216 bm.bin_id = tmp.binary_id 

217 AND key != ALL (:metadata_skip) 

218 ) AS metadata 

219 ) 

220 || COALESCE(E'\n' || (SELECT 

221 STRING_AGG(key || E'\: ' || value, E'\n' ORDER BY key) 

222 FROM external_overrides eo 

223 WHERE 

224 eo.package = tmp.package 

225 AND eo.suite = :overridesuite AND eo.component = :component 

226 ), '') 

227 || E'\nSection\: ' || COALESCE(sec.section, tmp.fallback_section) 

228 || E'\nPriority\: ' || COALESCE(pri.priority, tmp.fallback_priority) 

229 || E'\nFilename\: pool/' || :component_name || '/' || tmp.filename 

230 || E'\nSize\: ' || tmp.size 

231 || CASE WHEN suite.checksums && array['md5sum'] THEN E'\nMD5sum\: ' || tmp.md5sum ELSE '' END 

232 || CASE WHEN suite.checksums && array['sha1'] THEN E'\nSHA1\: ' || tmp.sha1sum ELSE '' END 

233 || CASE WHEN suite.checksums && array['sha256'] THEN E'\nSHA256\: ' || tmp.sha256sum ELSE '' END 

234 

235FROM 

236 tmp 

237 LEFT JOIN override o ON o.package = tmp.package 

238 AND o.type = :type_id 

239 AND o.suite = :overridesuite 

240 AND o.component = :component 

241 LEFT JOIN section sec ON sec.id = o.section 

242 LEFT JOIN priority pri ON pri.id = o.priority 

243 LEFT JOIN suite ON suite.id = :suite 

244 

245WHERE 

246 ( 

247 architecture <> :arch_all 

248 OR 

249 (architecture = :arch_all AND source_id IN (SELECT source_id FROM tmp WHERE architecture <> :arch_all)) 

250 OR 

251 (architecture = :arch_all AND source NOT IN (SELECT DISTINCT source FROM tmp WHERE architecture <> :arch_all)) 

252 ) 

253 

254ORDER BY tmp.source, tmp.package, tmp.version 

255""" 

256 

257 

258def generate_packages( 

259 suite_id: int, component_id: int, architecture_id: int, type_name: str 

260) -> tuple[int, list[str]]: 

261 global _packages_query 

262 from daklib.dakmultiprocessing import PROC_STATUS_SUCCESS 

263 from daklib.dbconn import Architecture, Component, DBConn, OverrideType, Suite 

264 from daklib.filewriter import PackagesFileWriter 

265 

266 session = DBConn().session() 

267 arch_all_id = session.query(Architecture).filter_by(arch_string="all").one().arch_id 

268 type_id = ( 

269 session.query(OverrideType) 

270 .filter_by(overridetype=type_name) 

271 .one() 

272 .overridetype_id 

273 ) 

274 

275 suite = session.get_one(Suite, suite_id) 

276 component = session.get_one(Component, component_id) 

277 architecture = session.get_one(Architecture, architecture_id) 

278 

279 overridesuite_id = suite.get_overridesuite().suite_id 

280 include_long_description = suite.include_long_description 

281 

282 # We currently filter out the "Tag" line. They are set by external 

283 # overrides and NOT by the maintainer. And actually having it set by 

284 # maintainer means we output it twice at the moment -> which breaks 

285 # dselect. 

286 metadata_skip = ["Section", "Priority", "Tag"] 

287 if include_long_description: 287 ↛ 288line 287 didn't jump to line 288 because the condition on line 287 was never true

288 metadata_skip.append("Description-md5") 

289 

290 writer_args: dict[str, Any] = { 

291 "archive": suite.archive.path, 

292 "suite": suite.suite_name, 

293 "component": component.component_name, 

294 "architecture": architecture.arch_string, 

295 "debtype": type_name, 

296 } 

297 if suite.indices_compression is not None: 297 ↛ 299line 297 didn't jump to line 299 because the condition on line 297 was always true

298 writer_args["compression"] = suite.indices_compression 

299 writer = PackagesFileWriter(**writer_args) 

300 output = writer.open() 

301 

302 r = session.execute( 

303 sql.text(_packages_query), 

304 { 

305 "archive_id": suite.archive.archive_id, 

306 "suite": suite_id, 

307 "component": component_id, 

308 "component_name": component.component_name, 

309 "arch": architecture_id, 

310 "type_id": type_id, 

311 "type_name": type_name, 

312 "arch_all": arch_all_id, 

313 "overridesuite": overridesuite_id, 

314 "metadata_skip": metadata_skip, 

315 "include_long_description": "true" if include_long_description else "false", 

316 }, 

317 ) 

318 for (stanza,) in r: 

319 print(stanza, file=output) 

320 print("", file=output) 

321 

322 writer.close() 

323 

324 message = [ 

325 "generate-packages", 

326 suite.suite_name, 

327 component.component_name, 

328 architecture.arch_string, 

329 ] 

330 session.rollback() 

331 return (PROC_STATUS_SUCCESS, message) 

332 

333 

334############################################################################# 

335 

336 

337_translations_query = r""" 

338WITH 

339 override_suite AS 

340 (SELECT 

341 s.id AS id, 

342 COALESCE(os.id, s.id) AS overridesuite_id 

343 FROM suite AS s LEFT JOIN suite AS os ON s.overridesuite = os.suite_name) 

344 

345SELECT 

346 E'Package\: ' || b.package 

347 || E'\nDescription-md5\: ' || bm_description_md5.value 

348 || E'\nDescription-en\: ' || bm_description.value 

349 || E'\n' 

350FROM binaries b 

351 -- join tables for suite and component 

352 JOIN bin_associations ba ON b.id = ba.bin 

353 JOIN override_suite os ON os.id = ba.suite 

354 JOIN override o ON b.package = o.package AND o.suite = os.overridesuite_id AND o.type = (SELECT id FROM override_type WHERE type = 'deb') 

355 

356 -- join tables for Description and Description-md5 

357 JOIN binaries_metadata bm_description ON b.id = bm_description.bin_id AND bm_description.key_id = (SELECT key_id FROM metadata_keys WHERE key = 'Description') 

358 JOIN binaries_metadata bm_description_md5 ON b.id = bm_description_md5.bin_id AND bm_description_md5.key_id = (SELECT key_id FROM metadata_keys WHERE key = 'Description-md5') 

359 

360 -- we want to sort by source name 

361 JOIN source s ON b.source = s.id 

362 

363WHERE ba.suite = :suite AND o.component = :component 

364GROUP BY b.package, bm_description_md5.value, bm_description.value 

365ORDER BY MIN(s.source), b.package, bm_description_md5.value 

366""" 

367 

368 

369def generate_translations(suite_id: int, component_id: int) -> tuple[int, list[str]]: 

370 global _translations_query 

371 from daklib.dakmultiprocessing import PROC_STATUS_SUCCESS 

372 from daklib.dbconn import Component, DBConn, Suite 

373 from daklib.filewriter import TranslationFileWriter 

374 

375 session = DBConn().session() 

376 suite = session.get_one(Suite, suite_id) 

377 component = session.get_one(Component, component_id) 

378 

379 writer_args: dict[str, Any] = { 

380 "archive": suite.archive.path, 

381 "suite": suite.suite_name, 

382 "component": component.component_name, 

383 "language": "en", 

384 } 

385 if suite.i18n_compression is not None: 385 ↛ 387line 385 didn't jump to line 387 because the condition on line 385 was always true

386 writer_args["compression"] = suite.i18n_compression 

387 writer = TranslationFileWriter(**writer_args) 

388 output = writer.open() 

389 

390 r = session.execute( 

391 sql.text(_translations_query), {"suite": suite_id, "component": component_id} 

392 ) 

393 for (stanza,) in r: 

394 print(stanza, file=output) 

395 

396 writer.close() 

397 

398 message = ["generate-translations", suite.suite_name, component.component_name] 

399 session.rollback() 

400 return (PROC_STATUS_SUCCESS, message) 

401 

402 

403############################################################################# 

404 

405 

406def main() -> None: 

407 from daklib import daklog 

408 from daklib.config import Config 

409 

410 cnf = Config() 

411 

412 Arguments = [ 

413 ("h", "help", "Generate-Packages-Sources::Options::Help"), 

414 ("a", "archive", "Generate-Packages-Sources::Options::Archive", "HasArg"), 

415 ("s", "suite", "Generate-Packages-Sources::Options::Suite", "HasArg"), 

416 ("f", "force", "Generate-Packages-Sources::Options::Force"), 

417 ("o", "option", "", "ArbItem"), 

418 ] 

419 

420 apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) # type: ignore[attr-defined] 

421 try: 

422 Options = cnf.subtree("Generate-Packages-Sources::Options") 

423 except KeyError: 

424 Options = {} 

425 

426 if "Help" in Options: 

427 usage() 

428 

429 from daklib.dakmultiprocessing import ( 

430 PROC_STATUS_SIGNALRAISED, 

431 PROC_STATUS_SUCCESS, 

432 DakProcessPool, 

433 ) 

434 

435 pool = DakProcessPool() 

436 

437 logger = daklog.Logger("generate-packages-sources2") 

438 

439 from daklib.dbconn import Archive, DBConn, Suite, get_suite 

440 

441 session = DBConn().session() 

442 session.execute(sql.text("SELECT add_missing_description_md5()")) 

443 session.commit() 

444 

445 import daklib.utils 

446 

447 if "Suite" in Options: 

448 suites: list[Suite] = [] 

449 suite_names = daklib.utils.split_args(Options["Suite"]) 

450 for name in suite_names: 

451 suite = get_suite(name.lower(), session) 

452 if suite: 452 ↛ 455line 452 didn't jump to line 455 because the condition on line 452 was always true

453 suites.append(suite) 

454 else: 

455 print("I: Cannot find suite %s" % name) 

456 logger.log(["Cannot find suite %s" % name]) 

457 else: 

458 query = session.query(Suite).filter(Suite.untouchable == False) # noqa:E712 

459 if "Archive" in Options: 459 ↛ 464line 459 didn't jump to line 464 because the condition on line 459 was always true

460 archive_names = daklib.utils.split_args(Options["Archive"]) 

461 query = query.join(Suite.archive).filter( 

462 Archive.archive_name.in_(archive_names) 

463 ) 

464 suites = query.all() 

465 

466 force = "Force" in Options and Options["Force"] 

467 

468 def parse_results(message): 

469 # Split out into (code, msg) 

470 code, msg = message 

471 if code == PROC_STATUS_SUCCESS: 

472 logger.log(msg) 

473 elif code == PROC_STATUS_SIGNALRAISED: 

474 logger.log(["E: Subprocess received signal ", msg]) 

475 else: 

476 logger.log(["E: ", msg]) 

477 

478 # Lock tables so that nobody can change things underneath us 

479 session.execute(sql.text("LOCK TABLE src_associations IN SHARE MODE")) 

480 session.execute(sql.text("LOCK TABLE bin_associations IN SHARE MODE")) 

481 

482 for s in suites: 

483 component_ids = [c.component_id for c in s.components] 

484 if s.untouchable and not force: 484 ↛ 485line 484 didn't jump to line 485 because the condition on line 484 was never true

485 import daklib.utils 

486 

487 daklib.utils.fubar( 

488 "Refusing to touch %s (untouchable and not forced)" % s.suite_name 

489 ) 

490 for c in component_ids: 

491 pool.apply_async(generate_sources, [s.suite_id, c], callback=parse_results) 

492 if not s.include_long_description: 492 ↛ 496line 492 didn't jump to line 496 because the condition on line 492 was always true

493 pool.apply_async( 

494 generate_translations, [s.suite_id, c], callback=parse_results 

495 ) 

496 for a in s.architectures: 

497 if a == "source": 

498 continue 

499 pool.apply_async( 

500 generate_packages, 

501 [s.suite_id, c, a.arch_id, "deb"], 

502 callback=parse_results, 

503 ) 

504 pool.apply_async( 

505 generate_packages, 

506 [s.suite_id, c, a.arch_id, "udeb"], 

507 callback=parse_results, 

508 ) 

509 

510 pool.close() 

511 pool.join() 

512 

513 # this script doesn't change the database 

514 session.close() 

515 

516 logger.close() 

517 

518 sys.exit(pool.overall_status()) 

519 

520 

521if __name__ == "__main__": 

522 main()