Repository: conan-io/conan

Issue:
[bug] AttributeError: 'PackageEditableLayout' object has no attribute 'package_lock'
Hello,

I have a quiet large dependency tree with a couple of own conan packages. Lets call two of them foo and bar where foo depends on bar. To reproduce the entire build I use a lockfile. When I put foo and bar in editable mode ans call conan install I end up with the following error:
AttributeError: 'PackageEditableLayout' object has no attribute 'package_lock'
And the following Traceback (most recent call last):
  File "r:\tmp\.venv\lib\site-packages\conans\client\command.py", line 2220, in run
    method(args[0][1:])
  File "r:\tmp\.venv\lib\site-packages\conans\client\command.py", line 546, in install
    info = self._conan.install_reference(ref,
  File "r:\tmp\.venv\lib\site-packages\conans\client\conan_api.py", line 93, in wrapper
    return f(api, *args, **kwargs)
  File "r:\tmp\.venv\lib\site-packages\conans\client\conan_api.py", line 558, in install_reference
    deps_install(self.app, ref_or_path=reference, install_folder=install_folder,
  File "r:\tmp\.venv\lib\site-packages\conans\client\manager.py", line 82, in deps_install
    installer.install(deps_graph, remotes, build_modes, update, profile_host, profile_build,
  File "r:\tmp\.venv\lib\site-packages\conans\client\installer.py", line 316, in install
    self._build(nodes_by_level, keep_build, root_node, profile_host, profile_build,
  File "r:\tmp\.venv\lib\site-packages\conans\client\installer.py", line 451, in _build
    self._handle_node_cache(node, keep_build, processed_package_refs, remotes)
  File "r:\tmp\.venv\lib\site-packages\conans\client\installer.py", line 533, in _handle_node_cache
    with layout.package_lock(pref):

### Environment Details (include every applicable attribute)
  * Operating System+version: Windows & Linux
  * Conan version: 1.41.0
  * Python version: 3.9

I debugged the code an found the following code which causes the problem (/conans/client/installer.py LL 437.):
```
if node.binary == BINARY_EDITABLE:
    self._handle_node_editable(node, profile_host, profile_build, graph_lock)
    # Need a temporary package revision for package_revision_mode
    # Cannot be PREV_UNKNOWN otherwise the consumers can't compute their packageID
    node.prev = "editable"
else:
    if node.binary == BINARY_SKIP:  # Privates not necessary
        continue
    assert ref.revision is not None, "Installer should receive RREV always"
    if node.binary == BINARY_UNKNOWN:
        self._binaries_analyzer.reevaluate_node(node, remotes, build_mode, update)
        if node.binary == BINARY_MISSING:
            self._raise_missing([node])
    _handle_system_requirements(conan_file, node.pref, self._cache, output)
    self._handle_node_cache(node, keep_build, processed_package_refs, remotes)
```
when I reach that code with the editable node "foo" then _node.binary_ is set to _BINARY_UNKNOWN_. After the _reevaluate_node_ call the _node.binary_ is set to _BINARY_EDITABLE_ and is not correctly handled by the _handle_node_cache_ method.

I would like to suggest the following fix:
```
if node.binary == BINARY_UNKNOWN:
    self._binaries_analyzer.reevaluate_node(node, remotes, build_mode, update)
    if node.binary == BINARY_MISSING:
        self._raise_missing([node])
if node.binary == BINARY_EDITABLE:
    self._handle_node_editable(node, profile_host, profile_build, graph_lock)
    # Need a temporary package revision for package_revision_mode
    # Cannot be PREV_UNKNOWN otherwise the consumers can't compute their packageID
    node.prev = "editable"
else:
    if node.binary == BINARY_SKIP:  # Privates not necessary
        continue
    assert ref.revision is not None, "Installer should receive RREV always"
    _handle_system_requirements(conan_file, node.pref, self._cache, output)
    self._handle_node_cache(node, keep_build, processed_package_refs, remotes)
```


Discussion:
Hi @SamsonBox 

Yes, it seems that the ``package_revision_mode``, which is the one that is creating the ``BINARY_UNKNOWN`` is at the root of the issue, together with the way the lockfiles were designed was around strong revision locking, and that does not cover the editable scenario, because packages in editable mode no longer have a valid revision that can be validated against the revision in the lockfile.

We are proposing to the Tribe (https://conan.io/tribe.html) to remove the ``package_revision_mode`` for 2.0, and your issue is one added evidence that this mode is complicated and problematic. Please read https://github.com/conan-io/tribe/pull/30, specially the "motivation" section, the goal would be to have it removed for next alpha-4 (alpha-3 will probably be this week, we have already released alpha-2).

Hi @memsharded,

Thank you for your quick answer. I'm not quiet sure how to understand your comment. Do you mean it's not legal to use lockfiles in combination with editable packages, and there is no need to fix the bug? Or do you want to say the problem would be gone when the package_revision_mode is removed?
> Do you mean it's not legal to use lockfiles in combination with editable packages, and there is no need to fix the bug?

Not that it is not needed to fix it, mostly that it was not a considered use case, and then we need to investigate and think a little bit if this would make sense to support or not. Because if a lockfile locks "pkga/1.0#rev1", shouldn't you use exactly that revision1? And that is not true if the package is in editable mode. So even if the problem is gone when package_revision_mode is removed, surely there are implications here that we haven't considered yet and need some attention.

> Or do you want to say the problem would be gone when the package_revision_mode is removed?

It seems that this specific bug will be gone if not using ``package_revision_mode``, indeed. That does not exclude checking the above, but in any case it was important to let you know that it is likely that this mode will be gone, so you could also have this in mind while moving forward.
Thank you for the clarification. May be I should spend some words on the use case. We have a main software component which depends on a couple of Conan packages. During the install step we produce a lockfile to be able to reproduce the entire build. To debug one or more packages we bring these packages in editable mode and use the lockfile to ensure on the one hand, that the remaining packages stay unchanged. On the other hand the lockfile ensures that the package is build in the same environment (profile, settings etc.).

Resolving patch:
diff --git a/conans/client/installer.py b/conans/client/installer.py
--- a/conans/client/installer.py
+++ b/conans/client/installer.py
@@ -447,8 +447,14 @@ def _build(self, nodes_by_level, keep_build, root_node, profile_host, profile_bu
                         self._binaries_analyzer.reevaluate_node(node, remotes, build_mode, update)
                         if node.binary == BINARY_MISSING:
                             self._raise_missing([node])
-                    _handle_system_requirements(conan_file, node.pref, self._cache, output)
-                    self._handle_node_cache(node, keep_build, processed_package_refs, remotes)
+                    if node.binary == BINARY_EDITABLE:
+                        self._handle_node_editable(node, profile_host, profile_build, graph_lock)
+                        # Need a temporary package revision for package_revision_mode
+                        # Cannot be PREV_UNKNOWN otherwise the consumers can't compute their packageID
+                        node.prev = "editable"
+                    else:
+                        _handle_system_requirements(conan_file, node.pref, self._cache, output)
+                        self._handle_node_cache(node, keep_build, processed_package_refs, remotes)
 
         # Finally, propagate information to root node (ref=None)
         self._propagate_info(root_node, using_build_profile)


Test patch: