Repository: conan-io/conan
Pull request #16135 (2024-04-23 18:28:30)

Disallow `self.cpp_info` access in `validate_build()` method
Changelog: Bugfix: Disallow `self.cpp_info` access in `validate_build()` method
Docs: Omit

Ensures symmetry with `validate()` method


Patch:
diff --git a/conan/api/subapi/local.py b/conan/api/subapi/local.py
index eaaa3a170..88ae8a8fe 100644
--- a/conan/api/subapi/local.py
+++ b/conan/api/subapi/local.py
@@ -1,5 +1,6 @@
 import os
 
+from conan.api.output import ConanOutput
 from conan.cli import make_abs_path
 from conan.internal.conan_app import ConanApp
 from conans.client.cache.editable import EditablePackages
@@ -60,7 +61,8 @@ class LocalAPI:
 
     def editable_remove(self, path=None, requires=None, cwd=None):
         if path:
-            path = self._conan_api.local.get_conanfile_path(path, cwd, py=True)
+            path = make_abs_path(path, cwd)
+            path = os.path.join(path, "conanfile.py")
         return self.editable_packages.remove(path, requires)
 
     def editable_list(self):
diff --git a/conan/cli/commands/graph.py b/conan/cli/commands/graph.py
index 3f4f8ada7..bf591c461 100644
--- a/conan/cli/commands/graph.py
+++ b/conan/cli/commands/graph.py
@@ -14,9 +14,8 @@ from conan.errors import ConanException
 from conan.internal.deploy import do_deploys
 from conans.client.graph.graph import BINARY_MISSING
 from conans.client.graph.install_graph import InstallGraph
-from conans.errors import ConanConnectionError, NotFoundException
+from conans.errors import NotFoundException
 from conans.model.recipe_ref import ref_matches, RecipeReference
-from conans.model.version_range import VersionRange
 
 
 def explain_formatter_text(data):
@@ -214,10 +213,7 @@ def graph_info(conan_api, parser, subparser, *args):
                                                          remotes, args.update,
                                                          check_updates=args.check_updates)
     print_graph_basic(deps_graph)
-    if deps_graph.error:
-        ConanOutput().info("Graph error", Color.BRIGHT_RED)
-        ConanOutput().info("    {}".format(deps_graph.error), Color.BRIGHT_RED)
-    else:
+    if not deps_graph.error:
         conan_api.graph.analyze_binaries(deps_graph, args.build, remotes=remotes, update=args.update,
                                          lockfile=lockfile)
         print_graph_packages(deps_graph)
diff --git a/conan/cli/commands/new.py b/conan/cli/commands/new.py
index 1e93fe7fe..f09ebd275 100644
--- a/conan/cli/commands/new.py
+++ b/conan/cli/commands/new.py
@@ -18,7 +18,7 @@ def new(conan_api, parser, *args):
                         "either a predefined built-in or a user-provided one. "
                         "Available built-in templates: basic, cmake_lib, cmake_exe, "
                         "meson_lib, meson_exe, msbuild_lib, msbuild_exe, bazel_lib, bazel_exe, "
-                        "autotools_lib, autotools_exe, local_recipes_index"
+                        "autotools_lib, autotools_exe, local_recipes_index. "
                         "E.g. 'conan new cmake_lib -d name=hello -d version=0.1'. "
                         "You can define your own templates too by inputting an absolute path "
                         "as your template, or a path relative to your conan home folder."
diff --git a/conans/client/graph/compute_pid.py b/conans/client/graph/compute_pid.py
index d86a146f6..60842cefa 100644
--- a/conans/client/graph/compute_pid.py
+++ b/conans/client/graph/compute_pid.py
@@ -57,11 +57,12 @@ def compute_package_id(node, new_config, config_version):
 
     if hasattr(conanfile, "validate_build"):
         with conanfile_exception_formatter(conanfile, "validate_build"):
-            try:
-                conanfile.validate_build()
-            except ConanInvalidConfiguration as e:
-                # This 'cant_build' will be ignored if we don't have to build the node.
-                node.cant_build = str(e)
+            with conanfile_remove_attr(conanfile, ['cpp_info'], "validate_build"):
+                try:
+                    conanfile.validate_build()
+                except ConanInvalidConfiguration as e:
+                    # This 'cant_build' will be ignored if we don't have to build the node.
+                    node.cant_build = str(e)
 
     run_validate_package_id(conanfile)
 
