Coverage for rust2rpm/conf.py: 76%

495 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-10-27 15:21 +0100

1import configparser 

2import os 

3import sys 

4import textwrap 

5import tomllib 

6from typing import Any, Optional 

7 

8from cargo2rpm.metadata import FeatureFlags 

9import jsonschema 

10 

11from rust2rpm import log 

12 

13 

14TOML_SCHEMA = { 

15 "type": "object", 

16 "properties": { 

17 # package properties 

18 "package": { 

19 "type": "object", 

20 "properties": { 

21 # override package Summary 

22 "summary": { 

23 "type": "string", 

24 }, 

25 # override package description 

26 "description": { 

27 "type": "string", 

28 }, 

29 # override package project URL 

30 "url": { 

31 "type": "string", 

32 }, 

33 # override package source URL 

34 "source-url": { 

35 "type": "string", 

36 }, 

37 # supported target architectures 

38 "supported-arches": { 

39 "type": "array", 

40 "items": { 

41 "type": "string", 

42 "uniqueItems": True, 

43 }, 

44 }, 

45 # override binary subpackage name 

46 "bin-package-name": { 

47 "type": "string", 

48 }, 

49 # override installation of binary targets 

50 "cargo-install-bin": { 

51 "type": "boolean", 

52 }, 

53 # override installation of library sources 

54 "cargo-install-lib": { 

55 "type": "boolean", 

56 }, 

57 # override default debuginfo level (2) 

58 "debuginfo-level": { 

59 "type": "integer", 

60 "enum": [0, 1, 2], 

61 }, 

62 # comments for manual Cargo.toml patches (rust2rpm -p) 

63 "cargo-toml-patch-comments": { 

64 "type": "array", 

65 "items": { 

66 "type": "string", 

67 }, 

68 }, 

69 # additional source files 

70 "extra-sources": { 

71 "type": "array", 

72 "items": { 

73 "type": "object", 

74 "properties": { 

75 "file": { 

76 "type": "string", 

77 }, 

78 "number": { 

79 "type": "integer", 

80 }, 

81 "comments": { 

82 "type": "array", 

83 "items": { 

84 "type": "string", 

85 }, 

86 }, 

87 }, 

88 "additionalProperties": False, 

89 "required": ["file", "number"], 

90 }, 

91 }, 

92 # additional patch files 

93 "extra-patches": { 

94 "type": "array", 

95 "items": { 

96 "type": "object", 

97 "properties": { 

98 "file": { 

99 "type": "string", 

100 }, 

101 "number": { 

102 "type": "integer", 

103 }, 

104 "comments": { 

105 "type": "array", 

106 "items": { 

107 "type": "string", 

108 }, 

109 }, 

110 }, 

111 "additionalProperties": False, 

112 "required": ["file", "number"], 

113 }, 

114 }, 

115 # additional packaged files 

116 "extra-files": { 

117 "type": "array", 

118 "items": { 

119 "type": "string", 

120 }, 

121 }, 

122 # rename binaries to avoid conflicts 

123 "bin-renames": { 

124 "type": "object", 

125 "patternProperties": { 

126 "^.*$": { 

127 "type": "string", 

128 }, 

129 }, 

130 "additionalProperties": False, 

131 }, 

132 }, 

133 "additionalProperties": False, 

134 }, 

135 # additional commands for scriptlets 

136 "scripts": { 

137 "type": "object", 

138 "properties": { 

139 "prep": { 

140 "type": "object", 

141 "properties": { 

142 "pre": { 

143 "type": "array", 

144 "items": { 

145 "type": "string", 

146 }, 

147 }, 

148 "post": { 

149 "type": "array", 

150 "items": { 

151 "type": "string", 

152 }, 

153 }, 

154 }, 

155 "additionalProperties": False, 

156 }, 

157 "build": { 

158 "type": "object", 

159 "properties": { 

160 "pre": { 

161 "type": "array", 

162 "items": { 

163 "type": "string", 

164 }, 

165 }, 

166 "post": { 

167 "type": "array", 

168 "items": { 

169 "type": "string", 

170 }, 

171 }, 

172 }, 

173 "additionalProperties": False, 

174 }, 

175 "install": { 

176 "type": "object", 

177 "properties": { 

178 "pre": { 

179 "type": "array", 

180 "items": { 

181 "type": "string", 

182 }, 

183 }, 

184 "post": { 

185 "type": "array", 

186 "items": { 

187 "type": "string", 

188 }, 

189 }, 

190 }, 

191 "additionalProperties": False, 

192 }, 

193 "check": { 

194 "type": "object", 

195 "properties": { 

196 "pre": { 

197 "type": "array", 

198 "items": { 

199 "type": "string", 

200 }, 

201 }, 

202 "post": { 

203 "type": "array", 

204 "items": { 

205 "type": "string", 

206 }, 

207 }, 

208 }, 

209 "additionalProperties": False, 

210 }, 

211 }, 

212 "additionalProperties": False, 

213 }, 

214 # configure which tests are run 

215 "tests": { 

216 "type": "object", 

217 "properties": { 

218 "run": { 

219 "type": "array", 

220 "items": { 

221 "type": "string", 

222 "enum": ["none", "all", "lib", "bin", "doc", "bins", "tests"], 

223 "uniqueItems": True, 

224 }, 

225 }, 

226 "skip": { 

227 "type": "array", 

228 "items": { 

229 "type": "string", 

230 "uniqueItems": True, 

231 }, 

232 }, 

233 "skip-exact": { 

234 "type": "boolean", 

235 }, 

236 "comments": { 

237 "type": "array", 

238 "items": { 

239 "type": "string", 

240 }, 

241 }, 

242 }, 

243 "additionalProperties": False, 

244 "required": ["comments"], 

245 }, 

246 # feature flags passed to cargo 

247 "features": { 

248 "type": "object", 

249 "properties": { 

250 # pass "--all-features" 

251 "enable-all": { 

252 "type": "boolean", 

253 }, 

254 # pass "--features foo,bar" 

255 "enable": { 

256 "type": "array", 

257 "items": { 

258 "type": "string", 

259 "uniqueItems": True, 

260 }, 

261 }, 

262 # pass "--no-default-features" 

263 "disable-default": { 

264 "type": "boolean", 

265 }, 

266 # hide feature subpackages 

267 "hide": { 

268 "type": "array", 

269 "items": { 

270 "type": "string", 

271 "uniqueItems": True, 

272 }, 

273 }, 

274 }, 

275 "additionalProperties": False, 

276 }, 

277 # additional RPM Build/Requires 

278 "requires": { 

279 "type": "object", 

280 "properties": { 

281 # additional BuildRequires 

282 "build": { 

283 "type": "array", 

284 "items": { 

285 "type": "string", 

286 "uniqueItems": True, 

287 }, 

288 }, 

289 # additional BuildRequires gated by the "check" bconf 

290 "test": { 

291 "type": "array", 

292 "items": { 

293 "type": "string", 

294 "uniqueItems": True, 

295 }, 

296 }, 

297 # additional Requires for the -devel subpackage 

298 "lib": { 

299 "type": "array", 

300 "items": { 

301 "type": "string", 

302 "uniqueItems": True, 

303 }, 

304 }, 

305 # additional Requires for the binary subpackage 

306 "bin": { 

307 "type": "array", 

308 "items": { 

309 "type": "string", 

310 "uniqueItems": True, 

311 }, 

312 }, 

313 # additional Requires for feature subpackages 

314 "features": { 

315 "type": "object", 

316 "patternProperties": { 

317 "^.*$": { 

318 "type": "array", 

319 "items": { 

320 "type": "string", 

321 "uniqueItems": True, 

322 }, 

323 }, 

324 }, 

325 "additionalProperties": False, 

326 }, 

327 }, 

328 "additionalProperties": False, 

329 }, 

330 }, 

331 "additionalProperties": False, 

332} 

333 

334 

335def to_list(s, sort=True): 

336 return list(sorted(filter(None, (elem.strip() for elem in s.splitlines())))) 

337 

338 

339class ConfError(ValueError): 

340 pass 

341 

342 

343class IniConf: 

344 def __init__( 

345 self, 

346 *, 

347 summary: Optional[str] = None, 

348 supported_arches: Optional[list[str]] = None, 

349 all_features: Optional[bool] = None, 

350 unwanted_features: Optional[list[str]] = None, 

351 enabled_features: Optional[list[str]] = None, 

352 buildrequires: Optional[list[str]] = None, 

353 testrequires: Optional[list[str]] = None, 

354 bin_requires: Optional[list[str]] = None, 

355 lib_requires: Optional[dict[Optional[str], list[str]]] = None, 

356 conf_file: bool = False, 

357 ): 

358 self.summary: Optional[str] = summary 

359 self.supported_arches: list[str] = supported_arches or list() 

360 self.all_features: Optional[bool] = all_features 

361 self.unwanted_features: list[str] = unwanted_features or list() 

362 self.enabled_features: list[str] = enabled_features or list() 

363 self.buildrequires: list[str] = buildrequires or list() 

364 self.testrequires: list[str] = testrequires or list() 

365 self.bin_requires: list[str] = bin_requires or list() 

366 self.lib_requires: dict[Optional[str], list[str]] = lib_requires or dict() 

367 self.conf_file: bool = conf_file 

368 

369 def __eq__(self, other): 

370 if not isinstance(other, IniConf): 

371 return False # pragma nocover 

372 

373 # order of items in the list is consistent: 

374 # lists are sorted when parsing rust2rpm.conf files 

375 return ( 

376 self.summary == other.summary 

377 and self.supported_arches == other.supported_arches 

378 and self.all_features == other.all_features 

379 and self.unwanted_features == other.unwanted_features 

380 and self.enabled_features == other.enabled_features 

381 and self.buildrequires == other.buildrequires 

382 and self.testrequires == other.testrequires 

383 and self.bin_requires == other.bin_requires 

384 and self.lib_requires == other.lib_requires 

385 ) 

386 

387 @staticmethod 

388 def load(filenames: list[str], target: str, features: Optional[set[str]]) -> "IniConf": 

389 conf = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation()) 

390 confs = conf.read(filenames) 

391 

392 if len(confs) > 1: # pragma nocover 

393 raise FileExistsError 

394 

395 # clean up configuration files with deprecated names 

396 if ".rust2rpm.conf" in confs: # pragma nocover 

397 os.rename(".rust2rpm.conf", "rust2rpm.conf") 

398 log.info("Renamed deprecated, hidden .rust2rpm.conf file to rust2rpm.conf.") 

399 

400 if "_rust2rpm.conf" in confs: # pragma nocover 

401 os.rename("_rust2rpm.conf", "rust2rpm.conf") 

402 log.info("Renamed deprecated _rust2rpm.conf file to rust2rpm.conf.") 

403 

404 # merge target-specific configuration with default configuration 

405 if target not in conf: 

406 conf.add_section(target) 

407 merged = conf[target] 

408 

409 # validate configuration file 

410 valid_targets = ["fedora", "epel8", "mageia", "opensuse", "plain"] 

411 valid_keys = [ 

412 "summary", 

413 "supported-arches", 

414 "all-features", 

415 "unwanted-features", 

416 "enabled-features", 

417 "buildrequires", 

418 "testrequires", 

419 "lib.requires", 

420 "bin.requires", 

421 ] 

422 

423 if features is not None: 423 ↛ 432line 423 didn't jump to line 432 because the condition on line 423 was always true

424 # the "default" feature is always implicitly defined 

425 if "default" not in features: 425 ↛ 428line 425 didn't jump to line 428 because the condition on line 425 was always true

426 features.add("default") 

427 

428 for feature in features: 

429 valid_keys.append(f"lib+{feature}.requires") 

430 

431 # check section names 

432 for section in conf.keys(): 

433 if section != "DEFAULT" and section not in valid_targets: 433 ↛ 434line 433 didn't jump to line 434 because the condition on line 433 was never true

434 raise ConfError(f"Invalid section: {section!r}") 

435 

436 # check setting keys 

437 for key in merged.keys(): 

438 if key not in valid_keys: 438 ↛ 439line 438 didn't jump to line 439 because the condition on line 438 was never true

439 raise ConfError(f"Invalid key: {key!r}") 

440 

441 # parse configuration and validate settings 

442 settings = dict() 

443 

444 if summary := merged.get("summary"): 444 ↛ 445line 444 didn't jump to line 445 because the condition on line 444 was never true

445 settings["summary"] = summary 

446 

447 if supported_arches := merged.get("supported-arches"): 447 ↛ 448line 447 didn't jump to line 448 because the condition on line 447 was never true

448 settings["supported_arches"] = to_list(supported_arches, False) 

449 

450 if merged.get("all-features"): 

451 all_features = merged.getboolean("all-features") 

452 settings["all_features"] = all_features 

453 

454 if unwanted_features := merged.get("unwanted-features"): 

455 settings["unwanted_features"] = to_list(unwanted_features) 

456 

457 if features is not None: 457 ↛ 462line 457 didn't jump to line 462 because the condition on line 457 was always true

458 for unwanted_feature in settings["unwanted_features"]: 

459 if unwanted_feature not in features: 459 ↛ 460line 459 didn't jump to line 460 because the condition on line 459 was never true

460 raise ConfError(f'Unrecognized "unwanted" feature: {unwanted_feature!r}') 

461 

462 if enabled_features := merged.get("enabled-features"): 

463 settings["enabled_features"] = to_list(enabled_features) 

464 

465 if features is not None: 465 ↛ 470line 465 didn't jump to line 470 because the condition on line 465 was always true

466 for enabled_feature in settings["enabled_features"]: 

467 if enabled_feature not in features: 467 ↛ 468line 467 didn't jump to line 468 because the condition on line 467 was never true

468 raise ConfError(f'Unrecognized "enabled" feature: {enabled_feature!r}') 

469 

470 if buildrequires := merged.get("buildrequires"): 

471 settings["buildrequires"] = to_list(buildrequires) 

472 if testrequires := merged.get("testrequires"): 472 ↛ 473line 472 didn't jump to line 473 because the condition on line 472 was never true

473 settings["testrequires"] = to_list(testrequires) 

474 if bin_requires := merged.get("bin.requires"): 474 ↛ 475line 474 didn't jump to line 475 because the condition on line 474 was never true

475 settings["bin_requires"] = to_list(bin_requires) 

476 

477 if lib_requires := merged.get("lib.requires"): 

478 if "lib_requires" not in settings.keys(): 478 ↛ 480line 478 didn't jump to line 480 because the condition on line 478 was always true

479 settings["lib_requires"] = dict() 

480 settings["lib_requires"][None] = to_list(lib_requires) # type: ignore 

481 

482 if features is not None: 482 ↛ 489line 482 didn't jump to line 489 because the condition on line 482 was always true

483 for feature in features: 

484 if lib_feature_requires := merged.get(f"lib+{feature}.requires"): 

485 if "lib_requires" not in settings.keys(): 485 ↛ 486line 485 didn't jump to line 486 because the condition on line 485 was never true

486 settings["lib_requires"] = dict() 

487 settings["lib_requires"][feature] = to_list(lib_feature_requires) # type: ignore 

488 

489 settings["conf_file"] = len(confs) == 1 

490 

491 return IniConf(**settings) # type: ignore 

492 

493 

494def conf_comments_to_spec_comments(comments: Optional[list[str]]) -> list[str]: 

495 if not comments: 

496 return [] 

497 

498 lines = [] 

499 for comment in comments: 

500 lines.extend( 

501 textwrap.wrap( 

502 comment, 

503 width=80, 

504 initial_indent="# * ", 

505 subsequent_indent="# ", 

506 break_long_words=False, 

507 break_on_hyphens=False, 

508 ) 

509 ) 

510 

511 return lines 

512 

513 

514class Source: 

515 def __init__(self, data: dict): 

516 self._data = data 

517 

518 @property 

519 def file(self) -> str: 

520 return self._data["file"] 

521 

522 @property 

523 def number(self) -> int: 

524 return self._data["number"] 

525 

526 @property 

527 def comments(self) -> list[str]: 

528 return self._data.get("comments") or list() 

529 

530 @property 

531 def comment_lines(self) -> list[str]: 

532 return conf_comments_to_spec_comments(self.comments) 

533 

534 @property 

535 def whitespace(self) -> str: 

536 return " " * (16 - (len("Source") + len(str(self.number)) + 1)) 

537 

538 

539class Patch: 

540 def __init__(self, data: dict): 

541 self._data = data 

542 

543 @property 

544 def file(self) -> str: 

545 return self._data["file"] 

546 

547 @property 

548 def number(self) -> int: 

549 return self._data["number"] 

550 

551 @property 

552 def comments(self) -> list[str]: 

553 return self._data.get("comments") or list() 

554 

555 @property 

556 def comment_lines(self) -> list[str]: 

557 return conf_comments_to_spec_comments(self.comments) 

558 

559 @property 

560 def whitespace(self) -> str: 

561 return " " * (16 - (len("Patch") + len(str(self.number)) + 1)) 

562 

563 

564class TomlConf: 

565 def __init__(self, data: Optional[dict] = None): 

566 self._data = data or dict() 

567 

568 def __repr__(self) -> str: 

569 return repr(self._data) 

570 

571 def __eq__(self, other) -> bool: 

572 if not isinstance(other, TomlConf): 

573 return False # pragma nocover 

574 

575 return self._data == other._data 

576 

577 @property 

578 def _package(self) -> Optional[dict]: 

579 return self._data.get("package") 

580 

581 @property 

582 def package_summary(self) -> Optional[str]: 

583 if package := self._package: 583 ↛ 584line 583 didn't jump to line 584 because the condition on line 583 was never true

584 return package.get("summary") 

585 else: 

586 return None 

587 

588 @property 

589 def package_description(self) -> Optional[str]: 

590 if package := self._package: 590 ↛ 591line 590 didn't jump to line 591 because the condition on line 590 was never true

591 return package.get("description") 

592 else: 

593 return None 

594 

595 @property 

596 def package_url(self) -> Optional[str]: 

597 if package := self._package: 597 ↛ 598line 597 didn't jump to line 598 because the condition on line 597 was never true

598 return package.get("url") 

599 else: 

600 return None 

601 

602 @property 

603 def package_source_url(self) -> Optional[str]: 

604 if package := self._package: 604 ↛ 605line 604 didn't jump to line 605 because the condition on line 604 was never true

