Repository: conan-io/conan

Issue:
[bug] Consistent command results and error messages accross install/create and graph
### What is your suggestion?

When a dependency is `missing` or `invalid`, CLI commands `install`, `create`, etc, will output pertinent error messages and the exit code of the application will be `!=0`, meaning the command failed.

Making some tests, we have found an inconsistency all along `conan graph` subcommands.
`conan graph info` and `conan graph build-order` should output the same errors as `install` does and should fail in the same way. This does not imply when an error is found not generating the graph output. Commands should generate same graph output but they should fail.

## Example

```sh
 conan install --requires "unleash-client-cpp/1.3.0"
```

<details>

```

======== Input profiles ========
Profile host:
[settings]
arch=armv8
build_type=Release
compiler=apple-clang
compiler.cppstd=gnu17
compiler.libcxx=libc++
compiler.version=15
os=Macos

Profile build:
[settings]
arch=armv8
build_type=Release
compiler=apple-clang
compiler.cppstd=gnu17
compiler.libcxx=libc++
compiler.version=15
os=Macos


======== Computing dependency graph ========
Graph root
    cli
Requirements
    cpr/1.10.5#a227f2a4f0deed3ee1f8b8237477129e - Cache
    libcurl/8.6.0#752cd9c3df0e9487b72301862f15a965 - Cache
    nlohmann_json/3.11.3#45828be26eb619a2e04ca517bb7b828d - Cache
    unleash-client-cpp/1.3.0#a4354a616cd5bdb81354825229954522 - Cache
    zlib/1.3.1#f52e03ae3d251dec704634230cd806a2 - Cache
Build requirements
    autoconf/2.71#f9307992909d7fb3df459340f1932809 - Cache
    automake/1.16.5#058bda3e21c36c9aa8425daf3c1faf50 - Cache
    gnu-config/cci.20210814#dc430d754f465e8c74463019672fb97b - Cache
    libtool/2.4.7#08316dad5c72c541ed21e039e4cf217b - Cache
    m4/1.4.19#b38ced39a01e31fef5435bc634461fd2 - Cache
    meson/1.2.2#04bdfb85d665c82b08a3510aee3ffd19 - Cache
    ninja/1.11.1#77587f8c8318662ac8e5a7867eb4be21 - Cache
    pkgconf/2.1.0#27f44583701117b571307cf5b5fe5605 - Cache
Resolved version ranges
    libcurl/[>=7.78.0 <9]: libcurl/8.6.0
    zlib/[>=1.2.11 <2]: zlib/1.3.1

======== Computing necessary packages ========
cpr/1.10.5: Compatible package ID 01269d344dd5584f4884dfbce8b9b77ae08480e6 equal to the default package ID: Skipping it.
cpr/1.10.5: Checking 5 compatible configurations
cpr/1.10.5: Compatible configurations not found in cache, checking servers
cpr/1.10.5: 'dfee4b06bfe93e4207413fffd653ec0bb2ef0d5c': compiler.cppstd=17
cpr/1.10.5: '1b581912b6a996992095ec289d8a10caf974e2df': compiler.cppstd=20
cpr/1.10.5: 'b59d6a6f26a2d7f87370035527ef219122bb9093': compiler.cppstd=gnu20
cpr/1.10.5: '7b4224207f3ce01b81f5d00add4296667a22e2ba': compiler.cppstd=23
cpr/1.10.5: '839343c67d23729138e2ac0c4c4386e281cd1ffc': compiler.cppstd=gnu23
unleash-client-cpp/1.3.0: Compatible package ID e73661f3afc6efaa3f8ba17b9263163c204f8c52 equal to the default package ID: Skipping it.
unleash-client-cpp/1.3.0: Checking 5 compatible configurations
unleash-client-cpp/1.3.0: Compatible configurations not found in cache, checking servers
unleash-client-cpp/1.3.0: '6332c366c6c529367a2b716abd77f657b96d3c47': compiler.cppstd=17
unleash-client-cpp/1.3.0: '9194788030461f5d772c319ed4b3d745e75e2d47': compiler.cppstd=20
unleash-client-cpp/1.3.0: 'ee3dc95e09fa0d284e807bdf988a10a5cc6e59c9': compiler.cppstd=gnu20
unleash-client-cpp/1.3.0: '4ff0d71ebb4d6a0900ca4e36b2b780168848c458': compiler.cppstd=23
unleash-client-cpp/1.3.0: 'dafb9907f23cc61560a25535b18340c225627896': compiler.cppstd=gnu23
Requirements
    cpr/1.10.5#a227f2a4f0deed3ee1f8b8237477129e:01269d344dd5584f4884dfbce8b9b77ae08480e6 - Missing
    libcurl/8.6.0#752cd9c3df0e9487b72301862f15a965:267b943cf33985f9448f72c97e42fcdb5094fcf5 - Missing
    unleash-client-cpp/1.3.0#a4354a616cd5bdb81354825229954522:e73661f3afc6efaa3f8ba17b9263163c204f8c52 - Missing
    zlib/1.3.1#f52e03ae3d251dec704634230cd806a2:2ee39e692ca4177b4b689b15bc1f2cfdf8f83706 - Missing
Build requirements
Skipped binaries
    nlohmann_json/3.11.3, autoconf/2.71, automake/1.16.5, gnu-config/cci.20210814, libtool/2.4.7, m4/1.4.19, meson/1.2.2, ninja/1.11.1, pkgconf/2.1.0
ERROR: Missing binary: cpr/1.10.5:01269d344dd5584f4884dfbce8b9b77ae08480e6
ERROR: Missing binary: libcurl/8.6.0:267b943cf33985f9448f72c97e42fcdb5094fcf5
ERROR: Missing binary: unleash-client-cpp/1.3.0:e73661f3afc6efaa3f8ba17b9263163c204f8c52
ERROR: Missing binary: zlib/1.3.1:2ee39e692ca4177b4b689b15bc1f2cfdf8f83706

zlib/1.3.1: WARN: Can't find a 'zlib/1.3.1' package binary '2ee39e692ca4177b4b689b15bc1f2cfdf8f83706' for the configuration:
[settings]
arch=armv8
build_type=Release
compiler=apple-clang
compiler.version=15
os=Macos
[options]
fPIC=True
shared=False

ERROR: Missing prebuilt package for 'cpr/1.10.5', 'libcurl/8.6.0', 'unleash-client-cpp/1.3.0', 'zlib/1.3.1'. You can try:
    - List all available packages using 'conan list zlib/1.3.1:* -r=remote'
    - Explain missing binaries: replace 'conan install ...' with 'conan graph explain ...'
    - Try to build locally from sources using the '--build=cpr/1.10.5 --build=libcurl/8.6.0 --build=unleash-client-cpp/1.3.0 --build=zlib/1.3.1' argument

More Info at 'https://docs.conan.io/2/knowledge/faq.html#error-missing-prebuilt-package'

```
</details>

