Repository: conan-io/conan

Issue:
[feature] Print git commands when print_run_commands = True
<!-- What is your suggestion? Please be as specific as possible! -->
It would be nice to have `git` commands from the `tools.Git` helper be printed out if the `print_run_commands` variable is set to `True`. Could even be a new configuration option if desired

- [X] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).


Discussion:

Patch:
diff --git a/conan/api/subapi/config.py b/conan/api/subapi/config.py
index 3819c86d7..f489036d1 100644
--- a/conan/api/subapi/config.py
+++ b/conan/api/subapi/config.py
@@ -29,9 +29,8 @@ class ConfigAPI:
         # TODO: We probably want to split this into git-folder-http cases?
         from conans.client.conf.config_installer import configuration_install
         app = ConanApp(self.conan_api)
-        return configuration_install(app, path_or_url, verify_ssl,
-                                     config_type=config_type, args=args,
-                                     source_folder=source_folder, target_folder=target_folder)
+        configuration_install(app, path_or_url, verify_ssl, config_type=config_type, args=args,
+                              source_folder=source_folder, target_folder=target_folder)
 
     def get(self, name, default=None, check_type=None):
         return self.global_conf.get(name, default=default, check_type=check_type)
diff --git a/conan/cli/commands/graph.py b/conan/cli/commands/graph.py
index d8babfefa..be2439883 100644
--- a/conan/cli/commands/graph.py
+++ b/conan/cli/commands/graph.py
@@ -41,9 +41,13 @@ def cli_build_order(build_order):
     # TODO: Very simple cli output, probably needs to be improved
     for level in build_order:
         for item in level:
-            for package_level in item['packages']:
-                for package in package_level:
-                    cli_out_write(f"{item['ref']}:{package['package_id']} - {package['binary']}")
+            # If this is a configuration order, it has no packages entry, each item is a package
+            if 'packages' in item:
+                for package_level in item['packages']:
+                    for package in package_level:
+                        cli_out_write(f"{item['ref']}:{package['package_id']} - {package['binary']}")
+            else:
+                cli_out_write(f"{item['ref']}:{item['package_id']} - {item['binary']}")
 
 
 def json_build_order(build_order):
diff --git a/conan/tools/cmake/toolchain/toolchain.py b/conan/tools/cmake/toolchain/toolchain.py
index 52e446a6f..5f264ad1c 100644
--- a/conan/tools/cmake/toolchain/toolchain.py
+++ b/conan/tools/cmake/toolchain/toolchain.py
@@ -160,6 +160,8 @@ class CMakeToolchain(object):
         self.find_builddirs = True
         self.user_presets_path = "CMakeUserPresets.json"
         self.presets_prefix = "conan"
+        self.presets_build_environment = None
+        self.presets_run_environment = None
 
     def _context(self):
         """ Returns dict, the context for the template
@@ -232,8 +234,8 @@ class CMakeToolchain(object):
         if self._conanfile.conf.get("tools.cmake.cmaketoolchain:presets_environment", default="",
                                     check_type=str, choices=("disabled", "")) != "disabled":
 
-            build_env = VirtualBuildEnv(self._conanfile, auto_generate=True).vars()
-            run_env = VirtualRunEnv(self._conanfile, auto_generate=True).vars()
+            build_env = self.presets_build_environment.vars(self._conanfile) if self.presets_build_environment else VirtualBuildEnv(self._conanfile, auto_generate=True).vars()
+            run_env = self.presets_run_environment.vars(self._conanfile) if self.presets_run_environment else VirtualRunEnv(self._conanfile, auto_generate=True).vars()
 
             buildenv = {name: value for name, value in
                         build_env.items(variable_reference="$penv{{{name}}}")}
diff --git a/conan/tools/env/virtualbuildenv.py b/conan/tools/env/virtualbuildenv.py
index cdc80f4dd..0da4b9250 100644
--- a/conan/tools/env/virtualbuildenv.py
+++ b/conan/tools/env/virtualbuildenv.py
@@ -9,6 +9,7 @@ class VirtualBuildEnv:
     """
 
     def __init__(self, conanfile, auto_generate=False):
+        self._buildenv = None
         self._conanfile = conanfile
         if not auto_generate:
             self._conanfile.virtualbuildenv = False
