Repository: conan-io/conan

Issue:
[bug] export_sources fails if a local repo does not contain the recipe revision being exported
### Describe the bug

In a setup where a local repo has higher priority than a remote repo, and a particular recipe revision only exists in the remote repo, operations that involve export_sources for this recipe will fail rather than downloading the sources from the remote.

### How to reproduce it

* Add an empty folder as a local repo at index 0, and ensure conancenter is enabled at index 1.
* Run `conan create` with a conanfile that requires any package from the conancenter repo.

Discussion:
CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/conan-io/conan?pullRequest=16776) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/conan-io/conan?pullRequest=16776) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/conan-io/conan?pullRequest=16776) it.</sub>
cynix: Hi @memsharded, thanks for reviewing and cleaning up the test. I also thought it's weird for export_sources to access files outside the recipe dir, but the test was mostly a copy-paste of an existing test in the same file and I didn't want to change it too much 😅 apologies for the laziness. 
memsharded: > apologies for the laziness.

No need to apologize 🙂 , and no laziness at all. You did a great job providing a good unit test that covers the case, fails without the fix and works with the fix, that is fantastic. It is our job as maintainers to think about other aspects like the cost of running the full suite, etc. 

Patch:
diff --git a/conan/cli/commands/graph.py b/conan/cli/commands/graph.py
index 690be8869..a929f39f1 100644
--- a/conan/cli/commands/graph.py
+++ b/conan/cli/commands/graph.py
@@ -133,7 +133,6 @@ def graph_build_order(conan_api, parser, subparser, *args):
             "conan_error": install_graph.get_errors()}
 
 
-
 @conan_subcommand(formatters={"text": cli_build_order, "json": json_build_order,
                               "html": format_build_order_html})
 def graph_build_order_merge(conan_api, parser, subparser, *args):
