Repository: conan-io/conan

Issue:
Have `ConanFile.run()` be able to get `stderr` just like it does for `stdout`
Somestimes recipes call to programs that outùt important info to stderr instead of stdout, and Conan currently provides no good way to fetch that info


_Originally posted by @RubenRBS in https://github.com/conan-io/conan-center-index/pull/20979#discussion_r1395712850_
            

Discussion:

Patch:
diff --git a/conan/api/subapi/export.py b/conan/api/subapi/export.py
index 69b877750..984ac129d 100644
--- a/conan/api/subapi/export.py
+++ b/conan/api/subapi/export.py
@@ -4,6 +4,7 @@ from conans.client.cmd.export import cmd_export
 from conans.client.conanfile.package import run_package_method
 from conans.client.graph.graph import BINARY_BUILD
 from conans.model.package_ref import PkgReference
+from conans.util.files import mkdir
 
 
 class ExportAPI:
@@ -40,6 +41,8 @@ class ExportAPI:
         conanfile.folders.set_base_folders(source_folder, output_folder)
         dest_package_folder = pkg_layout.package()
         conanfile.folders.set_base_package(dest_package_folder)
+        mkdir(pkg_layout.metadata())
+        conanfile.folders.set_base_pkg_metadata(pkg_layout.metadata())
 
         with pkg_layout.set_dirty_context_manager():
             prev = run_package_method(conanfile, package_id, hook_manager, ref)
diff --git a/conan/tools/cmake/toolchain/toolchain.py b/conan/tools/cmake/toolchain/toolchain.py
index 7e47045c8..cba5103b1 100644
--- a/conan/tools/cmake/toolchain/toolchain.py
+++ b/conan/tools/cmake/toolchain/toolchain.py
@@ -115,6 +115,9 @@ class CMakeToolchain(object):
         {% endfor %}
         # Preprocessor definitions per configuration
         {{ iterate_configs(preprocessor_definitions_config, action='add_compile_definitions') }}
+
+        if(CMAKE_POLICY_DEFAULT_CMP0091)  # Avoid unused and not-initialized warnings
+        endif()
         """)
 
     def __init__(self, conanfile, generator=None):
diff --git a/conans/model/conan_file.py b/conans/model/conan_file.py
index d480d37fc..c2d009575 100644
--- a/conans/model/conan_file.py
+++ b/conans/model/conan_file.py
@@ -318,7 +318,7 @@ class ConanFile:
         return Path(self.generators_folder)
 
     def run(self, command, stdout=None, cwd=None, ignore_errors=False, env="", quiet=False,
-            shell=True, scope="build"):
+            shell=True, scope="build", stderr=None):
         # NOTE: "self.win_bash" is the new parameter "win_bash" for Conan 2.0
         command = self._conan_helpers.cmd_wrapper.wrap(command, conanfile=self)
         if env == "":  # This default allows not breaking for users with ``env=None`` indicating
@@ -332,7 +332,7 @@ class ConanFile:
         from conans.util.runners import conan_run
         if not quiet:
             ConanOutput().writeln(f"{self.display_name}: RUN: {command}", fg=Color.BRIGHT_BLUE)
-        retcode = conan_run(wrapped_cmd, cwd=cwd, stdout=stdout, shell=shell)
+        retcode = conan_run(wrapped_cmd, cwd=cwd, stdout=stdout, stderr=stderr, shell=shell)
         if not quiet:
             ConanOutput().writeln("")
 
diff --git a/pyinstaller.py b/pyinstaller.py
index 2fd314550..25f6d06d2 100644
--- a/pyinstaller.py
+++ b/pyinstaller.py
@@ -19,7 +19,6 @@ import os
 import platform
 import shutil
 import subprocess
-from distutils import dir_util
 
 from conans import __version__
 from conans.util.files import save
@@ -104,7 +103,6 @@ def pyinstall(source_folder):
     conan_path = os.path.join(source_folder, 'conans', 'conan.py')
     hidden = ("--hidden-import=glob "  # core stdlib
               "--hidden-import=pathlib "
-              "--hidden-import=distutils.dir_util "
               # Modules that can be imported in ConanFile conan.tools and errors
               "--collect-submodules=conan.cli.commands "
               "--hidden-import=conan.errors "


Test patch:
diff --git a/conans/test/functional/conanfile/runner_test.py b/conans/test/functional/conanfile/runner_test.py
index 9144dc459..c4180f2ae 100644
--- a/conans/test/functional/conanfile/runner_test.py
+++ b/conans/test/functional/conanfile/runner_test.py
@@ -56,3 +56,18 @@ class Pkg(ConanFile):
         client.save({"conanfile.py": conanfile})
         client.run("source .")
         self.assertIn('conanfile.py: Buffer got msgs Hello', client.out)
+
+    def test_custom_stream_stderr(self):
+        conanfile = textwrap.dedent("""
+            from io import StringIO
+            from conan import ConanFile
+            class Pkg(ConanFile):
+                def source(self):
+                    my_buf = StringIO()
+                    self.run('echo Hello 1>&2', stderr=my_buf)
+                    self.output.info("Buffer got stderr msgs {}".format(my_buf.getvalue()))
+            """)
+        client = TestClient()
+        client.save({"conanfile.py": conanfile})
+        client.run("source .")
+        self.assertIn('conanfile.py: Buffer got stderr msgs Hello', client.out)
diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
index a64cfa11a..3e8a3ddf4 100644
--- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -239,6 +239,7 @@ def test_cmake_toolchain_multiple_user_toolchain():
     client.run("create . --name=pkg --version=0.1")
     assert "mytoolchain1.cmake !!!running!!!" in client.out
     assert "mytoolchain2.cmake !!!running!!!" in client.out
+    assert "CMake Warning" not in client.out
 
 
 @pytest.mark.tool("cmake")
diff --git a/conans/test/integration/metadata/test_metadata_logs.py b/conans/test/integration/metadata/test_metadata_logs.py
index 357f10dd3..1371ed16f 100644
--- a/conans/test/integration/metadata/test_metadata_logs.py
+++ b/conans/test/integration/metadata/test_metadata_logs.py
@@ -190,3 +190,34 @@ class TestHooksMetadataLogs:
         assert c.load("metadata/logs/src.log") == "srclog!!"
         c.run("build .")
         assert c.load("mybuild/metadata/logs/mylogs.txt") == "some logs!!!"
+
+
+def test_metadata_export_pkg():
+    conanfile = textwrap.dedent("""
+        import os
+        from conan import ConanFile
+        from conan.tools.files import save, copy
+
+        class Pkg(ConanFile):
+            name = "pkg"
+            version = "0.1"
+
+            def build(self):
+                save(self, "mylogs.txt", "some logs!!!")
+                copy(self, "mylogs.txt", src=self.build_folder,
+                     dst=os.path.join(self.package_metadata_folder, "logs"))
+
+            def package(self):
+                copy(self, "*", src=os.path.join(self.build_folder, "metadata"),
+                     dst=self.package_metadata_folder)
+        """)
+
+    c = TestClient()
+    c.save({"conanfile.py": conanfile})
+    c.run("build .")
+    c.run("export-pkg .")
+    # Test local cache looks good
+    pkg_layout = c.created_layout()
+    assert os.listdir(pkg_layout.metadata()) == ["logs"]
+    assert os.listdir(os.path.join(pkg_layout.metadata(), "logs")) == ["mylogs.txt"]
+    assert load(os.path.join(pkg_layout.metadata(), "logs", "mylogs.txt")) == "some logs!!!"