|
7 | 7 | import shutil
|
8 | 8 | import json
|
9 | 9 | import pathlib
|
| 10 | +import importlib |
| 11 | +import subprocess |
10 | 12 |
|
11 | 13 | import click
|
12 | 14 | from spin import util
|
|
23 | 25 | )
|
24 | 26 |
|
25 | 27 |
|
| 28 | +def _get_numpy_tools(filename): |
| 29 | + filepath = pathlib.Path('tools', filename) |
| 30 | + spec = importlib.util.spec_from_file_location(filename.stem, filepath) |
| 31 | + module = importlib.util.module_from_spec(spec) |
| 32 | + spec.loader.exec_module(module) |
| 33 | + return module |
| 34 | + |
| 35 | + |
| 36 | +@click.command() |
| 37 | +@click.option( |
| 38 | + "-t", "--token", |
| 39 | + help="GitHub access token", |
| 40 | + required=True |
| 41 | +) |
| 42 | +@click.option( |
| 43 | + "--revision-range", |
| 44 | + help="<revision>..<revision>", |
| 45 | + required=True |
| 46 | +) |
| 47 | +@click.pass_context |
| 48 | +def changelog(ctx, token, revision_range): |
| 49 | + """👩 Get change log for provided revision range |
| 50 | +
|
| 51 | + \b |
| 52 | + Example: |
| 53 | +
|
| 54 | + \b |
| 55 | + $ spin authors -t $GH_TOKEN --revision-range v1.25.0..v1.26.0 |
| 56 | + """ |
| 57 | + try: |
| 58 | + from github.GithubException import GithubException |
| 59 | + from git.exc import GitError |
| 60 | + changelog = _get_numpy_tools(pathlib.Path('changelog.py')) |
| 61 | + except ModuleNotFoundError as e: |
| 62 | + raise click.ClickException( |
| 63 | + f"{e.msg}. Install the missing packages to use this command." |
| 64 | + ) |
| 65 | + click.secho( |
| 66 | + f"Generating change log for range {revision_range}", |
| 67 | + bold=True, fg="bright_green", |
| 68 | + ) |
| 69 | + try: |
| 70 | + changelog.main(token, revision_range) |
| 71 | + except GithubException as e: |
| 72 | + raise click.ClickException( |
| 73 | + f"GithubException raised with status: {e.status} " |
| 74 | + f"and message: {e.data['message']}" |
| 75 | + ) |
| 76 | + except GitError as e: |
| 77 | + raise click.ClickException( |
| 78 | + f"Git error in command `{' '.join(e.command)}` " |
| 79 | + f"with error message: {e.stderr}" |
| 80 | + ) |
| 81 | + |
| 82 | + |
26 | 83 | @click.command()
|
27 | 84 | @click.option(
|
28 | 85 | "-j", "--jobs",
|
@@ -263,6 +320,47 @@ def _run_asv(cmd):
|
263 | 320 |
|
264 | 321 | util.run(cmd, cwd='benchmarks', env=env)
|
265 | 322 |
|
| 323 | +@click.command() |
| 324 | +@click.option( |
| 325 | + "-b", "--branch", |
| 326 | + metavar='branch', |
| 327 | + default="main", |
| 328 | +) |
| 329 | +@click.option( |
| 330 | + '--uncommitted', |
| 331 | + is_flag=True, |
| 332 | + default=False, |
| 333 | + required=False, |
| 334 | +) |
| 335 | +@click.pass_context |
| 336 | +def lint(ctx, branch, uncommitted): |
| 337 | + """🔦 Run lint checks on diffs. |
| 338 | + Provide target branch name or `uncommitted` to check changes before committing: |
| 339 | +
|
| 340 | + \b |
| 341 | + Examples: |
| 342 | +
|
| 343 | + \b |
| 344 | + For lint checks of your development brach with `main` or a custom branch: |
| 345 | +
|
| 346 | + \b |
| 347 | + $ spin lint # defaults to main |
| 348 | + $ spin lint --branch custom_branch |
| 349 | +
|
| 350 | + \b |
| 351 | + To check just the uncommitted changes before committing |
| 352 | +
|
| 353 | + \b |
| 354 | + $ spin lint --uncommitted |
| 355 | + """ |
| 356 | + try: |
| 357 | + linter = _get_numpy_tools(pathlib.Path('linter.py')) |
| 358 | + except ModuleNotFoundError as e: |
| 359 | + raise click.ClickException( |
| 360 | + f"{e.msg}. Install using linter_requirements.txt" |
| 361 | + ) |
| 362 | + |
| 363 | + linter.DiffLinter(branch).run_lint(uncommitted) |
266 | 364 |
|
267 | 365 | @click.command()
|
268 | 366 | @click.option(
|
@@ -470,3 +568,78 @@ def _config_openblas(blas_variant):
|
470 | 568 | os.makedirs(openblas_dir, exist_ok=True)
|
471 | 569 | with open(pkg_config_fname, "wt", encoding="utf8") as fid:
|
472 | 570 | fid.write(openblas.get_pkg_config().replace("\\", "/"))
|
| 571 | + |
| 572 | + |
| 573 | +@click.command() |
| 574 | +@click.option( |
| 575 | + "-v", "--version-override", |
| 576 | + help="NumPy version of release", |
| 577 | + required=False |
| 578 | +) |
| 579 | +@click.pass_context |
| 580 | +def notes(ctx, version_override): |
| 581 | + """🎉 Generate release notes and validate |
| 582 | +
|
| 583 | + \b |
| 584 | + Example: |
| 585 | +
|
| 586 | + \b |
| 587 | + $ spin notes --version-override 2.0 |
| 588 | +
|
| 589 | + \b |
| 590 | + To automatically pick the version |
| 591 | +
|
| 592 | + \b |
| 593 | + $ spin notes |
| 594 | + """ |
| 595 | + project_config = util.get_config() |
| 596 | + version = version_override or project_config['project.version'] |
| 597 | + |
| 598 | + click.secho( |
| 599 | + f"Generating release notes for NumPy {version}", |
| 600 | + bold=True, fg="bright_green", |
| 601 | + ) |
| 602 | + |
| 603 | + # Check if `towncrier` is installed |
| 604 | + if not shutil.which("towncrier"): |
| 605 | + raise click.ClickException( |
| 606 | + f"please install `towncrier` to use this command" |
| 607 | + ) |
| 608 | + |
| 609 | + click.secho( |
| 610 | + f"Reading upcoming changes from {project_config['tool.towncrier.directory']}", |
| 611 | + bold=True, fg="bright_yellow" |
| 612 | + ) |
| 613 | + # towncrier build --version 2.1 --yes |
| 614 | + cmd = ["towncrier", "build", "--version", version, "--yes"] |
| 615 | + try: |
| 616 | + p = util.run( |
| 617 | + cmd=cmd, |
| 618 | + sys_exit=False, |
| 619 | + output=True, |
| 620 | + encoding="utf-8" |
| 621 | + ) |
| 622 | + except subprocess.SubprocessError as e: |
| 623 | + raise click.ClickException( |
| 624 | + f"`towncrier` failed returned {e.returncode} with error `{e.stderr}`" |
| 625 | + ) |
| 626 | + |
| 627 | + output_path = project_config['tool.towncrier.filename'].format(version=version) |
| 628 | + click.secho( |
| 629 | + f"Release notes successfully written to {output_path}", |
| 630 | + bold=True, fg="bright_yellow" |
| 631 | + ) |
| 632 | + |
| 633 | + click.secho( |
| 634 | + "Verifying consumption of all news fragments", |
| 635 | + bold=True, fg="bright_green", |
| 636 | + ) |
| 637 | + |
| 638 | + try: |
| 639 | + test_notes = _get_numpy_tools(pathlib.Path('ci', 'test_all_newsfragments_used.py')) |
| 640 | + except ModuleNotFoundError as e: |
| 641 | + raise click.ClickException( |
| 642 | + f"{e.msg}. Install the missing packages to use this command." |
| 643 | + ) |
| 644 | + |
| 645 | + test_notes.main() |
0 commit comments