605 return package.get("source-url") 

606 else: 

607 return None 

608 

609 @property 

610 def package_supported_arches(self) -> Optional[list[str]]: 

611 if package := self._package: 611 ↛ 612line 611 didn't jump to line 612 because the condition on line 611 was never true

612 return package.get("supported-arches") 

613 else: 

614 return None 

615 

616 @property 

617 def package_bin_package_name(self) -> Optional[str]: 

618 if package := self._package: 618 ↛ 619line 618 didn't jump to line 619 because the condition on line 618 was never true

619 return package.get("bin-package-name") 

620 else: 

621 return None 

622 

623 @property 

624 def package_cargo_install_bin(self) -> Optional[bool]: 

625 if package := self._package: 625 ↛ 626line 625 didn't jump to line 626 because the condition on line 625 was never true

626 return package.get("cargo-install-bin") 

627 else: 

628 return None 

629 

630 @property 

631 def package_cargo_install_lib(self) -> Optional[bool]: 

632 if package := self._package: 632 ↛ 633line 632 didn't jump to line 633 because the condition on line 632 was never true

633 return package.get("cargo-install-lib") 

634 else: 

635 return None 

636 

637 @property 

638 def package_debuginfo_level(self) -> Optional[int]: 

639 if package := self._package: 639 ↛ 640line 639 didn't jump to line 640 because the condition on line 639 was never true

640 return package.get("debuginfo-level") 

641 else: 

642 return None 

643 

644 @property 

645 def package_cargo_toml_patch_comments(self) -> Optional[list[str]]: 

646 if package := self._package: 646 ↛ 647line 646 didn't jump to line 647 because the condition on line 646 was never true

647 return package.get("cargo-toml-patch-comments") 

648 else: 

649 return None 

650 

651 @property 

652 def package_cargo_toml_patch_comment_lines(self) -> Optional[list[str]]: 

653 return conf_comments_to_spec_comments(self.package_cargo_toml_patch_comments) 

654 

655 @property 

656 def package_extra_sources(self) -> Optional[list[Source]]: 

657 if package := self._package: 657 ↛ 658line 657 didn't jump to line 658 because the condition on line 657 was never true

658 if sources := package.get("extra-sources"): 

659 return [Source(source) for source in sources] 

660 else: 

661 return None 

662 else: 

663 return None 

664 

665 @property 

666 def package_extra_patches(self) -> Optional[list[Patch]]: 

667 if package := self._package: 667 ↛ 668line 667 didn't jump to line 668 because the condition on line 667 was never true

668 if patches := package.get("extra-patches"): 

669 return [Patch(patch) for patch in patches] 

670 else: 

671 return None 

672 else: 

673 return None 

674 

675 @property 

676 def package_extra_files(self) -> Optional[list[str]]: 

677 if package := self._package: 677 ↛ 678line 677 didn't jump to line 678 because the condition on line 677 was never true

678 return package.get("extra-files") 

679 else: 

680 return None 

681 

682 @property 

683 def package_bin_renames(self) -> Optional[dict[str, str]]: 

684 if package := self._package: 684 ↛ 685line 684 didn't jump to line 685 because the condition on line 684 was never true

685 return package.get("bin-renames") 

686 else: 

687 return None 

688 

689 @property 

690 def _scripts(self) -> Optional[dict[str, dict[str, list[str]]]]: 

691 return self._data.get("scripts") 

692 

693 @property 

694 def _scripts_prep(self) -> Optional[dict[str, list[str]]]: 

695 if scripts := self._scripts: 695 ↛ 696line 695 didn't jump to line 696 because the condition on line 695 was never true

696 return scripts.get("prep") 

697 else: 

698 return None 

699 

700 @property 

701 def scripts_prep_pre(self) -> Optional[list[str]]: 

702 if scripts_prep := self._scripts_prep: 702 ↛ 703line 702 didn't jump to line 703 because the condition on line 702 was never true

703 return scripts_prep.get("pre") 

704 else: 

705 return None 

706 

707 @property 

708 def scripts_prep_post(self) -> Optional[list[str]]: 

709 if scripts_prep := self._scripts_prep: 709 ↛ 710line 709 didn't jump to line 710 because the condition on line 709 was never true

710 return scripts_prep.get("post") 

711 else: 

712 return None 

713 

714 @property 

715 def _scripts_build(self) -> Optional[dict[str, list[str]]]: 

716 if scripts := self._scripts: 716 ↛ 717line 716 didn't jump to line 717 because the condition on line 716 was never true

717 return scripts.get("build") 

718 else: 

719 return None 

720 

721 @property 

722 def scripts_build_pre(self) -> Optional[list[str]]: 