```sh
conan graph info --requires "unleash-client-cpp/1.3.0" > /dev/null
```

<details>

```
======== Computing dependency graph ========
Graph root
    cli
Requirements
    cpr/1.10.5#a227f2a4f0deed3ee1f8b8237477129e - Cache
    libcurl/8.6.0#752cd9c3df0e9487b72301862f15a965 - Cache
    nlohmann_json/3.11.3#45828be26eb619a2e04ca517bb7b828d - Cache
    unleash-client-cpp/1.3.0#a4354a616cd5bdb81354825229954522 - Cache
    zlib/1.3.1#f52e03ae3d251dec704634230cd806a2 - Cache
Build requirements
    autoconf/2.71#f9307992909d7fb3df459340f1932809 - Cache
    automake/1.16.5#058bda3e21c36c9aa8425daf3c1faf50 - Cache
    gnu-config/cci.20210814#dc430d754f465e8c74463019672fb97b - Cache
    libtool/2.4.7#08316dad5c72c541ed21e039e4cf217b - Cache
    m4/1.4.19#b38ced39a01e31fef5435bc634461fd2 - Cache
    meson/1.2.2#04bdfb85d665c82b08a3510aee3ffd19 - Cache
    ninja/1.11.1#77587f8c8318662ac8e5a7867eb4be21 - Cache
    pkgconf/2.1.0#27f44583701117b571307cf5b5fe5605 - Cache
Resolved version ranges
    libcurl/[>=7.78.0 <9]: libcurl/8.6.0
    zlib/[>=1.2.11 <2]: zlib/1.3.1

======== Computing necessary packages ========
cpr/1.10.5: Compatible package ID 01269d344dd5584f4884dfbce8b9b77ae08480e6 equal to the default package ID: Skipping it.
cpr/1.10.5: Checking 5 compatible configurations
cpr/1.10.5: Compatible configurations not found in cache, checking servers
cpr/1.10.5: 'dfee4b06bfe93e4207413fffd653ec0bb2ef0d5c': compiler.cppstd=17
cpr/1.10.5: '1b581912b6a996992095ec289d8a10caf974e2df': compiler.cppstd=20
cpr/1.10.5: 'b59d6a6f26a2d7f87370035527ef219122bb9093': compiler.cppstd=gnu20
cpr/1.10.5: '7b4224207f3ce01b81f5d00add4296667a22e2ba': compiler.cppstd=23
cpr/1.10.5: '839343c67d23729138e2ac0c4c4386e281cd1ffc': compiler.cppstd=gnu23
unleash-client-cpp/1.3.0: Compatible package ID e73661f3afc6efaa3f8ba17b9263163c204f8c52 equal to the default package ID: Skipping it.
unleash-client-cpp/1.3.0: Checking 5 compatible configurations
unleash-client-cpp/1.3.0: Compatible configurations not found in cache, checking servers
unleash-client-cpp/1.3.0: '6332c366c6c529367a2b716abd77f657b96d3c47': compiler.cppstd=17
unleash-client-cpp/1.3.0: '9194788030461f5d772c319ed4b3d745e75e2d47': compiler.cppstd=20
unleash-client-cpp/1.3.0: 'ee3dc95e09fa0d284e807bdf988a10a5cc6e59c9': compiler.cppstd=gnu20
unleash-client-cpp/1.3.0: '4ff0d71ebb4d6a0900ca4e36b2b780168848c458': compiler.cppstd=23
unleash-client-cpp/1.3.0: 'dafb9907f23cc61560a25535b18340c225627896': compiler.cppstd=gnu23
Requirements
    cpr/1.10.5#a227f2a4f0deed3ee1f8b8237477129e:01269d344dd5584f4884dfbce8b9b77ae08480e6 - Missing
    libcurl/8.6.0#752cd9c3df0e9487b72301862f15a965:267b943cf33985f9448f72c97e42fcdb5094fcf5 - Missing
    unleash-client-cpp/1.3.0#a4354a616cd5bdb81354825229954522:e73661f3afc6efaa3f8ba17b9263163c204f8c52 - Missing
    zlib/1.3.1#f52e03ae3d251dec704634230cd806a2:2ee39e692ca4177b4b689b15bc1f2cfdf8f83706 - Missing
Build requirements
Skipped binaries
    nlohmann_json/3.11.3, autoconf/2.71, automake/1.16.5, gnu-config/cci.20210814, libtool/2.4.7, m4/1.4.19, meson/1.2.2, ninja/1.11.1, pkgconf/2.1.0

-------- Basic graph information --------
```
</details>

```sh
conan graph build-order --order-by=recipe --requires "unleash-client-cpp/1.3.0" > /dev/null
```

<details>