@@ -42,33 +43,36 @@ class VirtualBuildEnv:
 
         :return: an ``Environment`` object instance containing the obtained variables.
         """
-        # FIXME: Cache value?
-        build_env = Environment()
+
+        if self._buildenv is None:
+            self._buildenv = Environment()
+        else:
+            return self._buildenv
 
         # Top priority: profile
         profile_env = self._conanfile.buildenv
-        build_env.compose_env(profile_env)
+        self._buildenv.compose_env(profile_env)
 
         build_requires = self._conanfile.dependencies.build.topological_sort
         for require, build_require in reversed(build_requires.items()):
             if require.direct:  # Only buildenv_info from direct deps is propagated
                 # higher priority, explicit buildenv_info
                 if build_require.buildenv_info:
-                    build_env.compose_env(build_require.buildenv_info)
+                    self._buildenv.compose_env(build_require.buildenv_info)
             # Lower priority, the runenv of all transitive "requires" of the build requires
             if build_require.runenv_info:
-                build_env.compose_env(build_require.runenv_info)
+                self._buildenv.compose_env(build_require.runenv_info)
             # Then the implicit
             os_name = self._conanfile.settings_build.get_safe("os")
-            build_env.compose_env(runenv_from_cpp_info(build_require, os_name))
+            self._buildenv.compose_env(runenv_from_cpp_info(build_require, os_name))
 
         # Requires in host context can also bring some direct buildenv_info
         host_requires = self._conanfile.dependencies.host.topological_sort
         for require in reversed(host_requires.values()):
             if require.buildenv_info:
-                build_env.compose_env(require.buildenv_info)
+                self._buildenv.compose_env(require.buildenv_info)
 
-        return build_env
+        return self._buildenv
 
     def vars(self, scope="build"):
         """
diff --git a/conan/tools/env/virtualrunenv.py b/conan/tools/env/virtualrunenv.py
index bd9cda077..c7308418b 100644
--- a/conan/tools/env/virtualrunenv.py
+++ b/conan/tools/env/virtualrunenv.py
@@ -34,6 +34,7 @@ class VirtualRunEnv:
 
         :param conanfile:  The current recipe object. Always use ``self``.
         """
+        self._runenv = None
         self._conanfile = conanfile
         if not auto_generate:
             self._conanfile.virtualrunenv = False
@@ -60,23 +61,26 @@ class VirtualRunEnv:
 
         :return: an ``Environment`` object instance containing the obtained variables.
         """
-        runenv = Environment()
+
+        if self._runenv is None:
+            self._runenv = Environment()
+        else:
+            return self._runenv
 
         # Top priority: profile
         profile_env = self._conanfile.runenv
-        runenv.compose_env(profile_env)
-        # FIXME: Cache value?
+        self._runenv.compose_env(profile_env)
 
         host_req = self._conanfile.dependencies.host
         test_req = self._conanfile.dependencies.test
         for require, dep in list(host_req.items()) + list(test_req.items()):
             if dep.runenv_info:
-                runenv.compose_env(dep.runenv_info)
+                self._runenv.compose_env(dep.runenv_info)
             if require.run:  # Only if the require is run (shared or application to be run)
                 _os = self._conanfile.settings.get_safe("os")
-                runenv.compose_env(runenv_from_cpp_info(dep, _os))
+                self._runenv.compose_env(runenv_from_cpp_info(dep, _os))
 
-        return runenv
+        return self._runenv
 
     def vars(self, scope="run"):
         """
diff --git a/conan/tools/scm/git.py b/conan/tools/scm/git.py
index b4944a1b3..b1e4dabd5 100644
--- a/conan/tools/scm/git.py
+++ b/conan/tools/scm/git.py
@@ -1,6 +1,7 @@
 import fnmatch
 import os
 
+from conan.api.output import Color
 from conan.tools.files import chdir, update_conandata
 from conan.errors import ConanException
 from conans.model.conf import ConfDefinition
@@ -31,12 +32,14 @@ class Git:
             else:
                 self._excluded = conf_excluded
 
-    def run(self, cmd):
+    def run(self, cmd, hidden_output=None):
         """
         Executes ``git <cmd>``
 
         :return: The console output of the command.
         """
+        print_cmd = cmd if hidden_output is None else cmd.replace(hidden_output, "<hidden>")
+        self._conanfile.output.info(f"RUN: git {print_cmd}", fg=Color.BRIGHT_BLUE)
         with chdir(self._conanfile, self.folder):
             # We tried to use self.conanfile.run(), but it didn't work:
             #  - when using win_bash, crashing because access to .settings (forbidden in source())
@@ -203,7 +206,8 @@ class Git:
         mkdir(self.folder)
         self._conanfile.output.info("Cloning git repo")
         target_path = f'"{target}"' if target else ""  # quote in case there are spaces in path
-        self.run('clone "{}" {} {}'.format(url, " ".join(args), target_path))
+        # Avoid printing the clone command, it can contain tokens
+        self.run('clone "{}" {} {}'.format(url, " ".join(args), target_path), hidden_output=url)
 
     def fetch_commit(self, url, commit):
         """
