Repository: conan-io/conan
Pull request: Allow settings_target across compatibility method
Changelog: Feature: Allow access to `settings_target` in compatibility method.
Docs: Omit

Closes #14527


In the info.py module, following the guidance provided in the comments for the settings_target property, this commit focuses on refining the handling of settings_target within various package methods.

When populating the settings_target property via the package_id() method, the adjustment guarantees that it remains accessible to both the info and original_info methods. By extending its accessibility to these methods, it allows for seamless utilization within the compatibility() method as well.


- [X] Refer to the issue that supports this Pull Request.
- [X] If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
- [X] I've read the [Contributing guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [x] I've followed the PEP8 style guides for Python code.
- [ ] I've opened another PR in the Conan docs repo to the ``develop`` branch, documenting this one.

--- 
Test Case Result

Before
```
E
E           ======== Computing necessary packages ========
E           tool/0.1: Compatible package ID 30b28f4b57d074979ce37758614b0bd2ca007f31 equal to the default package ID
E           app/0.1: Forced build from source
E           Requirements
E               app/0.1#0a0d67a98e79b54a848dc56809f632a4:4acd47797ab70f037765f4aa81d97e141228872f - Build
E           Build requirements
E               tool/0.1#643d7250bb4082abb8ef774ceb2e720c:30b28f4b57d074979ce37758614b0bd2ca007f31 - Missing
E
E           ======== Installing packages ========
E           ERROR: Missing binary: tool/0.1:30b28f4b57d074979ce37758614b0bd2ca007f31
E
E           tool/0.1: WARN: Can't find a 'tool/0.1' package binary '30b28f4b57d074979ce37758614b0bd2ca007f31' for the configuration:
E           [settings]
E           arch=x86_64
E           [settings_target]
E           arch=armv7
```

After
```
====================================== test session starts ===========================
platform linux -- Python 3.10.6, pytest-6.2.5, py-1.11.0, pluggy-1.2.0
rootdir: /mnt/c/Users/AAli/Desktop/workspace/conan, configfile: pytest.ini
plugins: xdist-3.3.1
collected 1 item

conans/test/integration/package_id/test_validate.py .                                                                                                                                                       [100%]

====================================== 1 passed in 1.14s ==============================
```


Problem statement:
[feature][2.0.9] settings_target accessible in the compatibility method
### settings_target in compatibility

Hello,

Currently compatibility method does not allow a tool to be made compatible across various target settings, I have tested a use case with the following patch and it seems to work. Please have a look, if this makes sense then I can also create a PR for it.

Changes at the following locations are made. Uploading a patch file isnt allowed here, so I am trying to explain here :) 

1. when a settings_target is added to the conan package it has to be done via package_id method. So when this is added then add this to both info and original_info objects
2. when cloning the info object, if settings_target are available then clone them as well
3. allow settings_target to be passed from compatibility method in conanfile.py 

```
diff --git a/conans/client/graph/compatibility.py b/conans/client/graph/compatibility.py
index 51de09ee9..fe7c4fa0a 100644
--- a/conans/client/graph/compatibility.py
+++ b/conans/client/graph/compatibility.py
@@ -99,6 +99,7 @@ class BinaryCompatibility:
             # use the compatible ones
             conanfile.info = c
             conanfile.settings = c.settings
+            conanfile.settings_target = c.settings_target
             conanfile.options = c.options
             run_validate_package_id(conanfile)
             pid = c.package_id()
@@ -123,4 +124,7 @@ class BinaryCompatibility:
                 if options:
                     compat_info.options.update(options_values=OrderedDict(options))
                 result.append(compat_info)
+                settings_target = elem.get("settings_target")
+                if settings_target:
+                    compat_info.settings_target.update_values(settings_target)
         return result
diff --git a/conans/client/graph/compute_pid.py b/conans/client/graph/compute_pid.py
index 8dc42beb6..0bad23c84 100644
--- a/conans/client/graph/compute_pid.py
+++ b/conans/client/graph/compute_pid.py
@@ -63,6 +63,10 @@ def compute_package_id(node, new_config):

     run_validate_package_id(conanfile)

+    if (conanfile.info.settings_target):
+        # settings_target has beed added to conan package via package_id api
+        conanfile.original_info.settings_target = conanfile.info.settings_target
+
     info = conanfile.info
     node.package_id = info.package_id()

diff --git a/conans/model/info.py b/conans/model/info.py
index 0c2de4634..a900d0e18 100644
--- a/conans/model/info.py
+++ b/conans/model/info.py
@@ -325,6 +325,7 @@ class ConanInfo:
         result.build_requires = self.build_requires.copy()
         result.python_requires = self.python_requires.copy()
         result.conf = self.conf.copy()
+        result.settings_target = self.settings_target.copy() if self.settings_target else None
         return result

     def dumps(self):
--
```


Thanks.
Adnan Ali

### Have you read the CONTRIBUTING guide?

- [ ] I've read the CONTRIBUTING guide

Hints from issue discussion:
Hi @adnan-ali1 

Thanks for your request.