```

======== Computing dependency graph ========
Graph root
    cli
Requirements
    cpr/1.10.5#a227f2a4f0deed3ee1f8b8237477129e - Cache
    libcurl/8.8.0#9170903d66d6f6e6795fbb566a403129 - Cache
    nlohmann_json/3.11.3#45828be26eb619a2e04ca517bb7b828d - Cache
    unleash-client-cpp/1.3.0#a4354a616cd5bdb81354825229954522 - Cache
    zlib/1.3.1#f52e03ae3d251dec704634230cd806a2 - Cache
Build requirements
    autoconf/2.71#f9307992909d7fb3df459340f1932809 - Cache
    automake/1.16.5#058bda3e21c36c9aa8425daf3c1faf50 - Cache
    gnu-config/cci.20210814#dc430d754f465e8c74463019672fb97b - Cache
    libtool/2.4.7#08316dad5c72c541ed21e039e4cf217b - Cache
    m4/1.4.19#b38ced39a01e31fef5435bc634461fd2 - Cache
    meson/1.2.2#04bdfb85d665c82b08a3510aee3ffd19 - Cache
    ninja/1.11.1#77587f8c8318662ac8e5a7867eb4be21 - Cache
    pkgconf/2.1.0#27f44583701117b571307cf5b5fe5605 - Cache
Resolved version ranges
    libcurl/[>=7.78.0 <9]: libcurl/8.8.0
    zlib/[>=1.2.11 <2]: zlib/1.3.1

======== Computing necessary packages ========
cpr/1.10.5: Compatible package ID 6da29aec5517b05e82cc36e7f0cea25b756592a1 equal to the default package ID: Skipping it.
cpr/1.10.5: Checking 5 compatible configurations
cpr/1.10.5: Compatible configurations not found in cache, checking servers
cpr/1.10.5: '7c482b127e0f60686b2846e9000db88c8e11666f': compiler.cppstd=17
cpr/1.10.5: '8190a0a48706aaa6e73601f80ccad90dfb634831': compiler.cppstd=20
cpr/1.10.5: '033edebf48b9a193f4775cf79a226f42000d28f2': compiler.cppstd=gnu20
cpr/1.10.5: '4e3c016827f1e0890c20a8270f1157bc59399749': compiler.cppstd=23
cpr/1.10.5: 'a938cbada2ed4cf609e1017660a982a2d845dfe8': compiler.cppstd=gnu23
unleash-client-cpp/1.3.0: Compatible package ID 31826f3ddae6dc65d0a7c1f89aee57b356b02c35 equal to the default package ID: Skipping it.
unleash-client-cpp/1.3.0: Checking 5 compatible configurations
unleash-client-cpp/1.3.0: Compatible configurations not found in cache, checking servers
unleash-client-cpp/1.3.0: '075884ad7d4ee0b37c12ff00532b073d1d95a6ed': compiler.cppstd=17
unleash-client-cpp/1.3.0: 'd1b62f23ae496268defae946797d055b11f8ad07': compiler.cppstd=20
unleash-client-cpp/1.3.0: '1d89d0c5293777c413984d998ab350e8c256ff3f': compiler.cppstd=gnu20
unleash-client-cpp/1.3.0: '8dbe067233297ceac6a97a9cfdc6f0511d43e42d': compiler.cppstd=23
unleash-client-cpp/1.3.0: '0d0154f2b3f41e640700de62e84302ebd7f3ad54': compiler.cppstd=gnu23
Requirements
    cpr/1.10.5#a227f2a4f0deed3ee1f8b8237477129e:6da29aec5517b05e82cc36e7f0cea25b756592a1 - Missing
    libcurl/8.8.0#9170903d66d6f6e6795fbb566a403129:da91ff7ebbce6962ceee923568a6793fa42e7ebe - Missing
    unleash-client-cpp/1.3.0#a4354a616cd5bdb81354825229954522:31826f3ddae6dc65d0a7c1f89aee57b356b02c35 - Missing
    zlib/1.3.1#f52e03ae3d251dec704634230cd806a2:2ee39e692ca4177b4b689b15bc1f2cfdf8f83706 - Missing
Build requirements
Skipped binaries
    nlohmann_json/3.11.3, autoconf/2.71, automake/1.16.5, gnu-config/cci.20210814, libtool/2.4.7, m4/1.4.19, meson/1.2.2, ninja/1.11.1, pkgconf/2.1.0

======== Computing the build order ========
zlib/1.3.1#f52e03ae3d251dec704634230cd806a2:2ee39e692ca4177b4b689b15bc1f2cfdf8f83706 - Missing
libcurl/8.8.0#9170903d66d6f6e6795fbb566a403129:da91ff7ebbce6962ceee923568a6793fa42e7ebe - Missing
cpr/1.10.5#a227f2a4f0deed3ee1f8b8237477129e:6da29aec5517b05e82cc36e7f0cea25b756592a1 - Missing
unleash-client-cpp/1.3.0#a4354a616cd5bdb81354825229954522:31826f3ddae6dc65d0a7c1f89aee57b356b02c35 - Missing
```
</details>

As it can be easily seen, the graph info and build-order commands does not output the expected errors.

### Have you read the CONTRIBUTING guide?

- [X] I've read the CONTRIBUTING guide

Discussion:

Patch:
diff --git a/conan/cli/command.py b/conan/cli/command.py
index 7a074dc7c..b2b50f1c7 100644
--- a/conan/cli/command.py
+++ b/conan/cli/command.py
@@ -98,6 +98,12 @@ class BaseConanCommand:
 
         formatter(info)
 
+    def _dispatch_errors(self, info):
+        if info and isinstance(info, dict):
+            if info.get("conan_error"):
+                raise ConanException(info["conan_error"])
+            if info.get("conan_warning"):
+                ConanOutput().warning(info["conan_warning"])
 
 class ConanArgumentParser(argparse.ArgumentParser):
 
@@ -179,6 +185,7 @@ class ConanCommand(BaseConanCommand):
             else:
                 sub.set_parser(subcommand_parser, conan_api)
                 sub.run(conan_api, parser, *args)
+        self._dispatch_errors(info)
 
     @property
     def group(self):
@@ -198,6 +205,7 @@ class ConanSubCommand(BaseConanCommand):
         info = self._method(conan_api, parent_parser, self._parser, *args)
         # It is necessary to do it after calling the "method" otherwise parser not complete
         self._format(parent_parser, info, *args)
+        self._dispatch_errors(info)
 
     def set_name(self, parent_name):
         self._name = self._subcommand_name.replace(f'{parent_name}-', '', 1)