723 if scripts_build := self._scripts_build: 723 ↛ 724line 723 didn't jump to line 724 because the condition on line 723 was never true

724 return scripts_build.get("pre") 

725 else: 

726 return None 

727 

728 @property 

729 def scripts_build_post(self) -> Optional[list[str]]: 

730 if scripts_build := self._scripts_build: 730 ↛ 731line 730 didn't jump to line 731 because the condition on line 730 was never true

731 return scripts_build.get("post") 

732 else: 

733 return None 

734 

735 @property 

736 def _scripts_install(self) -> Optional[dict[str, list[str]]]: 

737 if scripts := self._scripts: 737 ↛ 738line 737 didn't jump to line 738 because the condition on line 737 was never true

738 return scripts.get("install") 

739 else: 

740 return None 

741 

742 @property 

743 def scripts_install_pre(self) -> Optional[list[str]]: 

744 if scripts_install := self._scripts_install: 744 ↛ 745line 744 didn't jump to line 745 because the condition on line 744 was never true

745 return scripts_install.get("pre") 

746 else: 

747 return None 

748 

749 @property 

750 def scripts_install_post(self) -> Optional[list[str]]: 

751 if scripts_install := self._scripts_install: 751 ↛ 752line 751 didn't jump to line 752 because the condition on line 751 was never true

752 return scripts_install.get("post") 

753 else: 

754 return None 

755 

756 @property 

757 def _scripts_check(self) -> Optional[dict[str, list[str]]]: 

758 if scripts := self._scripts: 758 ↛ 759line 758 didn't jump to line 759 because the condition on line 758 was never true

759 return scripts.get("check") 

760 else: 

761 return None 

762 

763 @property 

764 def scripts_check_pre(self) -> Optional[list[str]]: 

765 if scripts_check := self._scripts_check: 765 ↛ 766line 765 didn't jump to line 766 because the condition on line 765 was never true

766 return scripts_check.get("pre") 

767 else: 

768 return None 

769 

770 @property 

771 def scripts_check_post(self) -> Optional[list[str]]: 

772 if scripts_check := self._scripts_check: 772 ↛ 773line 772 didn't jump to line 773 because the condition on line 772 was never true

773 return scripts_check.get("post") 

774 else: 

775 return None 

776 

777 @property 

778 def _tests(self) -> Optional[dict[str, Any]]: 

779 return self._data.get("tests") 

780 

781 @property 

782 def tests_run(self) -> Optional[list[str]]: 

783 if tests := self._tests: 

784 return tests.get("run") 

785 return None 

786 

787 @property 

788 def tests_skip(self) -> Optional[list[str]]: 

789 if tests := self._tests: 

790 return tests.get("skip") 

791 return None 

792 

793 @property 

794 def tests_skip_exact(self) -> Optional[bool]: 

795 if tests := self._tests: 

796 return tests.get("skip-exact") 

797 return None 

798 

799 @property 

800 def tests_comments(self) -> Optional[list[str]]: 

801 if tests := self._tests: 

802 return tests.get("comments") 

803 return None 

804 

805 @property 

806 def tests_comment_lines(self) -> Optional[list[str]]: 

807 return conf_comments_to_spec_comments(self.tests_comments) 

808 

809 @property 

810 def _features(self) -> Optional[dict]: 

811 return self._data.get("features") 

812 

813 @property 

814 def features_enable_all(self) -> Optional[bool]: 

815 if features := self._features: 

816 return features.get("enable-all") 

817 else: 

818 return None 

819 

820 @property 

821 def features_enable(self) -> Optional[list[str]]: 

822 if features := self._features: 

823 return features.get("enable") 

824 else: 

825 return None 

826 

827 @property 

828 def features_disable_default(self) -> Optional[bool]: 

829 if features := self._features: 

830 return features.get("disable-default") 

831 else: 

832 return None 

833 

834 @property 

835 def features_hide(self) -> Optional[list[str]]: 

836 if features := self._features: 

837 return features.get("hide") 

838 else: 

839 return None 

840 

841 @property 

842 def _requires(self) -> Optional[dict]: 

843 return self._data.get("requires") 

844 

845 @property 

846 def requires_build(self) -> Optional[list[str]]: 

847 if requires := self._requires: 

848 return requires.get("build") 

849 else: 

850 return None 

851 

852 @property 

853 def requires_test(self) -> Optional[list[str]]: 

854 if requires := self._requires: 

855 return requires.get("test") 

856 else: 

857 return None 

858 

859 @property 

860 def requires_lib(self) -> Optional[list[str]]: 

861 if requires := self._requires: 

862 return requires.get("lib") 

863 else: 

864 return None 

865 

866 @property 

867 def requires_bin(self) -> Optional[list[str]]: 

868 if requires := self._requires: 

