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 

31from typing import NoReturn 

32 

33import apt_pkg 

34import sys 

35 

36 

37def usage() -> NoReturn: 

38 print("""Usage: dak generate-packages-sources2 [OPTIONS] 

39Generate the Packages/Sources files 

40 

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

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

43 Default: All suites not marked 'untouchable' 

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

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

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

47 

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

49 --suite=unstable testing 

50""") 

51 sys.exit() 

52 

53############################################################################# 

54 

55 

56# Here be dragons. 

57_sources_query = R""" 

58SELECT 

59 

60 (SELECT 

61 STRING_AGG( 

62 CASE 

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

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

65 WHEN key = 'Files' THEN NULL 

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

67 WHEN key = 'Checksums-Sha1' THEN NULL 

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

69 WHEN key = 'Checksums-Sha256' THEN NULL 

70 ELSE key || E'\: ' 

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

72 FROM 

73 source_metadata sm 

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

75 WHERE s.id=sm.src_id 

76 ) 

77 || 

78 CASE 

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

80 ELSE '' 

81 END 

82 || 

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

84 || 

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

86 || 

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

88 

89FROM 

90 

91source s 

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

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

94JOIN files_archive_map fam 

95 ON fam.file_id = f.id 

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

97 AND fam.component_id = :component 

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

99 AND o.suite = :overridesuite 

100 AND o.component = :component 

101 AND o.type = :dsc_type 

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

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

104LEFT JOIN suite on suite.id = :suite 

105 

106ORDER BY 

107s.source, s.version 

108""" 

109 

110 

111def generate_sources(suite_id: int, component_id: int): 

112 global _sources_query 

113 from daklib.filewriter import SourcesFileWriter 

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

115 from daklib.dakmultiprocessing import PROC_STATUS_SUCCESS 

116 

117 session = DBConn().session() 

118 dsc_type = session.query(OverrideType).filter_by(overridetype='dsc').one().overridetype_id 

119 

120 suite = session.query(Suite).get(suite_id) 

121 component = session.query(Component).get(component_id) 

122 

123 overridesuite_id = suite.get_overridesuite().suite_id 

124 

125 writer_args = { 

126 'archive': suite.archive.path, 

127 'suite': suite.suite_name, 

128 'component': component.component_name 

129 } 

130 if suite.indices_compression is not None: 130 ↛ 132line 130 didn't jump to line 132, because the condition on line 130 was never false

131 writer_args['compression'] = suite.indices_compression 

132 writer = SourcesFileWriter(**writer_args) 

133 output = writer.open() 

134 

135 # run query and write Sources 

136 r = session.execute(_sources_query, {"suite": suite_id, "component": component_id, "component_name": component.component_name, "dsc_type": dsc_type, "overridesuite": overridesuite_id}) 

137 for (stanza,) in r: 

138 print(stanza, file=output) 

139 print("", file=output) 

140 

141 writer.close() 

142 

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

144 session.rollback() 

145 return (PROC_STATUS_SUCCESS, message) 

146 

147############################################################################# 

148 

149 

150# Here be large dragons. 

151_packages_query = R""" 

152WITH 

153 

154 tmp AS ( 

155 SELECT 

156 b.id AS binary_id, 

157 b.package AS package, 

158 b.version AS version, 

159 b.architecture AS architecture, 

160 b.source AS source_id, 

161 s.source AS source, 

162 f.filename AS filename, 

163 f.size AS size, 

164 f.md5sum AS md5sum, 

165 f.sha1sum AS sha1sum, 

166 f.sha256sum AS sha256sum, 

167 (SELECT value FROM binaries_metadata 

168 WHERE bin_id = b.id 

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

170 AS fallback_priority, 

171 (SELECT value FROM binaries_metadata 

172 WHERE bin_id = b.id 

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

174 AS fallback_section 

175 FROM 

176 binaries b 

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

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

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

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

181 WHERE 

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

183 AND ba.suite = :suite 

184 AND fam.component_id = :component 

185 ) 

186 

187SELECT 

188 (SELECT 

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

190 FROM 

191 (SELECT key, ordering, 

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

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

194 ELSE value 

195 END AS value 

196 FROM 

197 binaries_metadata bm 

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

199 WHERE 

200 bm.bin_id = tmp.binary_id 

201 AND key != ALL (:metadata_skip) 

202 ) AS metadata 

203 ) 

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

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

206 FROM external_overrides eo 

207 WHERE 

208 eo.package = tmp.package 

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

210 ), '') 

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

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

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

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

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

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

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

218 

219FROM 

220 tmp 

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

222 AND o.type = :type_id 

223 AND o.suite = :overridesuite 

224 AND o.component = :component 

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

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

227 LEFT JOIN suite ON suite.id = :suite 

228 

229WHERE 

230 ( 

231 architecture <> :arch_all 

232 OR 

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

234 OR 

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

236 ) 

237 

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

239""" 