diff --git a/conans/client/graph/graph_error.py b/conans/client/graph/graph_error.py
index b29e84fb2..cf2497e35 100644
--- a/conans/client/graph/graph_error.py
+++ b/conans/client/graph/graph_error.py
@@ -25,19 +25,15 @@ class GraphConflictError(GraphError):
                             "require": self.require.serialize()}}
 
     def __str__(self):
-        if self.node.ref is not None and self.base_previous.ref is not None:
-            return f"Version conflict: {self.node.ref}->{self.require.ref}, " \
-                   f"{self.base_previous.ref}->{self.prev_require.ref}."
-        else:
-            conflicting_node = self.node.ref or self.base_previous.ref
-            conflicting_node_msg = ""
-            if conflicting_node is not None:
-                conflicting_node_msg = f"\nConflict originates from {conflicting_node}\n"
-            return f"Version conflict: " \
-                   f"Conflict between {self.require.ref} and {self.prev_require.ref} in the graph." \
-                   f"{conflicting_node_msg}" \
-                   f"\nRun conan graph info with your recipe and add --format=html " \
-                   f"to inspect the graph errors in an easier to visualize way."
+        conflicting_node = self.node.ref or self.base_previous.ref
+        conflicting_node_msg = ""
+        if conflicting_node is not None:
+            conflicting_node_msg = f"\nConflict originates from {conflicting_node}\n"
+        return f"Version conflict: " \
+               f"Conflict between {self.require.ref} and {self.prev_require.ref} in the graph." \
+               f"{conflicting_node_msg}" \
+               f"\nRun 'conan graph info ... --format=html > graph.html' " \
+               f"and open 'graph.html' to inspect the conflict graphically."
 
 
 class GraphLoopError(GraphError):
diff --git a/conans/paths/__init__.py b/conans/paths/__init__.py
index dc17a7093..41e4dd953 100644
--- a/conans/paths/__init__.py
+++ b/conans/paths/__init__.py
@@ -3,6 +3,8 @@ import os
 import platform
 from pathlib import Path
 
+from conans.errors import ConanException
+
 if platform.system() == "Windows":
     from conans.util.windows import conan_expand_user
 else:
@@ -46,7 +48,7 @@ def get_conan_user_home():
     else:  # Do an expansion, just in case the user is using ~/something/here
         user_home = conan_expand_user(user_home)
     if not os.path.isabs(user_home):