It is true that at the moment the ``compatibility()`` is only exposing and managing the minimum ``settings`` + ``options``, but not other elements. We typically wait for more real world use cases and requests for it, to make sure the right thing is being added.

I agree it makes sense to add ``settings_target`` to the equation for the cases you expose. Yes, it would be great, as you already did most of the work, if you could submit a PR to ``release/2.0`` branch to include it, thanks very much for the offer to contribute!
Sure, it would be my pleasure to contribute. I will create a PR for this.

Commit message: Adding testcase for compatibility check

Signed-off-by: Adnan Ali <m_adnanali_1@hotmail.com>

Patch to review:
diff --git a/conans/client/graph/compatibility.py b/conans/client/graph/compatibility.py
index 44c96b87c05..1768da906da 100644
--- a/conans/client/graph/compatibility.py
+++ b/conans/client/graph/compatibility.py
@@ -96,6 +96,7 @@ def compatibles(self, conanfile):
             # use the compatible ones
             conanfile.info = c
             conanfile.settings = c.settings
+            conanfile.settings_target = c.settings_target
             conanfile.options = c.options
             run_validate_package_id(conanfile)
             pid = c.package_id()
@@ -120,4 +121,7 @@ def _compatible_infos(conanfile, compatibles):
                 if options:
                     compat_info.options.update(options_values=OrderedDict(options))
                 result.append(compat_info)
+                settings_target = elem.get("settings_target")
+                if settings_target:
+                    compat_info.settings_target.update_values(settings_target)
         return result
diff --git a/conans/client/graph/compute_pid.py b/conans/client/graph/compute_pid.py
index 8dc42beb680..0bad23c8414 100644
--- a/conans/client/graph/compute_pid.py
+++ b/conans/client/graph/compute_pid.py
@@ -63,6 +63,10 @@ def compute_package_id(node, new_config):
 
     run_validate_package_id(conanfile)
 
+    if (conanfile.info.settings_target):
+        # settings_target has beed added to conan package via package_id api
+        conanfile.original_info.settings_target = conanfile.info.settings_target
+
     info = conanfile.info
     node.package_id = info.package_id()
 
diff --git a/conans/model/info.py b/conans/model/info.py
index 0c2de46340b..a900d0e189d 100644
--- a/conans/model/info.py
+++ b/conans/model/info.py
@@ -325,6 +325,7 @@ def clone(self):
         result.build_requires = self.build_requires.copy()
         result.python_requires = self.python_requires.copy()
         result.conf = self.conf.copy()
+        result.settings_target = self.settings_target.copy() if self.settings_target else None
         return result
 
     def dumps(self):
diff --git a/conans/test/integration/package_id/test_validate.py b/conans/test/integration/package_id/test_validate.py
index f3496ddb20f..5b14d4d4fb8 100644
--- a/conans/test/integration/package_id/test_validate.py
+++ b/conans/test/integration/package_id/test_validate.py
@@ -660,3 +660,46 @@ def package_id(self):
         client.run(f"install app {settings} -s compiler.cppstd=11 --build=engine*",
                    assert_error=True)
         assert 'pkg/0.1: Invalid: I need at least cppstd=14 to be used' in client.out
+
+class TestCompatibleSettingsTarget:
+    """ aims to be a very close to real use case of tool being used across different settings_target
+    """
+    def test_tool_compatibility_across_different_archs(self):
+        client = TestClient()
+        """
+        test a tool which is compatible with multiple target architectures
+        """
+        tool_conanfile = textwrap.dedent("""
+            from conan import ConanFile
+
+            class Pkg(ConanFile):
+                settings = "arch"
+                def compatibility(self):
+                    if self.settings_target.arch == "armv7":
+                        return [{"settings_target": [("arch", "armv6")]}]
+                
+                def package_id(self):
+                    self.info.settings_target = self.settings_target
+                    for field in self.info.settings_target.fields:
+                        if field != "arch":
+                            self.info.settings_target.rm_safe(field)
+            """)
+        
+        client.save({"conanfile.py": tool_conanfile})
+        package_id = "44fbc30cf7c3dc16d6ebd6e6d2ef26dcba8e2712"
+        client.run("create . --name=tool --version=0.1 -s os=Linux -s:h arch=armv6 --build-require")
+        assert f"tool/0.1: Package '{package_id}' created" in client.out
+
+        app_conanfile = textwrap.dedent("""
+            from conan import ConanFile
+
+            class Pkg(ConanFile):
+                settings = "arch"
+                def requirements(self):
+                    self.tool_requires("tool/0.1")
+            """)
+
+        client.run("create . --name=tool --version=0.1 -s os=Linux -s:h arch=armv6 --build-require")
+        client.save({"conanfile.py": app_conanfile})
+        client.run("create . --name=app --version=0.1 -s os=Linux -s:h arch=armv7")
+        assert f"Using compatible package '{package_id}'"


Code review comments:
diff_hunk: @@ -63,6 +63,10 @@ def compute_package_id(node, new_config):
 
     run_validate_package_id(conanfile)
 
+    if (conanfile.info.settings_target):
path: conans/client/graph/compute_pid.py
text: @user1:
```suggestion
    if conanfile.info.settings_target:
```
Parenthesis unnecessary