240 

241 

242def generate_packages(suite_id: int, component_id: int, architecture_id: int, type_name: str): 

243 global _packages_query 

244 from daklib.filewriter import PackagesFileWriter 

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

246 from daklib.dakmultiprocessing import PROC_STATUS_SUCCESS 

247 

248 session = DBConn().session() 

249 arch_all_id = session.query(Architecture).filter_by(arch_string='all').one().arch_id 

250 type_id = session.query(OverrideType).filter_by(overridetype=type_name).one().overridetype_id 

251 

252 suite = session.query(Suite).get(suite_id) 

253 component = session.query(Component).get(component_id) 

254 architecture = session.query(Architecture).get(architecture_id) 

255 

256 overridesuite_id = suite.get_overridesuite().suite_id 

257 include_long_description = suite.include_long_description 

258 

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

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

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

262 # dselect. 

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

264 if include_long_description: 264 ↛ 265line 264 didn't jump to line 265, because the condition on line 264 was never true

265 metadata_skip.append("Description-md5") 

266 

267 writer_args = { 

268 'archive': suite.archive.path, 

269 'suite': suite.suite_name, 

270 'component': component.component_name, 

271 'architecture': architecture.arch_string, 

272 'debtype': type_name 

273 } 

274 if suite.indices_compression is not None: 274 ↛ 276line 274 didn't jump to line 276, because the condition on line 274 was never false

275 writer_args['compression'] = suite.indices_compression 

276 writer = PackagesFileWriter(**writer_args) 

277 output = writer.open() 

278 

279 r = session.execute(_packages_query, {"archive_id": suite.archive.archive_id, 

280 "suite": suite_id, "component": component_id, 'component_name': component.component_name, 

281 "arch": architecture_id, "type_id": type_id, "type_name": type_name, "arch_all": arch_all_id, 

282 "overridesuite": overridesuite_id, "metadata_skip": metadata_skip, 

283 "include_long_description": 'true' if include_long_description else 'false'}) 

284 for (stanza,) in r: 

285 print(stanza, file=output) 

286 print("", file=output) 

287 

288 writer.close() 

289 

290 message = ["generate-packages", suite.suite_name, component.component_name, architecture.arch_string] 

291 session.rollback() 

292 return (PROC_STATUS_SUCCESS, message) 

293 

294############################################################################# 

295 

296 

297_translations_query = r""" 

298WITH 

299 override_suite AS 

300 (SELECT 

301 s.id AS id, 

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

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

304 

305SELECT 

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

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

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

309 || E'\n' 

310FROM binaries b 

311 -- join tables for suite and component 

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

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

314 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') 

315 

316 -- join tables for Description and Description-md5 

317 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') 

318 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') 

319 

320 -- we want to sort by source name 

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

322 

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

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

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

326""" 

327 

328 

329def generate_translations(suite_id: int, component_id: int): 

330 global _translations_query 

331 from daklib.filewriter import TranslationFileWriter 

332 from daklib.dbconn import DBConn, Suite, Component 

333 from daklib.dakmultiprocessing import PROC_STATUS_SUCCESS 

334 

335 session = DBConn().session() 

336 suite = session.query(Suite).get(suite_id) 

337 component = session.query(Component).get(component_id) 

338 

339 writer_args = { 

340 'archive': suite.archive.path, 

341 'suite': suite.suite_name, 

342 'component': component.component_name, 

343 'language': 'en', 

344 } 