diff --git a/conan/cli/commands/graph.py b/conan/cli/commands/graph.py
index 8f4124744..690be8869 100644
--- a/conan/cli/commands/graph.py
+++ b/conan/cli/commands/graph.py
@@ -45,8 +45,9 @@ def graph(conan_api, parser, *args):
     """
 
 
-def cli_build_order(build_order):
+def cli_build_order(result):
     # TODO: Very simple cli output, probably needs to be improved
+    build_order = result["build_order"]
     build_order = build_order["order"] if isinstance(build_order, dict) else build_order
     for level in build_order:
         for item in level:
@@ -59,8 +60,8 @@ def cli_build_order(build_order):
                 cli_out_write(f"{item['ref']}:{item['package_id']} - {item['binary']}")
 
 
-def json_build_order(build_order):
-    cli_out_write(json.dumps(build_order, indent=4))
+def json_build_order(result):
+    cli_out_write(json.dumps(result["build_order"], indent=4))
 
 
 @conan_subcommand(formatters={"text": cli_build_order, "json": json_build_order,
@@ -128,7 +129,9 @@ def graph_build_order(conan_api, parser, subparser, *args):
                                                   clean=args.lockfile_clean)
     conan_api.lockfile.save_lockfile(lockfile, args.lockfile_out, cwd)
 
-    return install_order_serialized
+    return {"build_order": install_order_serialized,
+            "conan_error": install_graph.get_errors()}
+
 
 
 @conan_subcommand(formatters={"text": cli_build_order, "json": json_build_order,
@@ -159,7 +162,9 @@ def graph_build_order_merge(conan_api, parser, subparser, *args):
     install_order_serialized = result.install_build_order()
     if getattr(result, "legacy"):
         install_order_serialized = install_order_serialized["order"]
-    return install_order_serialized
+
+    return {"build_order": install_order_serialized,
+            "conan_error": result.get_errors()}
 
 
 @conan_subcommand(formatters={"text": format_graph_info,
@@ -237,7 +242,10 @@ def graph_info(conan_api, parser, subparser, *args):
     return {"graph": deps_graph,
             "field_filter": args.filter,
             "package_filter": args.package_filter,
-            "conan_api": conan_api}
+            "conan_api": conan_api,
+            "conan_error": str(deps_graph.error) if deps_graph.error else None,
+            # Do not compute graph errors if there are dependency errors
+            "conan_warning": InstallGraph(deps_graph).get_errors() if not deps_graph.error else None}
 
 
 @conan_subcommand(formatters={"text": explain_formatter_text,
diff --git a/conan/cli/formatters/graph/build_order_html.py b/conan/cli/formatters/graph/build_order_html.py
index e785bb01b..07d6af46e 100644
--- a/conan/cli/formatters/graph/build_order_html.py
+++ b/conan/cli/formatters/graph/build_order_html.py
@@ -266,7 +266,8 @@ def _render_build_order(build_order, template):
     return template.render(context)
 
 
-def format_build_order_html(build_order):
+def format_build_order_html(result):
+    build_order = result["build_order"]
     build_order = build_order["order"] if isinstance(build_order, dict) else build_order
     template = Template(build_order_html, autoescape=select_autoescape(['html', 'xml']))
     cli_out_write(_render_build_order(build_order, template))
diff --git a/conan/cli/formatters/graph/graph.py b/conan/cli/formatters/graph/graph.py
index 5ebfd23dd..7bec8ba65 100644
--- a/conan/cli/formatters/graph/graph.py
+++ b/conan/cli/formatters/graph/graph.py
@@ -126,8 +126,6 @@ def format_graph_html(result):
         error["type"] = "conflict"
         error["should_highlight_node"] = lambda node: node.id == graph.error.node.id
     cli_out_write(_render_graph(graph, error, template, template_folder))
-    if graph.error:
-        raise graph.error
 
 
 def format_graph_dot(result):
@@ -138,8 +136,6 @@ def format_graph_dot(result):
     user_template = os.path.join(template_folder, "graph.dot")
     template = load(user_template) if os.path.isfile(user_template) else graph_info_dot
     cli_out_write(_render_graph(graph, None, template, template_folder))
-    if graph.error:
-        raise graph.error
 
 
 def format_graph_json(result):
@@ -150,5 +146,3 @@ def format_graph_json(result):
     serial = filter_graph(serial, package_filter=package_filter, field_filter=field_filter)
     json_result = json.dumps({"graph": serial}, indent=4)
     cli_out_write(json_result)
-    if graph.error:
-        raise graph.error
diff --git a/conan/cli/formatters/graph/graph_info_text.py b/conan/cli/formatters/graph/graph_info_text.py
index 055496f54..aa431cd85 100644
--- a/conan/cli/formatters/graph/graph_info_text.py
+++ b/conan/cli/formatters/graph/graph_info_text.py
@@ -33,8 +33,6 @@ def format_graph_info(result):
     for n in serial["nodes"].values():
         cli_out_write(f"{n['ref']}:")  # FIXME: This can be empty for consumers and it is ugly ":"
         _serial_pretty_printer(n, indent="  ")
-    if graph.error:
-        raise graph.error
 
 
 def _serial_pretty_printer(data, indent=""):
diff --git a/conan/tools/gnu/gnutoolchain.py b/conan/tools/gnu/gnutoolchain.py
index 9848e18fc..d154f1cfb 100644
--- a/conan/tools/gnu/gnutoolchain.py
+++ b/conan/tools/gnu/gnutoolchain.py
@@ -1,3 +1,5 @@
+import os
+
 from conan.internal import check_duplicated_generator
 from conan.internal.internal_tools import raise_on_universal_arch
 from conan.tools.apple.apple import is_apple_os, resolve_apple_flags
@@ -9,6 +11,7 @@ from conan.tools.build.flags import architecture_flag, build_type_flags, cppstd_
 from conan.tools.env import Environment
 from conan.tools.gnu.get_gnu_triplet import _get_gnu_triplet
 from conan.tools.microsoft import VCVars, msvc_runtime_flag, unix_path, check_min_vs, is_msvc
+from conans.errors import ConanException
 from conans.model.pkg_type import PackageType
 
 
@@ -18,6 +21,8 @@ class GnuToolchain:
 
     Note: it's based on legacy AutotoolsToolchain but with a more modern and usable UX
     """