-        raise Exception("Invalid CONAN_HOME value '%s', "
+        raise ConanException("Invalid CONAN_HOME value '%s', "
                         "please specify an absolute or path starting with ~/ "
                         "(relative to user home)" % user_home)
     return user_home


Test patch:
diff --git a/conans/test/integration/command/info/test_graph_info_graphical.py b/conans/test/integration/command/info/test_graph_info_graphical.py
index 29dfe14b4..6637f2299 100644
--- a/conans/test/integration/command/info/test_graph_info_graphical.py
+++ b/conans/test/integration/command/info/test_graph_info_graphical.py
@@ -131,7 +131,7 @@ def test_graph_info_html_error_reporting_output():
 
     tc.run("graph info --requires=math/1.0 --requires=ui/1.0 --format=html", assert_error=True,
            redirect_stdout="graph.html")
-    assert "ERROR: Version conflict:" in tc.out # check that it doesn't crash
+    assert "ERROR: Version conflict:" in tc.out  # check that it doesn't crash
 
     # change order,  just in case
     tc.run("graph info --requires=ui/1.0 --requires=math/1.0 --format=html", assert_error=True,
@@ -144,3 +144,20 @@ def test_graph_info_html_error_reporting_output():
     assert "ERROR: Version conflict:" in tc.out  # check that it doesn't crash
     # Check manually
     # tc.run_command(f"{tc.current_folder}/graph.html")
+
+
+def test_graph_conflict_diamond():
+    c = TestClient()
+    c.save({"math/conanfile.py": GenConanfile("math"),
+            "engine/conanfile.py": GenConanfile("engine", "1.0").with_requires("math/1.0"),
+            "ai/conanfile.py": GenConanfile("ai", "1.0").with_requires("math/1.0.1"),
+            "game/conanfile.py": GenConanfile("game", "1.0").with_requires("engine/1.0", "ai/1.0"),
+            })
+    c.run("create math --version=1.0")
+    c.run("create math --version=1.0.1")
+    c.run("create math --version=1.0.2")
+    c.run("create engine")
+    c.run("create ai")
+    c.run("graph info game --format=html", assert_error=True, redirect_stdout="graph.html")
+    # check that it doesn't crash
+    assert "ERROR: Version conflict: Conflict between math/1.0.1 and math/1.0 in the graph." in c.out
diff --git a/conans/test/integration/editable/editable_remove_test.py b/conans/test/integration/editable/editable_remove_test.py
index e28164f68..837e17601 100644
--- a/conans/test/integration/editable/editable_remove_test.py
+++ b/conans/test/integration/editable/editable_remove_test.py
@@ -48,8 +48,9 @@ class TestRemoveEditablePackageTest:
         c.save({'pkg/conanfile.py': GenConanfile()})
         c.run('editable add pkg --name=lib --version=version')
         shutil.rmtree(os.path.join(c.current_folder, "pkg"))
-        c.run("editable remove pkg", assert_error=True)
-        c.run("editable remove --refs=lib/version")
+        # https://github.com/conan-io/conan/issues/16164
+        # Making it possible, repeated issue
+        c.run("editable remove pkg")
         assert "Removed editable 'lib/version'" in c.out
         c.run("editable list")
         assert "lib" not in c.out
diff --git a/conans/test/integration/graph/conflict_diamond_test.py b/conans/test/integration/graph/conflict_diamond_test.py
index f55336fda..462bfecdd 100644
--- a/conans/test/integration/graph/conflict_diamond_test.py
+++ b/conans/test/integration/graph/conflict_diamond_test.py
@@ -23,7 +23,7 @@ class TestConflictDiamondTest:
         c.run("create engine")
         c.run("create ai")
         c.run("install game", assert_error=True)
-        assert "Version conflict: ai/1.0->math/1.0.1, game/1.0->math/1.0" in c.out
+        assert "Version conflict: Conflict between math/1.0.1 and math/1.0 in the graph" in c.out
         # This shouldnt error, so we are able to diagnose our dependency graph
         # The UX still need to be improved, but this is start
         c.run("graph info game --filter=requires", assert_error=True)
diff --git a/conans/test/integration/lockfile/test_graph_overrides.py b/conans/test/integration/lockfile/test_graph_overrides.py
index bae9f6817..6c64ecb1a 100644
--- a/conans/test/integration/lockfile/test_graph_overrides.py
+++ b/conans/test/integration/lockfile/test_graph_overrides.py
@@ -315,7 +315,7 @@ def test_introduced_conflict(override, force):
             })
 
     c.run("graph info pkgd --lockfile=pkgd/conan.lock --lockfile-partial", assert_error=True)
-    assert "Version conflict: pkgc/0.1->pkga/[>=0.2 <0.3], pkgd/0.1->pkga/0.1" in c.out
+    assert "Version conflict: Conflict between pkga/[>=0.2 <0.3] and pkga/0.1 in the graph" in c.out
     # Resolve the conflict with an override or force
     c.save({"pkgd/conanfile.py": GenConanfile("pkgd", "0.1").with_requirement("pkgb/0.1")
                                                             .with_requirement("pkgc/0.1")
diff --git a/conans/test/unittests/model/version/test_version_range.py b/conans/test/unittests/model/version/test_version_range.py
index 256d4420e..7f82d625c 100644
--- a/conans/test/unittests/model/version/test_version_range.py
+++ b/conans/test/unittests/model/version/test_version_range.py
@@ -35,6 +35,7 @@ values = [
     ['>1- <2.0 || ^3.2 ',  [[['>', '1-'], ['<', '2.0-']], [['>=', '3.2-'], ['<', '4.0-']]],
                            ["1.0", "1.2", "3.3"],  ["1-pre.1", "1.5-a1", "3.3-a1"]],
     ['^1.1.2',  [[['>=', '1.1.2-'], ['<', '2.0.0-']]], ["1.2.3"],  ["1.2.0-alpha1", "2.0.0-alpha1"]],
+    ['^1.0.0, include_prerelease=True',  [[['>=', '1.0.0-'], ['<', '2.0.0-']]], ["1.2.3", "1.2.0-alpha1"],  ["2.0.0-alpha1"]],
     ['~1.1.2',  [[['>=', '1.1.2-'], ['<', '1.2.0-']]], ["1.1.3"],  ["1.1.3-alpha1", "1.2.0-alpha1"]],
     ['>=2.0-pre.0 <3', [[['>=', '2.0-pre.0'], ['<', '3-']]], ["2.1"], ["2.0-pre.1", "2.0-alpha.1"]],
     # Build metadata