345 if suite.i18n_compression is not None: 345 ↛ 347line 345 didn't jump to line 347, because the condition on line 345 was never false

346 writer_args['compression'] = suite.i18n_compression 

347 writer = TranslationFileWriter(**writer_args) 

348 output = writer.open() 

349 

350 r = session.execute(_translations_query, {"suite": suite_id, "component": component_id}) 

351 for (stanza,) in r: 

352 print(stanza, file=output) 

353 

354 writer.close() 

355 

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

357 session.rollback() 

358 return (PROC_STATUS_SUCCESS, message) 

359 

360############################################################################# 

361 

362 

363def main(): 

364 from daklib.config import Config 

365 from daklib import daklog 

366 

367 cnf = Config() 

368 

369 Arguments = [('h', "help", "Generate-Packages-Sources::Options::Help"), 

370 ('a', 'archive', 'Generate-Packages-Sources::Options::Archive', 'HasArg'), 

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

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

373 ('o', 'option', '', 'ArbItem')] 

374 

375 apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) 

376 try: 

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

378 except KeyError: 

379 Options = {} 

380 

381 if "Help" in Options: 

382 usage() 

383 

384 from daklib.dakmultiprocessing import DakProcessPool, PROC_STATUS_SUCCESS, PROC_STATUS_SIGNALRAISED 

385 pool = DakProcessPool() 

386 

387 logger = daklog.Logger('generate-packages-sources2') 

388 

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

390 session = DBConn().session() 

391 session.execute("SELECT add_missing_description_md5()") 

392 session.commit() 

393 

394 import daklib.utils 

395 

396 if "Suite" in Options: 

397 suites = [] 

398 suite_names = daklib.utils.split_args(Options['Suite']) 

399 for s in suite_names: 

400 suite = get_suite(s.lower(), session) 

401 if suite: 401 ↛ 404line 401 didn't jump to line 404, because the condition on line 401 was never false

402 suites.append(suite) 

403 else: 

404 print("I: Cannot find suite %s" % s) 

405 logger.log(['Cannot find suite %s' % s]) 

406 else: 

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

408 if 'Archive' in Options: 408 ↛ 411line 408 didn't jump to line 411, because the condition on line 408 was never false

409 archive_names = daklib.utils.split_args(Options['Archive']) 

410 query = query.join(Suite.archive).filter(Archive.archive_name.in_(archive_names)) 

411 suites = query.all() 

412 

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

414 

415 def parse_results(message): 

416 # Split out into (code, msg) 

417 code, msg = message 

418 if code == PROC_STATUS_SUCCESS: 418 ↛ 420line 418 didn't jump to line 420, because the condition on line 418 was never false

419 logger.log(msg) 

420 elif code == PROC_STATUS_SIGNALRAISED: 

421 logger.log(['E: Subprocess received signal ', msg]) 

422 else: 

423 logger.log(['E: ', msg]) 

424 

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

426 session.execute("LOCK TABLE src_associations IN SHARE MODE") 

427 session.execute("LOCK TABLE bin_associations IN SHARE MODE") 

428 

429 for s in suites: 

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

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

432 import daklib.utils 

433 daklib.utils.fubar("Refusing to touch %s (untouchable and not forced)" % s.suite_name) 

434 for c in component_ids: 

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

436 if not s.include_long_description: 436 ↛ 438line 436 didn't jump to line 438, because the condition on line 436 was never false

437 pool.apply_async(generate_translations, [s.suite_id, c], callback=parse_results) 

438 for a in s.architectures: 

439 if a == 'source': 

440 continue 

441 pool.apply_async(generate_packages, [s.suite_id, c, a.arch_id, 'deb'], callback=parse_results) 

442 pool.apply_async(generate_packages, [s.suite_id, c, a.arch_id, 'udeb'], callback=parse_results) 

443 

444 pool.close() 

445 pool.join() 

446 

447 # this script doesn't change the database 

448 session.close() 

449 

450 logger.close() 

451 

452 sys.exit(pool.overall_status()) 

453 

454 

455if __name__ == '__main__': 

456 main()