@@ -214,7 +218,7 @@ class Git:
             url = url.replace("\\", "/")  # Windows local directory
         self._conanfile.output.info("Shallow fetch of git repo")
         self.run('init')
-        self.run(f'remote add origin "{url}"')
+        self.run(f'remote add origin "{url}"', hidden_output=url)
         self.run(f'fetch --depth 1 origin {commit}')
         self.run('checkout FETCH_HEAD')
 
diff --git a/conans/client/conf/config_installer.py b/conans/client/conf/config_installer.py
index 67411f6b7..a83cbea4c 100644
--- a/conans/client/conf/config_installer.py
+++ b/conans/client/conf/config_installer.py
@@ -12,18 +12,16 @@ from conans.util.files import mkdir, rmdir, remove, unzip, chdir
 from conans.util.runners import detect_runner
 
 
-class ConanIgnoreMatcher:
+class _ConanIgnoreMatcher:
     def __init__(self, conanignore_path):
-        self.conanignore_path = os.path.abspath(conanignore_path)
+        conanignore_path = os.path.abspath(conanignore_path)
         self._ignored_entries = {".conanignore"}
-        self._parse_conanignore()
-
-    def _parse_conanignore(self):
-        with open(self.conanignore_path, 'r') as conanignore:
-            for line in conanignore:
-                line_content = line.strip()
-                if line_content != "":
-                    self._ignored_entries.add(line_content)
+        if os.path.exists(conanignore_path):
+            with open(conanignore_path, 'r') as conanignore:
+                for line in conanignore:
+                    line_content = line.strip()
+                    if line_content != "":
+                        self._ignored_entries.add(line_content)
 
     def matches(self, path):
         for ignore_entry in self._ignored_entries:
@@ -106,9 +104,8 @@ def _process_file(directory, filename, config, cache, folder):
         else:
             target_folder = os.path.join(cache.cache_folder, relpath)
 
-        if os.path.exists(target_folder):
-            if os.path.isfile(target_folder):  # Existed as a file and now should be a folder
-                remove(target_folder)
+        if os.path.isfile(target_folder):  # Existed as a file and now should be a folder
+            remove(target_folder)
 
         mkdir(target_folder)
         output.info("Copying file %s to %s" % (filename, target_folder))
@@ -121,15 +118,13 @@ def _process_folder(config, folder, cache):
     if config.source_folder:
         folder = os.path.join(folder, config.source_folder)
     conanignore_path = os.path.join(folder, '.conanignore')
-    conanignore = None
-    if os.path.exists(conanignore_path):
-        conanignore = ConanIgnoreMatcher(conanignore_path)
+    conanignore = _ConanIgnoreMatcher(conanignore_path)
     for root, dirs, files in os.walk(folder):
         # .git is always ignored by default, even if not present in .conanignore
         dirs[:] = [d for d in dirs if d != ".git"]
         for f in files:
             rel_path = os.path.relpath(os.path.join(root, f), folder)
-            if conanignore is None or not conanignore.matches(rel_path):
+            if not conanignore.matches(rel_path):
                 _process_file(root, f, config, cache, folder)
 
 
@@ -150,51 +145,27 @@ def _process_download(config, cache, requester):
 
 
 class _ConfigOrigin(object):
-    def __init__(self, data):
-        self.type = data.get("type")
-        self.uri = data.get("uri")
-        self.verify_ssl = data.get("verify_ssl")
-        self.args = data.get("args")
-        self.source_folder = data.get("source_folder")
-        self.target_folder = data.get("target_folder")
-
-    def __eq__(self, other):
-        return (self.type == other.type and self.uri == other.uri and
-                self.args == other.args and self.source_folder == other.source_folder
-                and self.target_folder == other.target_folder)
-
-    def json(self):
-        return {"type": self.type,
-                "uri": self.uri,
-                "verify_ssl": self.verify_ssl,
-                "args": self.args,
-                "source_folder": self.source_folder,
-                "target_folder": self.target_folder}
-
-    @staticmethod
-    def from_item(uri, config_type, verify_ssl, args, source_folder, target_folder):
-        config = _ConfigOrigin({})
+    def __init__(self, uri, config_type, verify_ssl, args, source_folder, target_folder):
         if config_type:
