Repository: conan-io/conan (Python)

Issue:
[bug] Compatible packages are locked incorrectly
### Environment Details (include every applicable attribute)
  * Operating System+version: Linux
  * Conan version: develop, 1.33.1, 1.36.0
  * Python version: 3.9

### Steps to reproduce (Include if Applicable)
I've created a test for conan which demonstrate the issue: 
https://github.com/conan-io/conan/compare/develop...ytimenkov:compatible-lock-demo 

1. Create a package with compatible setting.
2. In consumer file create lock `conan lock create` for "compatible" package (one with missing binary). During this conan writes "Main binary package '1fba023b99af36dcb917d3d3ea0ab91fd834c725' missing. Using compatible package '06848324fc74da256b5bd6df5e66fe95399a1494'" (or similar)
3. When I try to consume this lock, for example with `conan info --lockfile` Conan fails with message: "package-id '1fba023b99af36dcb917d3d3ea0ab91fd834c725' doesn't match the locked one '06848324fc74da256b5bd6df5e66fe95399a1494'"


Discussion:
Thanks very much for the test, that was super useful to reproduce.

I have proposed this fix in https://github.com/conan-io/conan/pull/9008, it has some (small) risk, so should be carefully reviewed before merging, lets see how it goes.
Merged https://github.com/conan-io/conan/pull/9008, will be in Conan 1.37 (next Monday/Tuesday hopefully)



Patch:
diff --git a/conans/client/graph/graph_binaries.py b/conans/client/graph/graph_binaries.py
index f9cb92abe2a..200f94b68dc 100644
--- a/conans/client/graph/graph_binaries.py
+++ b/conans/client/graph/graph_binaries.py
@@ -157,18 +157,27 @@ def _evaluate_node(self, node, build_mode, update, remotes):
 
         # If it has lock
         locked = node.graph_lock_node
-        if locked and locked.package_id:  # if package_id = None, nothing to lock here
-            # First we update the package_id, just in case there are differences or something
-            if locked.package_id != node.package_id and locked.package_id != PACKAGE_ID_UNKNOWN:
-                raise ConanException("'%s' package-id '%s' doesn't match the locked one '%s'"
-                                     % (repr(locked.ref), node.package_id, locked.package_id))
-            locked.package_id = node.package_id  # necessary for PACKAGE_ID_UNKNOWN
+        if locked and locked.package_id and locked.package_id != PACKAGE_ID_UNKNOWN:
             pref = PackageReference(locked.ref, locked.package_id, locked.prev)  # Keep locked PREV
             self._process_node(node, pref, build_mode, update, remotes)
             if node.binary == BINARY_MISSING and build_mode.allowed(node.conanfile):
                 node.binary = BINARY_BUILD
             if node.binary == BINARY_BUILD:
                 locked.unlock_prev()
+
+            if node.package_id != locked.package_id:  # It was a compatible package
+                # https://github.com/conan-io/conan/issues/9002
+                # We need to iterate to search the compatible combination
+                for compatible_package in node.conanfile.compatible_packages:
+                    comp_package_id = compatible_package.package_id()
+                    if comp_package_id == locked.package_id:
+                        node._package_id = locked.package_id  # FIXME: Ugly definition of private
+                        node.conanfile.settings.values = compatible_package.settings
+                        node.conanfile.options.values = compatible_package.options
+                        break
+                else:
+                    raise ConanException("'%s' package-id '%s' doesn't match the locked one '%s'"
+                                         % (repr(locked.ref), node.package_id, locked.package_id))
         else:
             assert node.prev is None, "Non locked node shouldn't have PREV in evaluate_node"
             assert node.binary is None, "Node.binary should be None if not locked"


Test patch:
diff --git a/conans/test/integration/package_id/compatible_test.py b/conans/test/integration/package_id/compatible_test.py
index e5b4fa9f655..061e1b3451c 100644
--- a/conans/test/integration/package_id/compatible_test.py
+++ b/conans/test/integration/package_id/compatible_test.py
@@ -494,6 +494,35 @@ def package_id(self):
                       client.out)
         self.assertIn("pkg/0.1@user/testing: Already installed!", client.out)
 
+    def test_compatible_lockfile(self):
+        # https://github.com/conan-io/conan/issues/9002
+        client = TestClient()
+        conanfile = textwrap.dedent("""
+            from conans import ConanFile
+            class Pkg(ConanFile):
+                settings = "os"
+                def package_id(self):
+                    if self.settings.os == "Windows":
+                        compatible_pkg = self.info.clone()
+                        compatible_pkg.settings.os = "Linux"
+                        self.compatible_packages.append(compatible_pkg)
+                def package_info(self):
+                    self.output.info("PackageInfo!: OS: %s!" % self.settings.os)
+            """)
+
+        client.save({"conanfile.py": conanfile})
+        client.run("create . pkg/0.1@user/stable -s os=Linux")
+        self.assertIn("pkg/0.1@user/stable: PackageInfo!: OS: Linux!", client.out)
+        self.assertIn("pkg/0.1@user/stable: Package 'cb054d0b3e1ca595dc66bc2339d40f1f8f04ab31'"
+                      " created", client.out)
+
+        client.save({"conanfile.py": GenConanfile().with_require("pkg/0.1@user/stable")})
+        client.run("lock create conanfile.py -s os=Windows --lockfile-out=deps.lock")
+        client.run("install conanfile.py --lockfile=deps.lock")
+        self.assertIn("pkg/0.1@user/stable: PackageInfo!: OS: Linux!", client.out)
+        self.assertIn("pkg/0.1@user/stable:cb054d0b3e1ca595dc66bc2339d40f1f8f04ab31", client.out)
+        self.assertIn("pkg/0.1@user/stable: Already installed!", client.out)
+
 
 def test_msvc_visual_incompatible():
     conanfile = GenConanfile().with_settings("os", "compiler", "build_type", "arch")