+    script_name = "conangnutoolchain"
+
     def __init__(self, conanfile, namespace=None, prefix="/"):
         """
         :param conanfile: The current recipe object. Always use ``self``.
@@ -63,8 +68,8 @@ class GnuToolchain:
             "host": {"triplet": self._conanfile.conf.get("tools.gnu:host_triplet")},
             "build": {"triplet": self._conanfile.conf.get("tools.gnu:build_triplet")}
         }
-        is_cross_building = cross_building(self._conanfile)
-        if is_cross_building:
+        self._is_cross_building = cross_building(self._conanfile)
+        if self._is_cross_building:
             compiler = self._conanfile.settings.get_safe("compiler")
             # Host triplet
             if not self.triplets_info["host"]["triplet"]:
@@ -88,7 +93,7 @@ class GnuToolchain:
         self.configure_args.update(self._get_default_configure_install_flags())
         self.configure_args.update(self._get_default_triplets())
         # Apple stuff
-        is_cross_building_osx = (is_cross_building
+        is_cross_building_osx = (self._is_cross_building
                                  and conanfile.settings_build.get_safe('os') == "Macos"
                                  and is_apple_os(conanfile))
         min_flag, arch_flag, isysroot_flag = (
@@ -99,7 +104,7 @@ class GnuToolchain:
         # -isysroot makes all includes for your library relative to the build directory
         self.apple_isysroot_flag = isysroot_flag
         self.apple_min_version_flag = min_flag
-        # MSVC common stuff
+        # Default initial environment flags
         self._initialize_default_extra_env()
 
     def yes_no(self, option_name, default=None, negated=False):
@@ -116,31 +121,99 @@ class GnuToolchain:
         option_value = not option_value if negated else option_value
         return "yes" if option_value else "no"
 
-    def _initialize_default_extra_env(self):
-        """Initialize the default environment variables."""
-        extra_env_vars = dict()
-        # Normally, these are the most common default flags used by MSVC in Windows
-        if is_msvc(self._conanfile):
-            extra_env_vars = {"CC": "cl -nologo",
-                              "CXX": "cl -nologo",
-                              "NM": "dumpbin -symbols",
-                              "OBJDUMP": ":",
-                              "RANLIB": ":",
-                              "STRIP": ":"}
+    def _resolve_android_cross_compilation(self):
+        # Issue related: https://github.com/conan-io/conan/issues/13443
+        ret = {}
+        if not self._is_cross_building or not self._conanfile.settings.get_safe("os") == "Android":
+            return ret
+
+        ndk_path = self._conanfile.conf.get("tools.android:ndk_path", check_type=str)
+        if not ndk_path:
+            raise ConanException("You must provide a NDK path. Use 'tools.android:ndk_path' "
+                                 "configuration field.")
+
+        if ndk_path:
+            arch = self._conanfile.settings.get_safe("arch")
+            os_build = self._conanfile.settings_build.get_safe("os")
+            ndk_os_folder = {
+                'Macos': 'darwin',
+                'iOS': 'darwin',
+                'watchOS': 'darwin',
+                'tvOS': 'darwin',
+                'visionOS': 'darwin',
+                'FreeBSD': 'linux',
+                'Linux': 'linux',
+                'Windows': 'windows',
+                'WindowsCE': 'windows',
+                'WindowsStore': 'windows'
+            }.get(os_build, "linux")
+            ndk_bin = os.path.join(ndk_path, "toolchains", "llvm", "prebuilt",
+                                   f"{ndk_os_folder}-x86_64", "bin")
+            android_api_level = self._conanfile.settings.get_safe("os.api_level")
+            android_target = {'armv7': 'armv7a-linux-androideabi',
+                              'armv8': 'aarch64-linux-android',
+                              'x86': 'i686-linux-android',
+                              'x86_64': 'x86_64-linux-android'}.get(arch)
+            os_build = self._conanfile.settings_build.get_safe('os')
+            ext = ".cmd" if os_build == "Windows" else ""
+            ret = {
+                "CC": os.path.join(ndk_bin, f"{android_target}{android_api_level}-clang{ext}"),
+                "CXX": os.path.join(ndk_bin, f"{android_target}{android_api_level}-clang++{ext}"),
+                "LD": os.path.join(ndk_bin, "ld"),
+                "STRIP": os.path.join(ndk_bin, "llvm-strip"),
+                "RANLIB": os.path.join(ndk_bin, "llvm-ranlib"),
+                "AS": os.path.join(ndk_bin, f"{android_target}{android_api_level}-clang{ext}"),
+                "AR": os.path.join(ndk_bin, "llvm-ar"),
+            }
+            # Overriding host triplet
+            self.triplets_info["host"]["triplet"] = android_target
+        return ret
+
+    def _resolve_compilers_mapping_variables(self):
+        ret = {}
         # Configuration map
         compilers_mapping = {"c": "CC", "cpp": "CXX", "cuda": "NVCC", "fortran": "FC",
                              "rc": "RC", "nm": "NM", "ranlib": "RANLIB",
                              "objdump": "OBJDUMP", "strip": "STRIP"}
         # Compiler definitions by conf
-        compilers_by_conf = self._conanfile.conf.get("tools.build:compiler_executables", default={},
-                                                     check_type=dict)
+        compilers_by_conf = self._conanfile.conf.get("tools.build:compiler_executables",
+                                                     default={}, check_type=dict)
         if compilers_by_conf:
             for comp, env_var in compilers_mapping.items():
                 if comp in compilers_by_conf:
                     compiler = compilers_by_conf[comp]
                     # https://github.com/conan-io/conan/issues/13780
                     compiler = unix_path(self._conanfile, compiler)
-                    extra_env_vars[env_var] = compiler  # User/tools ones have precedence
+                    ret[env_var] = compiler  # User/tools ones have precedence
+        # Issue related: https://github.com/conan-io/conan/issues/15486
+        if self._is_cross_building and self._conanfile.conf_build:
+            compilers_build_mapping = (
+                self._conanfile.conf_build.get("tools.build:compiler_executables", default={},
+                                               check_type=dict)
+            )
+            if "c" in compilers_build_mapping:
+                ret["CC_FOR_BUILD"] = compilers_build_mapping["c"]
+            if "cpp" in compilers_build_mapping:
+                ret["CXX_FOR_BUILD"] = compilers_build_mapping["cpp"]
+        return ret
+
+    def _initialize_default_extra_env(self):
+        """Initialize the default environment variables."""
+        # If it's an Android cross-compilation
+        extra_env_vars = self._resolve_android_cross_compilation()
+        if not extra_env_vars:
+            # Normally, these are the most common default flags used by MSVC in Windows
+            if is_msvc(self._conanfile):
+                extra_env_vars = {"CC": "cl -nologo",
+                                  "CXX": "cl -nologo",
+                                  "LD": "link -nologo",
+                                  "AR": "lib",
+                                  "NM": "dumpbin -symbols",
+                                  "OBJDUMP": ":",
+                                  "RANLIB": ":",
+                                  "STRIP": ":"}
+            extra_env_vars.update(self._resolve_compilers_mapping_variables())
+
         # Update the extra_env attribute with all the compiler values
         for env_var, env_value in extra_env_vars.items():
             self.extra_env.define(env_var, env_value)
@@ -252,7 +325,7 @@ class GnuToolchain:
         check_duplicated_generator(self, self._conanfile)
         # Composing both environments. User extra_env definitions has precedence
         env_vars = self._environment.vars(self._conanfile)
-        env_vars.save_script("conanautotoolstoolchain")
+        env_vars.save_script(GnuToolchain.script_name)
         # Converts all the arguments into strings
         args = {
             "configure_args": cmd_args_to_string(self._dict_to_list(self.configure_args)),
diff --git a/conans/client/graph/install_graph.py b/conans/client/graph/install_graph.py
index 65befe30d..312ba57e7 100644
--- a/conans/client/graph/install_graph.py
+++ b/conans/client/graph/install_graph.py
@@ -454,27 +454,62 @@ class InstallGraph:
                   "order": [[n.serialize() for n in level] for level in install_order]}
         return result
 
-    def raise_errors(self):
+    def _get_missing_invalid_packages(self):
         missing, invalid = [], []
-        for ref, install_node in self._nodes.items():
-            for package in install_node.packages.values():
-                if package.binary == BINARY_MISSING:
-                    missing.append(package)
-                elif package.binary == BINARY_INVALID:
-                    invalid.append(package)
+        def analyze_package(package):
+            if package.binary == BINARY_MISSING:
+                missing.append(package)
+            elif package.binary == BINARY_INVALID:
+                invalid.append(package)
+        for _, install_node in self._nodes.items():
+            if self._order == "recipe":
+                for package in install_node.packages.values():
+                    analyze_package(package)
+            elif self._order == "configuration":
+                analyze_package(install_node)
+
+        return missing, invalid
 
+    def raise_errors(self):
+        missing, invalid = self._get_missing_invalid_packages()
+        if invalid:
+            self._raise_invalid(invalid)
+        if missing:
+            self._raise_missing(missing)
+
+    def get_errors(self):
+        missing, invalid = self._get_missing_invalid_packages()
+        errors = []
+        tab = "    "
+        if invalid or missing:
+            errors.append("There are some error(s) in the graph:")
         if invalid:
-            msg = ["There are invalid packages:"]
             for package in invalid:
                 node = package.nodes[0]
                 if node.cant_build and node.should_build:
                     binary, reason = "Cannot build for this configuration", node.cant_build
                 else:
                     binary, reason = "Invalid", node.conanfile.info.invalid
-                msg.append("{}: {}: {}".format(node.conanfile, binary, reason))
-            raise ConanInvalidConfiguration("\n".join(msg))
+                errors.append(f"{tab}- {node.ref}: {binary}: {reason}")
         if missing:
-            self._raise_missing(missing)
+            missing_prefs = set(n.nodes[0].pref for n in missing)  # avoid duplicated
+            for pref in list(sorted([str(pref) for pref in missing_prefs])):
+                errors.append(f"{tab}- {pref}: Missing binary")
+        if errors:
+            return "\n".join(errors)
+        return None
+
+    def _raise_invalid(self, invalid):
+        msg = ["There are invalid packages:"]
+        for package in invalid:
+            node = package.nodes[0]
+            if node.cant_build and node.should_build:
+                binary, reason = "Cannot build for this configuration", node.cant_build
+            else:
+                binary, reason = "Invalid", node.conanfile.info.invalid
+            msg.append("{}: {}: {}".format(node.conanfile, binary, reason))
+        raise ConanInvalidConfiguration("\n".join(msg))
+
 
     def _raise_missing(self, missing):
         # TODO: Remove out argument


Test patch:
diff --git a/test/functional/toolchains/gnu/autotools/test_android.py b/test/functional/toolchains/gnu/autotools/test_android.py
index dc0fc4308..665ece684 100644
--- a/test/functional/toolchains/gnu/autotools/test_android.py
+++ b/test/functional/toolchains/gnu/autotools/test_android.py
@@ -4,7 +4,6 @@ import textwrap
 
 import pytest
 
-from conan.test.assets.sources import gen_function_cpp, gen_function_h
 from conan.test.utils.tools import TestClient
 from test.conftest import tools_locations
 
@@ -17,7 +16,7 @@ from test.conftest import tools_locations
 @pytest.mark.tool("android_ndk")
 @pytest.mark.tool("autotools")
 @pytest.mark.skipif(platform.system() != "Darwin", reason="NDK only installed on MAC")
-def test_android_meson_toolchain_cross_compiling(arch, expected_arch):
+def test_android_autotools_toolchain_cross_compiling(arch, expected_arch):
     profile_host = textwrap.dedent("""
     include(default)
 
