Repository: conan-io/conan

Issue:
[feature] Ability to identify if binaries can be executed on a particular platform
Typically there is made use of `tools.cross_building(self.settings)` to identify that for example test binaries can't be executed on a particular platform. Though this particular solution is not really sufficient to handle for example the case of running on a macOS with Apple Silicon (M1) chipset, as in that particular case you can execute both x86_64 and armv8 binaries without any issue (even without Rosetta enabled). 

Extending `tools.cross_building` with yet another parameter similar to `skip_x64_x86` seems not a good option. Therefore it would be good to have a dedicated method for this usecase like for example `tools.can_run()`.


Discussion:
It's true you don't need to invoke Rosetta manually on M1 to execute x86_64, but still, the architecture is not the same and Mac will run Rosetta 2 dynamically, which has some loss of performance. I could use QEMU on Linux and say the same thing, but still, is not optimized.

I disagree adding such feature because any possible incompatibility error when executing an app, will result result someone asking to Conan issues or Conan Center Index for a solution, which is not related to Conan.

Running aligned architecture is the best option ever, and Conan already supports M1, which means, you can package ARM packages too.
I don't dislike being able to run things that are cross-built in platforms that allow it, and it seems this feature could make sense.

A different story is if we want to cross-build and/or cross-execute in ConanCenter. I see a point if that execution subsystem is not rock solid, as any potential error will result in further undesirable broken builds, support, tickets, discussions, etc in ConanCenter.
also, as I understand, this move could be a temporary. e.g. previously, Rosetta allowed to execute x86 (32-bit) and PowerPC binaries on x86_64 machines, but eventually Apple has removed that support completely, as x86_64 Apple machines became more wide-spread than x86(32) and PowerPC. actually, it lasted only OS 3 releases (from 10.4 to 10.7) that time.
> also, as I understand, this move could be a temporary. e.g. previously, Rosetta allowed to execute x86 (32-bit) and PowerPC binaries on x86_64 machines, but eventually Apple has removed that support completely, as x86_64 Apple machines became more wide-spread than x86(32) and PowerPC. actually, it lasted only OS 3 releases (from 10.4 to 10.7) that time.

Yes, if the OSX support for this might be changing, dropped, etc., then trying to have something robust in Conan can be a maintenance burden and source of bugs and support tickets. And if support for x86(32) lasted only 3 releases, is not looking good...
the suggestion might be to implement an external configuration for cross-building function. maybe just a single boolean value within profile (or conf) that overrides the result of `tools.cross_building`. as there are other similar cases (e.g. https://github.com/conan-io/conan/issues/10923) when conan's built-in logic is not suitable enough.
> the suggestion might be to implement an external configuration for cross-building function. maybe just a single boolean value within profile (or conf) that overrides the result of tools.cross_building. as there are other similar cases (e.g. https://github.com/conan-io/conan/issues/10923) when conan's built-in logic is not suitable enough.

It cannot be a conf to alter the ``cross_building`` result. We still want to have the ``cross_building()`` to return ``True`` when building x86_64 in M1 computer, but still we want something else to tell that we can indeed run the binary (Note that the logic used now in recipes is ``if not cross_building(): run...``

So the only possible reasonable alternative I see at this moment is:

- Add a ``can_run()`` method.
- This methods uses internally ``cross_building()`` returning ``False`` if cross_building
- Unless there is a ``conf`` that says ``can_run=True`` and bypass the ``cross_building`` result (only for the ``can_run()``)

This might be dummy implementation, but allows recipes to have clean logic and users to control manually the execution, without needing to implement any "smart" logic inside conan.
yes, I support that direction

Resolving patch:
diff --git a/conan/tools/build/__init__.py b/conan/tools/build/__init__.py
--- a/conan/tools/build/__init__.py
+++ b/conan/tools/build/__init__.py
@@ -1,2 +1,2 @@
 from conan.tools.build.cpu import build_jobs
-from conan.tools.build.cross_building import cross_building
+from conan.tools.build.cross_building import cross_building, can_run
diff --git a/conan/tools/build/cross_building.py b/conan/tools/build/cross_building.py
--- a/conan/tools/build/cross_building.py
+++ b/conan/tools/build/cross_building.py
@@ -28,3 +28,14 @@ def get_cross_building_settings(conanfile):
                 os_host, arch_host)
     else:
         return os_host, arch_host, os_host, arch_host
+
+
+def can_run(conanfile):
+    """
+    Validates if the current build platform can run a file which is not for same arch
+    See https://github.com/conan-io/conan/issues/11035
+    """
+    allowed = conanfile.conf.get("tools.build.cross_building:can_run", check_type=bool)
+    if allowed is None:
+        return not cross_building(conanfile)
+    return allowed


Test patch:
diff --git a/conans/test/unittests/tools/build/__init__.py b/conans/test/unittests/tools/build/__init__.py
new file mode 100644
diff --git a/conans/test/unittests/tools/build/test_can_run.py b/conans/test/unittests/tools/build/test_can_run.py
new file mode 100644
--- /dev/null
+++ b/conans/test/unittests/tools/build/test_can_run.py
@@ -0,0 +1,66 @@
+import pytest
+
+from conans.test.utils.mocks import MockSettings, ConanFileMock
+from conan.tools.build import can_run
+
+
+class ConfigMock:
+    def __init__(self, run=None):
+        self.can_run = run
+
+    def get(self, conf_name, default=None, check_type=None):
+        return self.can_run
+
+
+@pytest.mark.parametrize("expected", [False, True])
+def test_can_run_explicit_config(expected):
+    """ When the config tools.build.cross_building:can_run is declared and evaluated (True|False),
+        can_run must return the same value.
+    """
+    config = ConfigMock(expected)
+    settings = MockSettings({"os": "Macos",
+                             "compiler": "apple-clang",
+                             "compiler.version": "11.0",
+                             "arch": "armv8"})
+    conanfile = ConanFileMock(settings)
+    conanfile.conf = config
+    assert expected == can_run(conanfile)
+
+
+@pytest.mark.parametrize("arch, expected", [("x86_64", False), ("armv8", True)])
+def test_can_run_cross_building(arch, expected):
+    """ When the config is None, and is cross-building, can_run must return False.
+    """
+    config = ConfigMock(None)
+    settings_build = MockSettings({"os": "Macos",
+                             "compiler": "apple-clang",
+                             "compiler.version": "11.0",
+                             "arch": "armv8"})
+    settings = MockSettings({"os": "Macos",
+                             "compiler": "apple-clang",
+                             "compiler.version": "11.0",
+                             "arch": arch})
+    conanfile = ConanFileMock()
+    conanfile.settings = settings
+    conanfile.settings_build = settings_build
+    conanfile.conf = config
+    assert expected == can_run(conanfile)
+
+
+def test_can_run_cross_building_with_explicit():
+    """ When the config is True or False, and is cross-building, can_run must follow the config.
+    """
+    config = ConfigMock(True)
+    settings_build = MockSettings({"os": "Macos",
+                             "compiler": "apple-clang",
+                             "compiler.version": "11.0",
+                             "arch": "armv8"})
+    settings = MockSettings({"os": "Macos",
+                             "compiler": "apple-clang",
+                             "compiler.version": "11.0",
+                             "arch": "x86_64"})
+    conanfile = ConanFileMock()
+    conanfile.settings = settings
+    conanfile.settings_build = settings_build
+    conanfile.conf = config
+    assert True == can_run(conanfile)