-            config.type = config_type
+            self.type = config_type
         else:
             if uri.endswith(".git"):
-                config.type = "git"
+                self.type = "git"
             elif os.path.isdir(uri):
-                config.type = "dir"
+                self.type = "dir"
             elif os.path.isfile(uri):
-                config.type = "file"
+                self.type = "file"
             elif uri.startswith("http"):
-                config.type = "url"
+                self.type = "url"
             else:
                 raise ConanException("Unable to deduce type config install: %s" % uri)
-        config.source_folder = source_folder
-        config.target_folder = target_folder
-        config.args = args
-        config.verify_ssl = verify_ssl
+        self.source_folder = source_folder
+        self.target_folder = target_folder
+        self.args = args
+        self.verify_ssl = verify_ssl
         if os.path.exists(uri):
             uri = os.path.abspath(uri)
-        config.uri = uri
-        return config
+        self.uri = uri
 
 
 def _is_compressed_file(filename):
@@ -202,15 +173,14 @@ def _is_compressed_file(filename):
     import zipfile
     if zipfile.is_zipfile(filename):
         return True
-    if (filename.endswith(".tar.gz") or filename.endswith(".tgz") or
-            filename.endswith(".tbz2") or filename.endswith(".tar.bz2") or
-            filename.endswith(".tar") or filename.endswith(".gz") or
-            filename.endswith(".tar.xz") or filename.endswith(".txz")):
-        return True
-    return False
+    tgz_exts = (".tar.gz", ".tgz", ".tbz2", ".tar.bz2", ".tar", ".gz", ".tar.xz", ".txz")
+    return any(filename.endswith(e) for e in tgz_exts)
 
 
-def _process_config(config, cache, requester):
+def configuration_install(app, uri, verify_ssl, config_type=None,
+                          args=None, source_folder=None, target_folder=None):
+    cache, requester = app.cache, app.requester
+    config = _ConfigOrigin(uri, config_type, verify_ssl, args, source_folder, target_folder)
     try:
         if config.type == "git":
             _process_git_repo(config, cache)
@@ -229,13 +199,3 @@ def _process_config(config, cache, requester):
             raise ConanException("Unable to process config install: %s" % config.uri)
     except Exception as e:
         raise ConanException("Failed conan config install: %s" % str(e))
-
-
-def configuration_install(app, uri, verify_ssl, config_type=None,
-                          args=None, source_folder=None, target_folder=None):
-    cache, requester = app.cache, app.requester
-
-    # Execute and store the new one
-    config = _ConfigOrigin.from_item(uri, config_type, verify_ssl, args,
-                                     source_folder, target_folder)
-    _process_config(config, cache, requester)


Test patch:
diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
index a7467411c..abe187569 100644
--- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -1496,114 +1496,188 @@ def test_redirect_stdout():
 
 
 @pytest.mark.tool("cmake", "3.23")
-def test_add_env_to_presets():
-    c = TestClient()
+class TestEnvironmentInPresets:
+    @pytest.fixture(scope="class")
+    def _init_client(self):
+        c = TestClient()
+        tool = textwrap.dedent(r"""
+            import os
+            from conan import ConanFile
+            from conan.tools.files import chdir, save
+            class Tool(ConanFile):
+                version = "0.1"
+                settings = "os", "compiler", "arch", "build_type"
+                def package(self):
+                    with chdir(self, self.package_folder):
+                        save(self, f"bin/{{self.name}}.bat", f"@echo off\necho running: {{self.name}}/{{self.version}}")
+                        save(self, f"bin/{{self.name}}.sh", f"echo running: {{self.name}}/{{self.version}}")
+                        os.chmod(f"bin/{{self.name}}.sh", 0o777)
+                def package_info(self):
+                    self.buildenv_info.define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE")
+                    {}
+            """)
 