diff --git a/test/functional/toolchains/gnu/test_gnutoolchain_android.py b/test/functional/toolchains/gnu/test_gnutoolchain_android.py
new file mode 100644
index 000000000..53f38407d
--- /dev/null
+++ b/test/functional/toolchains/gnu/test_gnutoolchain_android.py
@@ -0,0 +1,49 @@
+import os
+import platform
+import textwrap
+
+import pytest
+
+from conan.test.utils.mocks import ConanFileMock
+from conan.test.utils.tools import TestClient
+from conan.tools.files import replace_in_file
+from test.conftest import tools_locations
+
+
+@pytest.mark.parametrize("arch, expected_arch", [('armv8', 'aarch64'),
+                                                 ('armv7', 'arm'),
+                                                 ('x86', 'i386'),
+                                                 ('x86_64', 'x86_64')
+                                                 ])
+@pytest.mark.tool("android_ndk")
+@pytest.mark.tool("autotools")
+@pytest.mark.skipif(platform.system() != "Darwin", reason="NDK only installed on MAC")
+def test_android_gnutoolchain_cross_compiling(arch, expected_arch):
+    profile_host = textwrap.dedent("""
+    include(default)
+
+    [settings]
+    os = Android
+    os.api_level = 21
+    arch = {arch}
+
+    [conf]
+    tools.android:ndk_path={ndk_path}
+    """)
+    ndk_path = tools_locations["android_ndk"]["system"]["path"][platform.system()]
+    profile_host = profile_host.format(
+        arch=arch,
+        ndk_path=ndk_path
+    )
+
+    client = TestClient(path_with_spaces=False)
+    # FIXME: Change this when we have gnu_lib as template in the new command
+    client.run("new autotools_lib -d name=hello -d version=1.0")
+    replace_in_file(ConanFileMock(), os.path.join(client.current_folder, "conanfile.py"),
+                    "AutotoolsToolchain", "GnuToolchain")
+    client.save({"profile_host": profile_host})
+    client.run("build . --profile:build=default --profile:host=profile_host")
+    libhello = os.path.join("build-release", "src", ".libs", "libhello.a")
+    # Check binaries architecture
+    client.run_command('objdump -f "%s"' % libhello)
+    assert "architecture: %s" % expected_arch in client.out
diff --git a/test/integration/command_v2/test_info_build_order.py b/test/integration/command_v2/test_info_build_order.py
index b735920e1..24ea16427 100644
--- a/test/integration/command_v2/test_info_build_order.py
+++ b/test/integration/command_v2/test_info_build_order.py
@@ -76,9 +76,9 @@ def test_info_build_order():
     # test html format
     c.run("graph build-order consumer --build=missing --format=html")
     assert "<body>" in c.stdout
