From e12ce2d64716a044beeb628157b1a17ccba8f87c Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 4 Feb 2021 14:27:54 -0500 Subject: [PATCH] feat: print new files after building (#570) --- cibuildwheel/__main__.py | 18 ++++++++-------- cibuildwheel/util.py | 24 +++++++++++++++++++++- unit_test/main_tests/conftest.py | 10 +++++++++ unit_test/test_wheel_print.py | 35 ++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 unit_test/test_wheel_print.py diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index bebaf1eb..2fbec758 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -12,6 +12,7 @@ from packaging.specifiers import SpecifierSet import cibuildwheel import cibuildwheel.linux import cibuildwheel.macos +import cibuildwheel.util import cibuildwheel.windows from cibuildwheel.architecture import Architecture, allowed_architectures_check from cibuildwheel.environment import EnvironmentParseError, parse_environment @@ -281,14 +282,15 @@ def main() -> None: if not output_dir.exists(): output_dir.mkdir(parents=True) - if platform == 'linux': - cibuildwheel.linux.build(build_options) - elif platform == 'windows': - cibuildwheel.windows.build(build_options) - elif platform == 'macos': - cibuildwheel.macos.build(build_options) - else: - assert_never(platform) + with cibuildwheel.util.print_new_wheels("{n} wheels produced in {m:.0f} minutes:", output_dir): + if platform == 'linux': + cibuildwheel.linux.build(build_options) + elif platform == 'windows': + cibuildwheel.windows.build(build_options) + elif platform == 'macos': + cibuildwheel.macos.build(build_options) + else: + assert_never(platform) def detect_obsolete_options() -> None: diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index 761a964b..de7eb445 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -1,14 +1,16 @@ +import contextlib import fnmatch import itertools import os import re import ssl import textwrap +import time import urllib.request from enum import Enum from pathlib import Path from time import sleep -from typing import Dict, List, NamedTuple, Optional, Set +from typing import Dict, Iterator, List, NamedTuple, Optional, Set import bracex import certifi @@ -251,3 +253,23 @@ def unwrap(text: str) -> str: text = text.strip() # remove consecutive whitespace return re.sub(r'\s+', ' ', text) + + +@contextlib.contextmanager +def print_new_wheels(msg: str, output_dir: Path) -> Iterator[None]: + ''' + Prints the new items in a directory upon exiting. The message to display + can include {n} for number of wheels, {s} for total number of seconds, + and/or {m} for total number of minutes. Does not print anything if this + exits via exception. + ''' + + start_time = time.time() + existing_contents = set(output_dir.iterdir()) + yield + final_contents = set(output_dir.iterdir()) + new_contents = final_contents - existing_contents + n = len(new_contents) + s = time.time() - start_time + m = s / 60 + print(msg.format(n=n, s=s, m=m), *sorted(f" {f.name}" for f in new_contents), sep="\n") diff --git a/unit_test/main_tests/conftest.py b/unit_test/main_tests/conftest.py index 2a0f9bfd..bf2aa01f 100644 --- a/unit_test/main_tests/conftest.py +++ b/unit_test/main_tests/conftest.py @@ -1,3 +1,4 @@ +import contextlib import platform as platform_module import subprocess import sys @@ -58,6 +59,15 @@ def fake_package_dir(monkeypatch): return args +@pytest.fixture(autouse=True) +def disable_print_wheels(monkeypatch): + @contextlib.contextmanager + def empty_cm(*args, **kwargs): + yield + + monkeypatch.setattr(util, 'print_new_wheels', empty_cm) + + @pytest.fixture def allow_empty(request, monkeypatch, fake_package_dir): monkeypatch.setattr(sys, 'argv', fake_package_dir + ['--allow-empty']) diff --git a/unit_test/test_wheel_print.py b/unit_test/test_wheel_print.py new file mode 100644 index 00000000..35e1ce57 --- /dev/null +++ b/unit_test/test_wheel_print.py @@ -0,0 +1,35 @@ +import pytest + +from cibuildwheel.util import print_new_wheels + + +def test_printout_wheels(tmp_path, capsys): + tmp_path.joinpath("example.0").touch() + with print_new_wheels("TEST_MSG: {n}", tmp_path): + tmp_path.joinpath("example.1").touch() + tmp_path.joinpath("example.2").touch() + + captured = capsys.readouterr() + assert captured.err == "" + + assert "example.0" not in captured.out + assert "example.1\n" in captured.out + assert "example.2\n" in captured.out + assert "TEST_MSG:" in captured.out + assert "TEST_MSG: 2\n" in captured.out + + +def test_no_printout_on_error(tmp_path, capsys): + tmp_path.joinpath("example.0").touch() + with pytest.raises(RuntimeError): + with print_new_wheels("TEST_MSG: {n}", tmp_path): + tmp_path.joinpath("example.1").touch() + raise RuntimeError() + + captured = capsys.readouterr() + assert captured.err == "" + + assert "example.0" not in captured.out + assert "example.1" not in captured.out + assert "example.2" not in captured.out + assert "TEST_MSG:" not in captured.out