869 return requires.get("bin") 

870 else: 

871 return None 

872 

873 @property 

874 def requires_features(self) -> Optional[dict[str, list[str]]]: 

875 if requires := self._requires: 

876 return requires.get("features") 

877 else: 

878 return None 

879 

880 @staticmethod 

881 def load(path: str, features: Optional[set[str]]) -> "TomlConf": 

882 with open(path, "rb") as file: 

883 toml = tomllib.load(file) 

884 

885 jsonschema.validate(toml, TOML_SCHEMA) 

886 

887 conf = TomlConf(toml) 

888 

889 # the "default" feature is always implicitly defined 

890 if features and "default" not in features: 

891 features.add("default") 

892 

893 # validate the "tests.run" setting: 

894 # both "all" and "none" cannot be combimed with other values 

895 if tests_run := conf.tests_run: 

896 if "all" in tests_run: 896 ↛ 897line 896 didn't jump to line 897 because the condition on line 896 was never true

897 if len(tests_run) != 1: 

898 raise ConfError(f"Invalid set of tests to run: {tests_run!r}") 

899 if "none" in tests_run: 

900 if len(tests_run) != 1: 900 ↛ 901line 900 didn't jump to line 901 because the condition on line 900 was never true

901 raise ConfError(f"Invalid set of tests to run: {tests_run!r}") 

902 

903 # validate the "features.enable-all" and "features.disable-default" settings: 

904 # both cannot be enabled at the same time 

905 if conf.features_enable_all is True and conf.features_disable_default is True: 905 ↛ 906line 905 didn't jump to line 906 because the condition on line 905 was never true

906 raise ConfError("Conflicting settings for features: 'enable-all' and 'disable-default'") 

907 

908 if features is not None: 908 ↛ 931line 908 didn't jump to line 931 because the condition on line 908 was always true

909 # validate the "features.enable" setting: 

910 # list elements must be valid feature names 

911 if enable_list := conf.features_enable: 

912 for enabled in enable_list: 

913 if enabled not in features: 913 ↛ 914line 913 didn't jump to line 914 because the condition on line 913 was never true

914 raise ConfError(f'Unrecognized "enabled" feature: {enabled!r}') 

915 

916 # validate the "features.hide" setting: 

917 # list elements must be valid feature names 

918 if hide_list := conf.features_hide: 

919 for hidden in hide_list: 

920 if hidden not in features: 920 ↛ 921line 920 didn't jump to line 921 because the condition on line 920 was never true

921 raise ConfError(f'Unrecognized "hidden" feature: {hidden!r}') 

922 

923 # validate the "requires.features" setting 

924 # dictionary keys must be valid feature names 

925 if feature_requires := conf.requires_features: 

926 for feature in feature_requires.keys(): 

927 if feature not in features: 927 ↛ 928line 927 didn't jump to line 928 because the condition on line 927 was never true

928 raise ConfError(f'Unrecognized "Requires" for feature: {feature!r}') 

929 

930 # warn when conflicting settings are used 

931 if (conf.features_enable_all is True) and (conf.features_hide is not None and len(conf.features_hide) > 0): 

932 log.warn( 

933 "Conflicting settings for features: " 

934 "All features are enabled for the build but some feature subpackages are hidden. " 

935 "This is likely an error." 

936 ) 

937 

938 return conf 

939 

940 @staticmethod 

941 def from_conf(conf: IniConf) -> "TomlConf": 

942 settings: dict[str, Any] = dict() 

943 

944 package: dict[str, Any] = dict() 

945 if conf.summary: 945 ↛ 946line 945 didn't jump to line 946 because the condition on line 945 was never true

946 package["summary"] = conf.summary 

947 if conf.supported_arches: 947 ↛ 948line 947 didn't jump to line 948 because the condition on line 947 was never true

948 package["supported-arches"] = conf.supported_arches 

949 if package: 949 ↛ 950line 949 didn't jump to line 950 because the condition on line 949 was never true

950 settings["package"] = package 

951 

952 features: dict[str, Any] = dict() 

953 if conf.all_features is not None: 

954 features["enable-all"] = conf.all_features 

955 if conf.enabled_features: 

956 features["enable"] = conf.enabled_features 

957 if conf.unwanted_features: 

958 features["hide"] = conf.unwanted_features 

959 if features: 

960 settings["features"] = features 

961 

962 requires: dict[str, Any] = dict() 

963 if conf.buildrequires: 

964 requires["build"] = conf.buildrequires 

965 if conf.testrequires: 965 ↛ 966line 965 didn't jump to line 966 because the condition on line 965 was never true

966 requires["test"] = conf.testrequires 

967 if conf.bin_requires: 967 ↛ 968line 967 didn't jump to line 968 because the condition on line 967 was never true