-    c.run("graph build-order consumer --order-by=recipe --format=html")
+    c.run("graph build-order consumer --order-by=recipe --build=missing --format=html")
     assert "<body>" in c.stdout
-    c.run("graph build-order consumer --order-by=configuration --format=html")
+    c.run("graph build-order consumer --order-by=configuration --build=missing --format=html")
     assert "<body>" in c.stdout
 
 
@@ -540,6 +540,58 @@ def test_info_build_order_lockfile_location():
     assert os.path.exists(os.path.join(c.current_folder, "myconan2.lock"))
 
 
+def test_build_order_missing_package_check_error():
+    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")
+
+    exit_code = c.run("graph build-order consumer --build='pkg/*' --order=configuration --format=json", assert_error=True)
+    bo_json = json.loads(c.stdout)
+    assert bo_json["order_by"] == "configuration"
+    assert exit_code != 0
+    assert "dep/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709: Missing binary" in c.out
+
+    result = [
+        [
+            {
+                "ref": "dep/0.1#4d670581ccb765839f2239cc8dff8fbd",
+                "pref": "dep/0.1#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709",
+                "package_id": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+                "prev": None,
+                "context": "host",
+                "binary": "Missing",
+                "options": [],
+                "filenames": [],
+                "depends": [],
+                "overrides": {},
+                "build_args": None,
+            }
+        ],
+        [
+            {
+                "ref": "pkg/0.1#1ac8dd17c0f9f420935abd3b6a8fa032",
+                "pref": "pkg/0.1#1ac8dd17c0f9f420935abd3b6a8fa032:59205ba5b14b8f4ebc216a6c51a89553021e82c1",
+                "package_id": "59205ba5b14b8f4ebc216a6c51a89553021e82c1",
+                "prev": None,
+                "context": "host",
+                "binary": "Build",
+                "options": [],
+                "filenames": [],
+                "depends": [
+                    "dep/0.1#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709"
+                ],
+                "overrides": {},
+                "build_args": "--requires=pkg/0.1 --build=pkg/0.1",
+            }
+        ],
+    ]
+
+    assert bo_json["order"] == result
+
+
 def test_info_build_order_broken_recipe():
     # https://github.com/conan-io/conan/issues/14104
     c = TestClient()
diff --git a/test/integration/conanfile/conan_data_test.py b/test/integration/conanfile/conan_data_test.py
index f81e144e0..64aa2a86f 100644
--- a/test/integration/conanfile/conan_data_test.py
+++ b/test/integration/conanfile/conan_data_test.py
@@ -442,3 +442,35 @@ def test_trim_conandata_as_hook_without_conandata(raise_if_missing):
     else:
         c.run("export . --version=1.0")
         assert c.exported_recipe_revision() == "a9ec2e5fbb166568d4670a9cd1ef4b26"