-    tool = textwrap.dedent(r"""
-        import os
-        from conan import ConanFile
-        from conan.tools.files import chdir, save
-        class Tool(ConanFile):
-            version = "0.1"
-            settings = "os", "compiler", "arch", "build_type"
-            def package(self):
-                with chdir(self, self.package_folder):
-                    save(self, f"bin/{{self.name}}.bat", f"@echo off\necho running: {{self.name}}/{{self.version}}")
-                    save(self, f"bin/{{self.name}}.sh", f"echo running: {{self.name}}/{{self.version}}")
-                    os.chmod(f"bin/{{self.name}}.sh", 0o777)
-            def package_info(self):
-                self.buildenv_info.define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE")
-                {}
+        consumer = textwrap.dedent("""
+            [tool_requires]
+            mytool/0.1
+            [test_requires]
+            mytesttool/0.1
+            [layout]
+            cmake_layout
         """)
 
-    consumer = textwrap.dedent("""
-        [tool_requires]
-        mytool/0.1
-        [test_requires]
-        mytesttool/0.1
-        [layout]
-        cmake_layout
-    """)
-
-    test_env = textwrap.dedent("""
-        #include <cstdlib>
-        int main() {
-            return std::getenv("MY_RUNVAR") ? 0 : 1;
-        }
-    """)
+        test_env = textwrap.dedent("""
+            #include <cstdlib>
+            int main() {
+                return std::getenv("MY_RUNVAR") ? 0 : 1;
+            }
+        """)
 
-    cmakelists = textwrap.dedent("""
-        cmake_minimum_required(VERSION 3.15)
-        project(MyProject)
-        if(WIN32)
-            set(MYTOOL_SCRIPT "mytool.bat")
-        else()
-            set(MYTOOL_SCRIPT "mytool.sh")
-        endif()
-        add_custom_target(run_mytool COMMAND ${MYTOOL_SCRIPT})
-        # build var should be available at configure
-        set(MY_BUILD_VAR $ENV{MY_BUILD_VAR})
-        if (MY_BUILD_VAR)
-            message("MY_BUILD_VAR:${MY_BUILD_VAR}")
-        else()
-            message("MY_BUILD_VAR NOT FOUND")
-        endif()
-        # run var should not be available at configure, just when testing
-        set(MY_RUNVAR $ENV{MY_RUNVAR})
-        if (MY_RUNVAR)
-            message("MY_RUNVAR:${MY_RUNVAR}")
-        else()
-            message("MY_RUNVAR NOT FOUND")
-        endif()
-        enable_testing()
-        add_executable(test_env test_env.cpp)
-        add_test(NAME TestRunEnv COMMAND test_env)
-    """)
+        cmakelists = textwrap.dedent("""
+            cmake_minimum_required(VERSION 3.15)
+            project(MyProject)
+
+            if(WIN32)
+                set(MYTOOL_SCRIPT "mytool.bat")
+            else()
+                set(MYTOOL_SCRIPT "mytool.sh")
+            endif()
+
+            add_custom_target(run_mytool COMMAND ${MYTOOL_SCRIPT})
+
+            function(check_and_report_variable var_name)
+                set(var_value $ENV{${var_name}})
+                if (var_value)
+                    message("${var_name}:${var_value}")
+                else()
+                    message("${var_name} NOT FOUND")
+                endif()
+            endfunction()
+
+            check_and_report_variable("MY_BUILD_VAR")
+            check_and_report_variable("MY_RUNVAR")
+            check_and_report_variable("MY_ENV_VAR")
+
+            enable_testing()
+            add_executable(test_env test_env.cpp)
+            add_test(NAME TestRunEnv COMMAND test_env)
+        """)
 
-    c.save({"tool.py": tool.format(""),
-            "test_tool.py": tool.format('self.runenv_info.define("MY_RUNVAR", "MY_RUNVAR_VALUE")'),
-            "conanfile.txt": consumer,
-            "CMakeLists.txt": cmakelists,
-            "test_env.cpp": test_env})
+        c.save({"tool.py": tool.format(""),
+                "test_tool.py": tool.format('self.runenv_info.define("MY_RUNVAR", "MY_RUNVAR_VALUE")'),
+                "conanfile.txt": consumer,
+                "CMakeLists.txt": cmakelists,
+                "test_env.cpp": test_env})
 
-    c.run("create tool.py --name=mytool")
+        c.run("create tool.py --name=mytool")
 
-    c.run("create test_tool.py --name=mytesttool")
-    c.run("create test_tool.py --name=mytesttool -s build_type=Debug")
+        c.run("create test_tool.py --name=mytesttool")
+        c.run("create test_tool.py --name=mytesttool -s build_type=Debug")
 
-    # do a first conan install with env disabled just to test that the conf works
-    c.run("install . -g CMakeToolchain -g CMakeDeps -c tools.cmake.cmaketoolchain:presets_environment=disabled")
+        yield c
 
-    presets_path = os.path.join("build", "Release", "generators", "CMakePresets.json") \
-        if platform.system() != "Windows" else os.path.join("build", "generators", "CMakePresets.json")
-    presets = json.loads(c.load(presets_path))
+    def test_add_env_to_presets(self, _init_client):
+        c = _init_client
 
-    assert presets["configurePresets"][0].get("env") is None
+        # do a first conan install with env disabled just to test that the conf works
+        c.run("install . -g CMakeToolchain -g CMakeDeps "
+              "-c tools.cmake.cmaketoolchain:presets_environment=disabled")
 
-    c.run("install . -g CMakeToolchain -g CMakeDeps")
-    c.run("install . -g CMakeToolchain -g CMakeDeps -s:h build_type=Debug")
+        presets_path = os.path.join("build", "Release", "generators", "CMakePresets.json") \
+            if platform.system() != "Windows" else os.path.join("build", "generators",
+                                                                "CMakePresets.json")
+        presets = json.loads(c.load(presets_path))
 
-    # test that the buildenv is correctly injected to configure and build steps
-    # that the runenv is not injected to configure, but it is when running tests
+        assert presets["configurePresets"][0].get("env") is None
 
-    preset = "conan-default" if platform.system() == "Windows" else "conan-release"
+        c.run("install . -g CMakeToolchain -g CMakeDeps")
+        c.run("install . -g CMakeToolchain -g CMakeDeps -s:h build_type=Debug")
 
-    c.run_command(f"cmake --preset {preset}")
-    assert "MY_BUILD_VAR:MY_BUILDVAR_VALUE" in c.out
-    assert "MY_RUNVAR NOT FOUND" in c.out
-    c.run_command("cmake --build --preset conan-release --target run_mytool --target test_env")
-    assert "running: mytool/0.1" in c.out
+        # test that the buildenv is correctly injected to configure and build steps
+        # that the runenv is not injected to configure, but it is when running tests
 
-    c.run_command("ctest --preset conan-release")
-    assert "tests passed" in c.out
+        preset = "conan-default" if platform.system() == "Windows" else "conan-release"
 
-    if platform.system() != "Windows":
-        c.run_command("cmake --preset conan-debug")
+        c.run_command(f"cmake --preset {preset}")
         assert "MY_BUILD_VAR:MY_BUILDVAR_VALUE" in c.out
         assert "MY_RUNVAR NOT FOUND" in c.out
+        c.run_command("cmake --build --preset conan-release --target run_mytool --target test_env")
+        assert "running: mytool/0.1" in c.out
+
+        c.run_command("ctest --preset conan-release")
+        assert "tests passed" in c.out
+
+        if platform.system() != "Windows":
+            c.run_command("cmake --preset conan-debug")
+            assert "MY_BUILD_VAR:MY_BUILDVAR_VALUE" in c.out
+            assert "MY_RUNVAR NOT FOUND" in c.out
+
+        c.run_command("cmake --build --preset conan-debug --target run_mytool --target test_env")
+        assert "running: mytool/0.1" in c.out
+
+        c.run_command("ctest --preset conan-debug")
+        assert "tests passed" in c.out
+
+    def test_add_generate_env_to_presets(self, _init_client):
+        c = _init_client
+
+        consumer = textwrap.dedent("""
+            from conan import ConanFile
+            from conan.tools.cmake import cmake_layout, CMakeToolchain, CMakeDeps
+            from conan.tools.env import VirtualBuildEnv, VirtualRunEnv, Environment
+
+            class mypkgRecipe(ConanFile):
+                name = "mypkg"
+                version = "1.0"
+                settings = "os", "compiler", "build_type", "arch"
+                tool_requires = "mytool/0.1"
+                test_requires = "mytesttool/0.1"
+                def layout(self):
+                    cmake_layout(self)
+
+                def generate(self):
+
+                    buildenv = VirtualBuildEnv(self)
+                    buildenv.environment().define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE_OVERRIDEN")
+                    buildenv.generate()
+
+                    runenv = VirtualRunEnv(self)
+                    runenv.environment().define("MY_RUN_VAR", "MY_RUNVAR_SET_IN_GENERATE")
+                    runenv.generate()
+
+                    env = Environment()
+                    env.define("MY_ENV_VAR", "MY_ENV_VAR_VALUE")
+                    env = env.vars(self)
+                    env.save_script("other_env")
+
+                    deps = CMakeDeps(self)
+                    deps.generate()
+                    tc = CMakeToolchain(self)
+                    tc.presets_build_environment = buildenv.environment().compose_env(env)
+                    tc.presets_run_environment = runenv.environment()
+                    tc.generate()
+        """)
+
+        test_env = textwrap.dedent("""
+            #include <string>
+            int main() {
+                return std::string(std::getenv("MY_RUNVAR"))=="MY_RUNVAR_SET_IN_GENERATE";
+            }
+        """)
+
+        c.save({"conanfile.py": consumer,
+                "test_env.cpp": test_env})
+
+        c.run("install conanfile.py")
+
+        preset = "conan-default" if platform.system() == "Windows" else "conan-release"
+
+        c.run_command(f"cmake --preset {preset}")
+        assert "MY_BUILD_VAR:MY_BUILDVAR_VALUE_OVERRIDEN" in c.out
+        assert "MY_ENV_VAR:MY_ENV_VAR_VALUE" in c.out
 
-    c.run_command("cmake --build --preset conan-debug --target run_mytool --target test_env")
-    assert "running: mytool/0.1" in c.out
+        c.run_command("cmake --build --preset conan-release --target run_mytool --target test_env")
+        assert "running: mytool/0.1" in c.out
 
-    c.run_command("ctest --preset conan-debug")
-    assert "tests passed" in c.out
+        c.run_command(f"ctest --preset conan-release")
+        assert "tests passed" in c.out
diff --git a/conans/test/functional/tools/scm/test_git.py b/conans/test/functional/tools/scm/test_git.py
index 3a47bd31b..90cd70257 100644
--- a/conans/test/functional/tools/scm/test_git.py
+++ b/conans/test/functional/tools/scm/test_git.py
@@ -275,7 +275,10 @@ class TestGitBasicClone:
 
         c = TestClient()
         c.save({"conanfile.py": self.conanfile.format(url=url, commit=commit)})
-        c.run("create .")
+        c.run("create . -v")
+        # Clone is not printed, it might contain tokens
+        assert 'pkg/0.1: RUN: git clone "<hidden>"  "."' in c.out
+        assert "pkg/0.1: RUN: git checkout" in c.out
         assert "pkg/0.1: MYCMAKE: mycmake" in c.out
         assert "pkg/0.1: MYFILE: myheader!" in c.out
 
@@ -407,7 +410,8 @@ class TestGitShallowClone:
 
         c = TestClient()
         c.save({"conanfile.py": self.conanfile.format(url=url, commit=commit)})
-        c.run("create .")
+        c.run("create . -v")
+        assert 'pkg/0.1: RUN: git remote add origin "<hidden>"' in c.out
         assert "pkg/0.1: MYCMAKE: mycmake" in c.out
         assert "pkg/0.1: MYFILE: myheader!" in c.out
 
diff --git a/conans/test/integration/command_v2/test_info_build_order.py b/conans/test/integration/command_v2/test_info_build_order.py
index 3f4844457..bee614430 100644
--- a/conans/test/integration/command_v2/test_info_build_order.py
+++ b/conans/test/integration/command_v2/test_info_build_order.py
@@ -111,6 +111,21 @@ def test_info_build_order_configuration():
     assert bo_json == result
 
 
+def test_info_build_order_configuration_text_formatter():
+    c = TestClient()
+    c.save({"dep/conanfile.py": GenConanfile(),
+            "pkg/conanfile.py": GenConanfile().with_requires("dep/0.1"),
+            "consumer/conanfile.txt": "[requires]\npkg/0.1"})
+    c.run("export dep --name=dep --version=0.1")
+    c.run("export pkg --name=pkg --version=0.1")
+    c.run("graph build-order consumer --build=missing --order=configuration --format=text")
+    assert textwrap.dedent("""\
+    ======== Computing the build order ========
+    dep/0.1#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709 - Build
+    pkg/0.1#1ac8dd17c0f9f420935abd3b6a8fa032:59205ba5b14b8f4ebc216a6c51a89553021e82c1 - Build
+    """) in c.out
+
+
 def test_info_build_order_build_require():
     c = TestClient()
     c.save({"dep/conanfile.py": GenConanfile(),