968 requires["bin"] = conf.bin_requires 

969 if conf.lib_requires: 

970 for key, value in conf.lib_requires.items(): 

971 if key is None: 

972 requires["lib"] = value 

973 else: 

974 if "features" not in requires.keys(): 

975 requires["features"] = dict() 

976 requires["features"][key] = value 

977 if requires: 

978 settings["requires"] = requires 

979 

980 jsonschema.validate(settings, TOML_SCHEMA) 

981 

982 return TomlConf(settings) 

983 

984 def to_bcond_check(self) -> bool: 

985 if tests_run := self.tests_run: 

986 return "none" not in tests_run 

987 else: 

988 return True 

989 

990 def to_feature_flags(self) -> FeatureFlags: 

991 return FeatureFlags( 

992 all_features=self.features_enable_all is True, 

993 no_default_features=self.features_disable_default is True, 

994 features=list(sorted(set(self.features_enable) if self.features_enable else set())), 

995 ) 

996 

997 def to_cargo_test_args(self) -> list[str]: 

998 if skip := self.tests_skip: 

999 skip_flags = [] 

1000 for name in skip: 

1001 skip_flags.extend(["--skip", name]) 

1002 else: 

1003 skip_flags = [] 

1004 

1005 if self.tests_skip_exact is True: 

1006 skip_exact_flag = ["--exact"] 

1007 else: 

1008 skip_exact_flag = [] 

1009 

1010 if run := self.tests_run: 

1011 if not ("all" in run or "none" in run): 

1012 multi = [] 

1013 for kind in run: 

1014 if skip_flags: 1014 ↛ 1015line 1014 didn't jump to line 1015 because the condition on line 1014 was never true

1015 multi.append(" " + " ".join(["--", f"--{kind}", "--"] + skip_exact_flag + skip_flags)) 

1016 else: 

1017 multi.append(" " + " ".join(["--", f"--{kind}"])) 

1018 return multi 

1019 

1020 if skip_flags: 

1021 return [" " + " ".join(["--", "--"] + skip_exact_flag + skip_flags)] 

1022 else: 

1023 return [] 

1024 

1025 

1026def load_config(features: Optional[set[str]], target: str) -> TomlConf: 

1027 # attempt to load rust2rpm.toml 

1028 try: 

1029 tomlconf = TomlConf.load("rust2rpm.toml", features) 

1030 

1031 except FileNotFoundError as _exc: 

1032 tomlconf = None 

1033 

1034 except tomllib.TOMLDecodeError as exc: 

1035 log.error("Cannot read rust2rpm.toml file (TOML syntax error):") 

1036 log.error(str(exc)) 

1037 sys.exit(1) 

1038 

1039 except jsonschema.ValidationError as exc: 

1040 if not exc.path: 

1041 log.error("Invalid rust2rpm.toml file (unknown setting or table):") 

1042 log.error(exc.message) 

1043 else: 

1044 err_path = "" 

1045 for elem in exc.path: 

1046 if isinstance(elem, int): 

1047 err_path += f"[{elem}]" 

1048 else: 

1049 err_path += f"/{elem}" 

1050 log.error(f"Invalid rust2rpm.toml file (invalid setting at {err_path!r}):") 

1051 log.error(exc.message) 

1052 sys.exit(1) 

1053 

1054 except ConfError as exc: 

1055 log.error("Invalid rust2rpm.toml file:") 

1056 log.error(str(exc)) 

1057 sys.exit(1) 

1058 

1059 if tomlconf: 

1060 return tomlconf 

1061 

1062 # fall back to rust2rpm.conf and translate 

1063 try: 

1064 # known file names (only in the current working directory) 

1065 filenames = [".rust2rpm.conf", "_rust2rpm.conf", "rust2rpm.conf"] 

1066 distconf = IniConf.load(filenames, target, features) 

1067 

1068 except FileExistsError: 

1069 log.error( 

1070 "More than one *rust2rpm.conf file is present in this directory. " 

1071 + "Ensure that there is only one, and that it has the correct contents." 

1072 ) 

1073 sys.exit(1) 

1074 

1075 except ConfError as exc: 

1076 log.error("Invalid rust2rpm configuration file:") 

1077 log.error(str(exc)) 

1078 sys.exit(1) 

1079 

1080 if distconf.conf_file: 

1081 log.warn( 

1082 "The rust2rpm.conf file format is deprecated and support for it will " 

1083 + "be removed in a future release of rust2rpm. Please migrate settings " 

1084 + "to the new TOML based format (rust2rpm.toml)." 

1085 ) 

1086 

1087 tomlconf = TomlConf.from_conf(distconf) 

1088 return tomlconf