+
+
+def test_trim_conandata_anchors():
+    """Anchors load correctly, because trim_conandata loads the yaml instead of replacing in place"""
+    tc = TestClient(light=True)
+    tc.save({"conanfile.py": textwrap.dedent("""
+     from conan import ConanFile
+     from conan.tools.files import trim_conandata
+
+     class Pkg(ConanFile):
+        name = "pkg"
+        def export(self):
+            trim_conandata(self)
+
+        def generate(self):
+            self.output.info("x: {}".format(self.conan_data["mapping"][self.version]["x"]))
+    """),
+             "conandata.yml": textwrap.dedent("""
+             mapping:
+                "1.0": &anchor
+                    "x": "foo"
+                "2.0": *anchor
+             """)})
+    tc.run("create . --version=2.0")
+    assert "x: foo" in tc.out
+    pkg_layout = tc.exported_layout()
+    conandata = tc.load(pkg_layout.conandata())
+    assert conandata == textwrap.dedent("""\
+        mapping:
+          '2.0':
+            x: foo
+        """)
diff --git a/test/integration/graph/test_replace_requires.py b/test/integration/graph/test_replace_requires.py
index 3d2c385ed..31edd82f2 100644
--- a/test/integration/graph/test_replace_requires.py
+++ b/test/integration/graph/test_replace_requires.py
@@ -147,3 +147,13 @@ def test_replace_requires_json_format():
     assert "pkg/0.1: pkg/0.2" in c.out  # The replacement happens
     graph = json.loads(c.stdout)
     assert graph["graph"]["replaced_requires"] == {"pkg/0.1": "pkg/0.2"}
+
+
+def test_replace_requires_test_requires():
+    c = TestClient(light=True)
+    c.save({"gtest/conanfile.py": GenConanfile("gtest", "0.2"),
+            "app/conanfile.py": GenConanfile().with_test_requires("gtest/0.1"),
+            "profile": f"[replace_requires]\ngtest/0.1: gtest/0.2"})
+    c.run("create gtest")
+    c.run("install app -pr=profile")
+    assert "gtest/0.1: gtest/0.2" in c.out  # The replacement happens
diff --git a/test/integration/toolchains/gnu/test_gnutoolchain.py b/test/integration/toolchains/gnu/test_gnutoolchain.py
index d35d45b04..e93583ec5 100644
--- a/test/integration/toolchains/gnu/test_gnutoolchain.py
+++ b/test/integration/toolchains/gnu/test_gnutoolchain.py
@@ -37,7 +37,7 @@ def test_extra_flags_via_conf(os_):
                  "profile": profile})
     client.run("install . --profile:build=profile --profile:host=profile")
     toolchain = client.load(
-        "conanautotoolstoolchain{}".format('.bat' if os_ == "Windows" else '.sh'))
+        "conangnutoolchain{}".format('.bat' if os_ == "Windows" else '.sh'))
     if os_ == "Windows":
         assert 'set "CPPFLAGS=%CPPFLAGS% -DNDEBUG -DDEF1 -DDEF2"' in toolchain
         assert 'set "CXXFLAGS=%CXXFLAGS% -O3 --flag1 --flag2"' in toolchain
@@ -88,7 +88,7 @@ def test_extra_flags_order():
     client.save({"conanfile.py": conanfile, "profile": profile})
     client.run('install . -pr=./profile')
     toolchain = client.load(
-        "conanautotoolstoolchain{}".format('.bat' if platform.system() == "Windows" else '.sh'))
+        "conangnutoolchain{}".format('.bat' if platform.system() == "Windows" else '.sh'))
 
     assert '-Dextra_defines -Ddefines' in toolchain
     assert 'extra_cxxflags cxxflags' in toolchain
@@ -113,7 +113,7 @@ def test_autotools_custom_environment():
 
     client.save({"conanfile.py": conanfile})
     client.run("install . -s:b os=Linux -s:h os=Linux")
-    content = load(os.path.join(client.current_folder, "conanautotoolstoolchain.sh"))
+    content = load(os.path.join(client.current_folder, "conangnutoolchain.sh"))
     assert 'export FOO="BAR"' in content
 
 
@@ -141,7 +141,7 @@ def test_linker_scripts_via_conf(os_):
                  "profile": profile})
     client.run("install . --profile:build=profile --profile:host=profile")
     toolchain = client.load(
-        "conanautotoolstoolchain{}".format('.bat' if os_ == "Windows" else '.sh'))
+        "conangnutoolchain{}".format('.bat' if os_ == "Windows" else '.sh'))
     if os_ == "Windows":
         assert 'set "LDFLAGS=%LDFLAGS% --flag5 --flag6 -T\'/linker/scripts/flash.ld\' -T\'/linker/scripts/extra_data.ld\'"' in toolchain
     else:
@@ -255,7 +255,7 @@ def test_toolchain_and_compilers_build_context():
         generators = "GnuToolchain"
 
         def build(self):
-            toolchain = os.path.join(self.generators_folder, "conanautotoolstoolchain.sh")
+            toolchain = os.path.join(self.generators_folder, "conangnutoolchain.sh")
             content = load(self, toolchain)
             assert 'export CC="clang"' in content
             assert 'export CXX="clang++"' in content
@@ -274,11 +274,14 @@ def test_toolchain_and_compilers_build_context():
         tool_requires = "tool/1.0"
 
         def build(self):
-            toolchain = os.path.join(self.generators_folder, "conanautotoolstoolchain.sh")
+            toolchain = os.path.join(self.generators_folder, "conangnutoolchain.sh")
             content = load(self, toolchain)
             assert 'export CC="gcc"' in content
             assert 'export CXX="g++"' in content
             assert 'export RC="windres"' in content
+            # Issue: https://github.com/conan-io/conan/issues/15486
+            assert 'export CC_FOR_BUILD="clang"' in content
+            assert 'export CXX_FOR_BUILD="clang++"' in content
     """)
     client = TestClient()
     client.save({
@@ -376,7 +379,7 @@ def test_msvc_profile_defaults():
         generators = "GnuToolchain"
 
         def build(self):
-            toolchain = os.path.join(self.generators_folder, "conanautotoolstoolchain.bat")
+            toolchain = os.path.join(self.generators_folder, "conangnutoolchain.bat")
             content = load(self, toolchain)
             # Default values and conf ones
             assert r'set "CC=clang"' in content  # conf value has precedence
@@ -415,7 +418,7 @@ def test_msvc_profile_defaults():
             tc.generate()
 
         def build(self):
-            toolchain = os.path.join(self.generators_folder, "conanautotoolstoolchain.bat")
+            toolchain = os.path.join(self.generators_folder, "conangnutoolchain.bat")
             content = load(self, toolchain)
             # Default values
             assert r'set "CC=compile clang"' in content