diff --git a/conan/tools/microsoft/visual.py b/conan/tools/microsoft/visual.py
index 0455ae45e..ab4a71293 100644
--- a/conan/tools/microsoft/visual.py
+++ b/conan/tools/microsoft/visual.py
@@ -367,11 +367,12 @@ def is_msvc_static_runtime(conanfile):
 
 def msvs_toolset(conanfile):
     """
-    Returns the corresponding platform toolset based on the compiler of the given conanfile.
+    Returns the corresponding platform toolset based on the compiler setting.
     In case no toolset is configured in the profile, it will return a toolset based on the
     compiler version, otherwise, it will return the toolset from the profile.
     When there is no compiler version neither toolset configured, it will return None
-    It supports Visual Studio, msvc and Intel.
+    It supports msvc, intel-cc and clang compilers. For clang, is assumes the ClangCl toolset,
+    as provided by the Visual Studio installer.
 
     :param conanfile: Conanfile instance to access settings.compiler
     :return: A toolset when compiler.version is valid or compiler.toolset is configured. Otherwise, None.
@@ -384,5 +385,7 @@ def msvs_toolset(conanfile):
         if subs_toolset:
             return subs_toolset
         return msvc_version_to_toolset_version(compiler_version)
+    if compiler == "clang":
+        return "ClangCl"
     if compiler == "intel-cc":
         return IntelCC(conanfile).ms_toolset
diff --git a/conans/client/graph/install_graph.py b/conans/client/graph/install_graph.py
index 312ba57e7..dbba4d845 100644
--- a/conans/client/graph/install_graph.py
+++ b/conans/client/graph/install_graph.py
@@ -485,14 +485,9 @@ class InstallGraph:
             errors.append("There are some error(s) in the graph:")
         if invalid:
             for package in invalid:
-                node = package.nodes[0]
-                if node.cant_build and node.should_build:
-                    binary, reason = "Cannot build for this configuration", node.cant_build
-                else:
-                    binary, reason = "Invalid", node.conanfile.info.invalid
-                errors.append(f"{tab}- {node.ref}: {binary}: {reason}")
+                errors.append(f"{tab}- {package.pref}: Invalid configuration")
         if missing:
-            missing_prefs = set(n.nodes[0].pref for n in missing)  # avoid duplicated
+            missing_prefs = set(n.pref for n in missing)  # avoid duplicated
             for pref in list(sorted([str(pref) for pref in missing_prefs])):
                 errors.append(f"{tab}- {pref}: Missing binary")
         if errors:
diff --git a/conans/client/rest_client_local_recipe_index.py b/conans/client/rest_client_local_recipe_index.py
index 1da0de5fc..e806fe3fe 100644
--- a/conans/client/rest_client_local_recipe_index.py
+++ b/conans/client/rest_client_local_recipe_index.py
@@ -11,7 +11,8 @@ from conan.api.output import ConanOutput
 from conan.internal.cache.home_paths import HomePaths
 from conans.client.cmd.export import cmd_export
 from conans.client.loader import ConanFileLoader
-from conans.errors import ConanException, PackageNotFoundException, RecipeNotFoundException
+from conans.errors import ConanException, PackageNotFoundException, RecipeNotFoundException, \
+    ConanReferenceDoesNotExistInDB, NotFoundException
 from conans.model.conf import ConfDefinition
 from conans.model.recipe_ref import RecipeReference
 from conans.util.files import load, save, rmdir, copytree_compat
@@ -76,7 +77,12 @@ class RestApiClientLocalRecipesIndex:
         return self._copy_files(export_folder, dest_folder)
 
     def get_recipe_sources(self, ref, dest_folder):
-        export_sources = self._app.cache.recipe_layout(ref).export_sources()
+        try:
+            export_sources = self._app.cache.recipe_layout(ref).export_sources()
+        except ConanReferenceDoesNotExistInDB as e:
+            # This can happen when there a local-recipes-index is being queried for sources it
+            # doesn't contain
+            raise NotFoundException(str(e))
         return self._copy_files(export_sources, dest_folder)
 
     def get_package(self, pref, dest_folder, metadata, only_metadata):
diff --git a/conans/requirements.txt b/conans/requirements.txt
index 165f71c65..2e274724e 100644
--- a/conans/requirements.txt
+++ b/conans/requirements.txt
@@ -2,7 +2,7 @@ requests>=2.25, <3.0.0
 urllib3>=1.26.6, <1.27
 colorama>=0.4.3, <0.5.0
 PyYAML>=6.0, <7.0
-patch-ng>=1.17.4, <1.18
+patch-ng>=1.18.0, <1.19
 fasteners>=0.15
 distro>=1.4.0, <=1.8.0; platform_system == 'Linux' or platform_system == 'FreeBSD'
 Jinja2>=3.0, <4.0.0


Test patch:
diff --git a/test/functional/test_local_recipes_index.py b/test/functional/test_local_recipes_index.py
index fd01a7963..2a3df3a07 100644
--- a/test/functional/test_local_recipes_index.py
+++ b/test/functional/test_local_recipes_index.py
@@ -118,3 +118,35 @@ class TestInRepo:
         # Finally lets remove the remote, check that the clone is cleared
         c.run('remote remove local')
         assert "Removing temporary files for 'local' local-recipes-index remote" in c.out
+
+    def test_not_found(self):
+        """testing that the correct exception is raised when a recipe is not found
+        """
+        repo1_folder = temp_folder()
+        repo2_folder = temp_folder()
+        config_yml = textwrap.dedent("""\
+            versions:
+              "0.1":
+                folder: all
+            """)
+        conanfile = textwrap.dedent("""\
+            import os
+            from conan import ConanFile
+            from conan.tools.files import copy
+
+            class PkgRecipe(ConanFile):
+                name = "pkg"
+
+                def export_sources(self):
+                    copy(self, "*", src=self.recipe_folder, dst=self.export_sources_folder)
+            """)
+
+        save_files(repo2_folder, {"recipes/pkg/config.yml": config_yml,
+                                  "recipes/pkg/all/conanfile.py": conanfile,
+                                  "recipes/pkg/all/pkg.h": gen_function_h(name="pkg")})
+
+        c = TestClient()
+        c.run(f"remote add local1 '{repo1_folder}'")
+        c.run(f"remote add local2 '{repo2_folder}'")
+        c.run("install --requires=pkg/0.1 --build=missing")
+        assert "Install finished successfully" in c.out
diff --git a/test/functional/toolchains/cmake/test_cmake_toolchain.py b/test/functional/toolchains/cmake/test_cmake_toolchain.py
index b64c5c791..ce014b821 100644
--- a/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -2016,3 +2016,22 @@ def test_cmake_toolchain_ninja_multi_config():
     assert 'DEFINE conan_test_answer=456!' in c.out
     assert 'DEFINE conan_test_other=abc!' in c.out
     assert 'DEFINE conan_test_complex="1 2"!' in c.out
+
+
+@pytest.mark.tool("cmake")
+@pytest.mark.skipif(platform.system() != "Darwin", reason="Only needs to run once, no need for extra platforms")
+def test_cxx_version_not_overriden_if_hardcoded():
+    """Any C++ standard set in the CMakeLists.txt will have priority even if the
+    compiler.cppstd is set in the profile"""
+    tc = TestClient()
+    tc.run("new cmake_exe -dname=foo -dversion=1.0")
+
+    cml_contents = tc.load("CMakeLists.txt")
+    cml_contents = cml_contents.replace("project(foo CXX)\n", "project(foo CXX)\nset(CMAKE_CXX_STANDARD 17)\n")
+    tc.save({"CMakeLists.txt": cml_contents})
+
+    # The compiler.cppstd will not override the CXX_STANDARD variable
+    tc.run("create . -s=compiler.cppstd=11")
+    assert "Conan toolchain: C++ Standard 11 with extensions OFF" in tc.out
+    # Conan says yes! But alas, the compiled code is C++17
+    assert "foo/1.0: __cplusplus2017" in tc.out
diff --git a/test/integration/command_v2/test_info_build_order.py b/test/integration/command_v2/test_info_build_order.py
index 24ea16427..f71d51ba9 100644
--- a/test/integration/command_v2/test_info_build_order.py
+++ b/test/integration/command_v2/test_info_build_order.py
@@ -717,3 +717,30 @@ class TestBuildOrderReduce:
         # different order
         c.run(f"graph build-order-merge --file=bo3.json --file=bo2.json", assert_error=True)
         assert "ERROR: Cannot merge build-orders of configuration!=recipe" in c.out
+
+    def test_merge_missing_error(self):
+        tc = TestClient(light=True)
+        tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0")})
+        tc.run("export dep")
+        tc.run("graph build-order --order=recipe --requires=dep/1.0 --format=json", assert_error=True, redirect_stdout="order.json")
+        tc.run("graph build-order-merge --file=order.json --file=order.json --format=json", assert_error=True)
+        assert "dep/1.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Missing binary" in tc.out
+        assert "IndexError: list index out of range" not in tc.out
+
+    def test_merge_invalid_error(self):
+        tc = TestClient(light=True)
+        conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        from conan.errors import ConanInvalidConfiguration
+        class Pkg(ConanFile):
+            name = "dep"
+            version = "1.0"
+            def validate(self):
+                raise ConanInvalidConfiguration("This configuration is not valid")
+        """)
+        tc.save({"dep/conanfile.py": conanfile})
+        tc.run("export dep")
+        tc.run("graph build-order --order=recipe --requires=dep/1.0 --format=json", assert_error=True, redirect_stdout="order.json")
+        tc.run("graph build-order-merge --file=order.json --file=order.json --format=json", assert_error=True)
+        assert "dep/1.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Invalid configuration" in tc.out
+        assert "IndexError: list index out of range" not in tc.out
diff --git a/test/unittests/tools/microsoft/test_msvs_toolset.py b/test/unittests/tools/microsoft/test_msvs_toolset.py
index e7b70a3f9..e1c7f3098 100644
--- a/test/unittests/tools/microsoft/test_msvs_toolset.py
+++ b/test/unittests/tools/microsoft/test_msvs_toolset.py
@@ -81,3 +81,10 @@ def test_intel_cc_default(compiler_version, compiler_mode, expected_toolset):
                                        "compiler.version": compiler_version,
                                        "compiler.mode": compiler_mode})
     assert msvs_toolset(conanfile) == expected_toolset
+
+def test_clang_platform_toolset():
+    conanfile = ConanFileMock()
+    conanfile.settings = MockSettings({"compiler": "clang",
+                             "compiler.version": "17",
+                             "compiler.runtime": "dynamic"})
+    assert msvs_toolset(conanfile) == "ClangCl"