Repository: conan-io/conan

Issue:
CONAN_PRINT_RUN_COMMANDS=1 leads to TypeError exception under python 2.7
Consider the following test example:
```python
from conans import ConanFile
from io import StringIO
import sys
import traceback

class TestConan(ConanFile):
    name = "test"
    version = "0.0.1"

    def package(self):
        output = StringIO()
        try:
            self.run("cd .", output=output)
        except:
            traceback.print_tb(sys.exc_info()[2])
            raise
```

Let's try to run `conan create` under python 2 & 3 with or without `CONAN_PRINT_RUN_COMMANDS=1` (I run python2 tests under lasote/conangcc5 docker image and python3 tests under Windows) 

Python3:
`conan create . test/0.0.1@dbely/testing` - works
`CONAN_PRINT_RUN_COMMANDS=1 conan create . test/0.0.1@dbely/testing` - works

Python2:
`conan create . test/0.0.1@dbely/testing` - works
`CONAN_PRINT_RUN_COMMANDS=1 conan create . test/0.0.1@dbely/testing` - fails with
```
  File "/home/conan/.conan/data/test/0.0.1/dbely/testing/export/conanfile.py", line 13, in package
    self.run("cd .", output=output)
  File "/usr/local/lib/python2.7/dist-packages/conans/model/conan_file.py", line 252, in run
    retcode = self._runner(command, output, os.path.abspath(RUN_LOG_NAME),  cwd)
  File "/usr/local/lib/python2.7/dist-packages/conans/client/runner.py", line 31, in __call__
    stream_output.write(call_message)
ERROR: test/0.0.1@dbely/testing: Error in package() method, line 13
        self.run("cd .", output=output)
        TypeError: unicode argument expected, got 'str'
```
Looks like a bug inside conan, isn't it? BTW, the way conan reports python exception (losing stack trace) is very misleading. I had to add `try/except` and `traceback.print_tb(sys.exc_info()[2])` to find out what was going on.



Discussion:
Yes, it looks like a bug, let's take a look.

About the traceback, have you tried the `conan.conf => verbose_traceback = True` or env `CONAN_VERBOSE_TRACEBACK`?
> About the traceback, have you tried the conan.conf => verbose_traceback = True or env CONAN_VERBOSE_TRACEBACK?

 Thanks for pointing this out. That's exactly what I needed.

Resolving patch:
diff --git a/conans/client/runner.py b/conans/client/runner.py
--- a/conans/client/runner.py
+++ b/conans/client/runner.py
@@ -1,3 +1,4 @@
+import io
 import os
 import sys
 from subprocess import Popen, PIPE, STDOUT
@@ -6,6 +7,7 @@
 import six
 
 
+
 class ConanRunner(object):
 
     def __init__(self, print_commands_to_output=False, generate_run_log_file=False, log_run_to_output=True):
@@ -20,6 +22,11 @@ def __call__(self, command, output, log_filepath=None, cwd=None):
         @param log_filepath: If specified, also log to a file
         @param cwd: Move to directory to execute
         """
+        if output and isinstance(output, io.StringIO) and six.PY2:
+            # in py2 writing to a StringIO requires unicode, otherwise it fails
+            raise Exception("Invalid output parameter of type io.StringIO(), "
+                            "use six.StringIO() instead")
+
         stream_output = output if output and hasattr(output, "write") else sys.stdout
 
         if not self._generate_run_log_file:


Test patch:
diff --git a/conans/test/functional/runner_test.py b/conans/test/functional/runner_test.py
--- a/conans/test/functional/runner_test.py
+++ b/conans/test/functional/runner_test.py
@@ -1,7 +1,10 @@
-import unittest
-from conans.test.utils.tools import TestClient
 import os
+import six
+import unittest
+
+from io import StringIO
 from conans.client.runner import ConanRunner
+from conans.test.utils.tools import TestClient
 
 
 class RunnerTest(unittest.TestCase):
@@ -32,6 +35,21 @@ def build(self):
         test_folder = os.path.join(client.current_folder, "test_folder")
         self.assertTrue(os.path.exists(test_folder))
 
+    def test_write_to_stringio(self):
+        runner = ConanRunner(print_commands_to_output=True,
+                             generate_run_log_file=True,
+                             log_run_to_output=True)
+        out = StringIO()
+        if six.PY2:
+            with self.assertRaisesRegexp(Exception, "Invalid output parameter"):
+                runner("python --version", output=out)
+
+        out = six.StringIO()
+        runner("python --version", output=out)
+        self.assertIn("""---Running------
+> python --version
+-----------------""", out.getvalue())
+
     def log_test(self):
         conanfile = '''
 from conans import ConanFile
@@ -42,7 +60,7 @@ class ConanFileToolsTest(ConanFile):
 
     def build(self):
         self.run("cmake --version")
-    '''
+'''
         # A runner logging everything
         runner = ConanRunner(print_commands_to_output=True,
                              generate_run_log_file=True,