diff --git a/conans/client/output.py b/conans/client/output.py index 9c18a494155..655e6770d46 100644 --- a/conans/client/output.py +++ b/conans/client/output.py @@ -58,18 +58,22 @@ def write(self, data, front=None, back=None, newline=False): data = decode_text(data) # Keep python 2 compatibility if self._color and (front or back): - color = "%s%s" % (front or '', back or '') - end = (Style.RESET_ALL + "\n") if newline else Style.RESET_ALL # @UndefinedVariable - data = "%s%s%s" % (color, data, end) - else: - if newline: - data = "%s\n" % data - - try: - self._stream.write(data) - except UnicodeError: - data = data.encode("utf8").decode("ascii", "ignore") - self._stream.write(data) + data = "%s%s%s%s" % (front or '', back or '', data, Style.RESET_ALL) + if newline: + data = "%s\n" % data + + # https://github.com/conan-io/conan/issues/4277 + # Windows output locks produce IOErrors + for _ in range(3): + try: + self._stream.write(data) + break + except IOError: + import time + time.sleep(0.02) + except UnicodeError: + data = data.encode("utf8").decode("ascii", "ignore") + self._stream.flush() def info(self, data): diff --git a/conans/test/unittests/client/conan_output_test.py b/conans/test/unittests/client/conan_output_test.py new file mode 100644 index 00000000000..77838157ce0 --- /dev/null +++ b/conans/test/unittests/client/conan_output_test.py @@ -0,0 +1,30 @@ +# coding=utf-8 + +import unittest +from types import MethodType + +from six import StringIO + +from conans.client.output import ConanOutput +from mock import mock + + +class ConanOutputTest(unittest.TestCase): + + def test_blocked_output(self): + # https://github.com/conan-io/conan/issues/4277 + stream = StringIO() + + def write_raise(self, data): + write_raise.counter = getattr(write_raise, "counter", 0) + 1 + if write_raise.counter < 2: + raise IOError("Stdout locked") + self.super_write(data) + stream.super_write = stream.write + stream.write = MethodType(write_raise, stream) + out = ConanOutput(stream) + + with mock.patch("time.sleep") as sleep: + out.write("Hello world") + sleep.assert_any_call(0.02) + self.assertEqual("Hello world", stream.getvalue()) diff --git a/conans/util/progress_bar.py b/conans/util/progress_bar.py index 51f64f4b205..295fb2f7c78 100644 --- a/conans/util/progress_bar.py +++ b/conans/util/progress_bar.py @@ -1,4 +1,3 @@ - import os from contextlib import contextmanager @@ -18,17 +17,15 @@ class _FileReaderWithProgressBar(object): def __init__(self, fileobj, output, desc=None): pb_kwargs = self.tqdm_defaults.copy() - self._ori_output = output # If there is no terminal, just print a beat every TIMEOUT_BEAT seconds. if not output.is_terminal: output = _NoTerminalOutput(output) pb_kwargs['mininterval'] = TIMEOUT_BEAT_SECONDS - self._output = output self._fileobj = fileobj self.seek(0, os.SEEK_END) - self._pb = tqdm(total=self.tell(), desc=desc, file=output, **pb_kwargs) + self._tqdm_bar = tqdm(total=self.tell(), desc=desc, file=output, **pb_kwargs) self.seek(0) def seekable(self): @@ -43,15 +40,11 @@ def tell(self): def read(self, size): prev = self.tell() ret = self._fileobj.read(size) - self._pb.update(self.tell() - prev) + self._tqdm_bar.update(self.tell() - prev) return ret def pb_close(self): - self._pb.close() - - def pb_write(self, message): - """ Allow to write messages to output without interfering with the progress bar """ - tqdm.write(message, file=self._ori_output) + self._tqdm_bar.close() class _NoTerminalOutput(object):