Repository: conan-io/conan
Pull request #11569 (2022-07-04 14:24:18)

Add ability to reference files in the local filesystem with file:// s…
Changelog: Add ability to download files in the local filesystem using `file://` URIs.
Docs: https://github.com/conan-io/docs/pull/2635
Close https://github.com/conan-io/conan/issues/8846

## Description
It has been requested be able to download local files with `tools.download()` and `tools.get()` (https://github.com/conan-io/conan/issues/8846 and https://github.com/conan-io/conan-center-index/issues/10623).

This draft PR gives `conan.tools.files.download()` (which is used by `.get()`) the ability to resolve URLs that start with `file://` to files in the local filesystem. In that scenario, the URL provided will be converted to a filesystem path, and `shutil.copy()` will be used to copy the file into the destination.

The underling implementation uses `urllib` functionality to perform this conversion, rather than any internal logic. It should be noted that while the `file://` URI scheme is specified by [RFC 8089](https://datatracker.ietf.org/doc/html/rfc8089), different platforms, applications and implementations may behave slightly differently (see discussion [here](https://discuss.python.org/t/file-uris-in-python/15600)).

Note that it is possible for a `file://` uri to point to resources on a remote system - this implementation makes an assumption that the path returned by the `urllib` library can be accessed without any additional handling.  
On windows this may not cover all cases, see: https://docs.microsoft.com/en-us/dotnet/api/system.uri.isunc?view=net-6.0 

Note that with this proposed implementation the downloads cache is skipped - under the assumption that the files to retrieve are already in the local filesystem. This may not always be true, since it's possible to locally mount network resources - happy to  try and look into making this interact with the downloads cache if necessary!


`pyfakefs` allows mocking a filesystem for python functions


Patch:
diff --git a/conan/tools/files/files.py b/conan/tools/files/files.py
index 7eca6af89..3f69573b9 100644
--- a/conan/tools/files/files.py
+++ b/conan/tools/files/files.py
@@ -11,6 +11,8 @@ from contextlib import contextmanager
 from fnmatch import fnmatch
 
 import six
+from urllib.parse import urlparse
+from urllib.request import url2pathname
 
 from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE, CONAN_TOOLCHAIN_ARGS_SECTION
 from conan.tools.apple.apple import is_apple_os
@@ -165,10 +167,13 @@ def download(conanfile, url, filename, verify=True, retry=None, retry_wait=None,
 
     def _download_file(file_url):
         # The download cache is only used if a checksum is provided, otherwise, a normal download
-        run_downloader(requester=requester, output=out, verify=verify, download_cache=download_cache,
-                       user_download=True, url=file_url,
-                       file_path=filename, retry=retry, retry_wait=retry_wait, overwrite=overwrite,
-                       auth=auth, headers=headers, md5=md5, sha1=sha1, sha256=sha256)
+        if file_url.startswith("file:"):
+            _copy_local_file_from_uri(conanfile, url=file_url, file_path=filename, md5=md5, sha1=sha1, sha256=sha256)
+        else:
+            run_downloader(requester=requester, output=out, verify=verify, download_cache=download_cache,
+                        user_download=True, url=file_url,
+                        file_path=filename, retry=retry, retry_wait=retry_wait, overwrite=overwrite,
+                        auth=auth, headers=headers, md5=md5, sha1=sha1, sha256=sha256)
         out.writeln("")
 
     if not isinstance(url, (list, tuple)):
@@ -184,6 +189,21 @@ def download(conanfile, url, filename, verify=True, retry=None, retry_wait=None,
         else:
             raise ConanException("All downloads from ({}) URLs have failed.".format(len(url)))
 
+def _copy_local_file_from_uri(conanfile, url, file_path, md5=None, sha1=None, sha256=None):
+    file_origin = _path_from_file_uri(url)
+    shutil.copyfile(file_origin, file_path)
+
+    if md5:
+        check_md5(conanfile, file_path, md5)
+    if sha1:
+        check_sha1(conanfile, file_path, sha1)
+    if sha256:
+        check_sha256(conanfile, file_path, sha256)
+
+def _path_from_file_uri(uri):
+    path = urlparse(uri).path
+    return url2pathname(path)
+
 
 def rename(conanfile, src, dst):
     """


Test patch:
diff --git a/conans/test/assets/autotools.py b/conans/test/assets/autotools.py
index 473b291d2..96c0559a3 100644
--- a/conans/test/assets/autotools.py
+++ b/conans/test/assets/autotools.py
@@ -55,12 +55,12 @@ def gen_makefile(**context):
         {% for lib in libs %}
         {%- set link_libs.str = link_libs.str + ' -l' + lib|string  %}
         lib{{lib}}.a: {{lib}}.o
-        	$(AR) rcs lib{{lib}}.a {{lib}}.o
+        	$(AR) rcs lib{{lib}}.a {{lib}}.o {%if static_runtime%}--static{%endif%}
         {% endfor %}
 
         {% for s in apps %}
         {{s}}: {{s}}.o libs
-        	$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) -o {{s}} {{s}}.o $(LIBS) {{link_libs.str}} -L.
+        	$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) -o {{s}} {{s}}.o $(LIBS) {{link_libs.str}} -L. {%if static_runtime%}--static{%endif%}
         {% endfor %}
         """)
 
diff --git a/conans/test/conftest.py b/conans/test/conftest.py
index 07905171f..5a8716836 100644
--- a/conans/test/conftest.py
+++ b/conans/test/conftest.py
@@ -92,6 +92,13 @@ tools_locations = {
         "exe": "mingw32-make",
         "system": {"path": {'Windows': "C:/msys64/mingw32/bin"}},
     },
+    'ucrt64': {
+        "disabled": True,
+        "platform": "Windows",
+        "default": "system",
+        "exe": "mingw32-make",
+        "system": {"path": {'Windows': "C:/msys64/ucrt64/bin"}},
+    },
     'mingw64': {
         "platform": "Windows",
         "default": "system",
@@ -104,6 +111,20 @@ tools_locations = {
         "exe": "make",
         "system": {"path": {'Windows': "C:/msys64/usr/bin"}},
     },
+    'msys2_clang64': {
+        "disabled": True,
+        "platform": "Windows",
+        "default": "system",
+        "exe": "mingw32-make",
+        "system": {"path": {'Windows': "C:/msys64/clang64/bin"}},
+    },
+    'msys2_mingw64_clang64': {
+        "disabled": True,
+        "platform": "Windows",
+        "default": "system",
+        "exe": "mingw32-make",
+        "system": {"path": {'Windows': "C:/msys64/mingw64/bin"}},
+    },
     'cygwin': {
         "platform": "Windows",
         "default": "system",
@@ -156,6 +177,8 @@ except ImportError as e:
 tools_environments = {
     'mingw32': {'Windows': {'MSYSTEM': 'MINGW32'}},
     'mingw64': {'Windows': {'MSYSTEM': 'MINGW64'}},
+    'ucrt64': {'Windows': {'MSYSTEM': 'UCRT64'}},
+    'msys2_clang64': {"Windows": {"MSYSTEM": "CLANG64"}},
     'android_ndk': {'Darwin': {'TEST_CONAN_ANDROID_NDK': '/usr/local/share/android-ndk'}}
 }
 
diff --git a/conans/test/functional/subsystems_build_test.py b/conans/test/functional/subsystems_build_test.py
index d8c6ac4ee..60ab0f080 100644
--- a/conans/test/functional/subsystems_build_test.py
+++ b/conans/test/functional/subsystems_build_test.py
@@ -1,11 +1,13 @@
 import platform
+import tempfile
 
 import pytest
 import textwrap
 
+from conans.client.tools import environment_append
 from conans.test.assets.autotools import gen_makefile
 from conans.test.assets.sources import gen_function_cpp
-from conans.test.functional.utils import check_exe_run
+from conans.test.functional.utils import check_exe_run, check_vs_runtime
 from conans.test.utils.tools import TestClient
 
 
@@ -14,12 +16,24 @@ class TestSubsystems:
 
     @pytest.mark.tool_msys2
     def test_msys2_available(self):
+        """
+        Msys2 needs to be installed:
+        - Go to https://www.msys2.org/, download the exe installer and run it
+        - Follow instructions in https://www.msys2.org/ to update the package DB
+        - Install msys2 autotools "pacman -S autotools"
+        - Make sure the entry in conftest_user.py of msys2 points to the right location
+        """
         client = TestClient()
         client.run_command('uname')
         assert "MSYS" in client.out
 
     @pytest.mark.tool_cygwin
     def test_cygwin_available(self):
+        """ Cygwin is necessary
+        - Install from https://www.cygwin.com/install.html, use the default packages
+        - Install automake 1.16, gcc-g++, make and binutils packages (will add autoconf and more)
+        - Make sure that the path in conftest_user.py is pointing to cygwin "bin" folder
+        """
         client = TestClient()
         client.run_command('uname')
         assert "CYGWIN" in client.out
@@ -27,13 +41,36 @@ class TestSubsystems:
     @pytest.mark.tool_msys2
     @pytest.mark.tool_mingw32
     def test_mingw32_available(self):
+        """ Mingw32 needs to be installed. We use msys2, don't know if others work
+        - Inside msys2, install pacman -S mingw-w64-i686-toolchain (all pkgs)
+        """
         client = TestClient()
         client.run_command('uname')
         assert "MINGW32_NT" in client.out
 
+    @pytest.mark.tool_msys2
+    @pytest.mark.tool_ucrt64
+    def test_ucrt64_available(self):
+        """ ucrt64 needs to be installed. We use msys2, don't know if others work
+        - Inside msys2, install pacman -S mingw-w64-ucrt-x86_64-toolchain (all pkgs)
+        """
+        client = TestClient()
+        client.run_command('uname')
+        assert "MINGW64_NT" in client.out
+
+    @pytest.mark.tool_msys2
+    @pytest.mark.tool_msys2_clang64
+    def test_clang64_available(self):
+        client = TestClient()
+        client.run_command('uname')
+        assert "MINGW64_NT" in client.out
+
     @pytest.mark.tool_msys2
     @pytest.mark.tool_mingw64
     def test_mingw64_available(self):
+        """ Mingw64 needs to be installed. We use msys2, don't know if others work
+        - Inside msys2, install pacman -S mingw-w64-x86_64-toolchain (all pkgs)
+        """
         client = TestClient()
         client.run_command('uname')
         assert "MINGW64_NT" in client.out
@@ -48,77 +85,136 @@ class TestSubsystems:
 class TestSubsystemsBuild:
 
     @staticmethod
-    def _build(client):
-        makefile = gen_makefile(apps=["app"])
+    def _build(client, static_runtime=None):
+        makefile = gen_makefile(apps=["app"], static_runtime=static_runtime)
         main_cpp = gen_function_cpp(name="main")
         client.save({"Makefile": makefile,
                      "app.cpp": main_cpp})
         client.run_command("make")
         client.run_command("app")
 
+    @pytest.mark.parametrize("static", [True, False])
     @pytest.mark.tool_msys2
-    def test_msys(self):
+    def test_msys2(self, static):
         """
         native MSYS environment, binaries depend on MSYS runtime (msys-2.0.dll)
+        Install:
+        - pacman -S gcc
         posix-compatible, intended to be run only in MSYS environment (not in pure Windows)
         """
         client = TestClient()
-        # pacman -S gcc
-        self._build(client)
-
-        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None)
+        self._build(client, static_runtime=static)
 
-        assert "__MINGW32__" not in client.out
-        assert "__MINGW64__" not in client.out
-        assert "__MSYS__" in client.out
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None, subsystem="msys2")
+        assert "_M_X64" not in client.out
+        # TODO: Do not hardcode the visual version
+        check_vs_runtime("app.exe", client, "15", "Debug", static_runtime=static,
+                         subsystem="msys2")
 
+    @pytest.mark.parametrize("static", [True, False])
     @pytest.mark.tool_msys2
     @pytest.mark.tool_mingw64
-    def test_mingw64(self):
+    def test_mingw64(self, static):
         """
-        64-bit GCC, binaries for generic Windows (no dependency on MSYS runtime)
+        This will work if you installed the Mingw toolchain inside msys2 as TestSubystems above
+        64-bit GCC, binaries
         """
         client = TestClient()
         # pacman -S mingw-w64-x86_64-gcc
-        self._build(client)
+        self._build(client, static_runtime=static)
+
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None, subsystem="mingw64")
+        # it also defines the VS 64 bits macro
+        assert "main _M_X64 defined" in client.out
+        check_vs_runtime("app.exe", client, "15", "Debug", static_runtime=static,
+                         subsystem="mingw64")
 
-        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None)
+    @pytest.mark.parametrize("static", [True, False])
+    @pytest.mark.tool_msys2
+    @pytest.mark.tool_msys2_clang64
+    def test_msys2_clang64(self, static):
+        """
+        in msys2
+        $ pacman -S mingw-w64-x86_64-clang (NO, this is the mingw variant in ming64)
+        $ pacman -S mingw-w64-clang-x86_64-toolchain
+        """
+        client = TestClient()
+        self._build(client, static_runtime=static)
 
-        assert "__MINGW64__" in client.out
-        assert "__CYGWIN__" not in client.out
-        assert "__MSYS__" not in client.out
+        check_exe_run(client.out, "main", "clang", None, "Debug", "x86_64", None,
+                      subsystem="mingw64")
+        # it also defines the VS 64 bits macro
+        assert "main _M_X64 defined" in client.out
+        check_vs_runtime("app.exe", client, "15", "Debug", static_runtime=static,
+                         subsystem="clang64")
 
+    @pytest.mark.tool_msys2
+    @pytest.mark.tool_msys2_mingw64_clang64
+    def test_msys2_mingw64_clang64(self):
+        """
+        in msys2
+        $ pacman -S mingw-w64-x86_64-clang
+        $ pacman -S mingw-w64-clang-x86_64-toolchain (NO, this is the clang)
+        """
+        client = TestClient()
+        static = False  # This variant needs --static-glibc -static-libstdc++ (weird) to link static
+        # Need to redefine CXX otherwise is gcc
+        with environment_append({"CXX": "clang++"}):
+            self._build(client, static_runtime=static)
+
+        check_exe_run(client.out, "main", "clang", None, "Debug", "x86_64", None,
+                      subsystem="mingw64")
+        # it also defines the VS 64 bits macro
+        assert "main _M_X64 defined" in client.out
+        check_vs_runtime("app.exe", client, "15", "Debug", static_runtime=static,
+                         subsystem="mingw64")
+
+    @pytest.mark.parametrize("static", [True, False])
     @pytest.mark.tool_msys2
     @pytest.mark.tool_mingw32
-    def test_mingw32(self):
+    def test_mingw32(self, static):
         """
+        This will work if you installed the Mingw toolchain inside msys2 as TestSubystems above
         32-bit GCC, binaries for generic Windows (no dependency on MSYS runtime)
         """
         client = TestClient()
         # pacman -S mingw-w64-i686-gcc
-        self._build(client)
+        self._build(client, static_runtime=static)
+
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86", None, subsystem="mingw32")
+        # It also defines the VS flag
+        assert "main _M_IX86 defined" in client.out
+        check_vs_runtime("app.exe", client, "15", "Debug", static_runtime=static,
+                         subsystem="mingw32")
 
-        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86", None)
+    @pytest.mark.parametrize("static", [True, False])
+    @pytest.mark.tool_msys2
+    @pytest.mark.tool_ucrt64
+    def test_ucrt64(self, static):
+        """
+        This will work if you installed the Mingw toolchain inside msys2 as TestSubystems above
+        """
+        client = TestClient()
 
-        assert "__MINGW32__" in client.out
-        assert "__CYGWIN__" not in client.out
-        assert "__MSYS__" not in client.out
+        self._build(client, static_runtime=static)
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None, subsystem="mingw32")
+        # it also defines the VS macro
+        assert "main _M_X64 defined" in client.out
+        check_vs_runtime("app.exe", client, "15", "Debug", static_runtime=static,
+                         subsystem="ucrt64")
 
+    @pytest.mark.parametrize("static", [True, False])
     @pytest.mark.tool_cygwin
-    def test_cygwin(self):
+    def test_cygwin(self, static):
         """
         Cygwin environment, binaries depend on Cygwin runtime (cygwin1.dll)
         posix-compatible, intended to be run only in Cygwin environment (not in pure Windows)
         """
         client = TestClient()
-        # install "gcc-c++" and "make" packages
-        self._build(client)
-        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None)
-
-        assert "__CYGWIN__" in client.out
-        assert "__MINGW32__" not in client.out
-        assert "__MINGW64__" not in client.out
-        assert "__MSYS__" not in client.out
+        self._build(client, static_runtime=static)
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None, subsystem="cygwin")
+        check_vs_runtime("app.exe", client, "15", "Debug", static_runtime=static,
+                         subsystem="cygwin")
 
 
 @pytest.mark.skipif(platform.system() != "Windows", reason="Tests Windows Subsystems")
@@ -157,12 +253,8 @@ class TestSubsystemsAutotoolsBuild:
         client = TestClient()
         # pacman -S gcc
         self._build(client)
-
-        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None)
-
-        assert "__MINGW32__" not in client.out
-        assert "__MINGW64__" not in client.out
-        assert "__MSYS__" in client.out
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None, subsystem="msys2")
+        check_vs_runtime("app.exe", client, "15", "Debug", subsystem="msys2")
 
     @pytest.mark.tool_msys2
     @pytest.mark.tool_mingw64
@@ -173,12 +265,8 @@ class TestSubsystemsAutotoolsBuild:
         client = TestClient()
         # pacman -S mingw-w64-x86_64-gcc
         self._build(client)
-
-        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None)
-
-        assert "__MINGW64__" in client.out
-        assert "__CYGWIN__" not in client.out
-        assert "__MSYS__" not in client.out
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None, subsystem="mingw64")
+        check_vs_runtime("app.exe", client, "15", "Debug", subsystem="mingw64")
 
     @pytest.mark.tool_msys2
     @pytest.mark.tool_mingw32
@@ -189,110 +277,162 @@ class TestSubsystemsAutotoolsBuild:
         client = TestClient()
         # pacman -S mingw-w64-i686-gcc
         self._build(client)
-
-        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86", None)
-
-        assert "__MINGW32__" in client.out
-        assert "__CYGWIN__" not in client.out
-        assert "__MSYS__" not in client.out
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86", None, subsystem="mingw32")
+        check_vs_runtime("app.exe", client, "15", "Debug", subsystem="mingw32")
 
     @pytest.mark.tool_cygwin
     def test_cygwin(self):
         """
         Cygwin environment, binaries depend on Cygwin runtime (cygwin1.dll)
         posix-compatible, intended to be run only in Cygwin environment (not in pure Windows)
+        # install autotools, autoconf, libtool, "gcc-c++" and "make" packages
         """
         client = TestClient()
-        # install "gcc-c++" and "make" packages
         self._build(client)
-        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None)
-
-        assert "__CYGWIN__" in client.out
-        assert "__MINGW32__" not in client.out
-        assert "__MINGW64__" not in client.out
-        assert "__MSYS__" not in client.out
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None, subsystem="cygwin")
+        check_vs_runtime("app.exe", client, "15", "Debug", subsystem="cygwin")
 
 
 @pytest.mark.skipif(platform.system() != "Windows", reason="Tests Windows Subsystems")
 class TestSubsystemsCMakeBuild:
+    """ These tests are running the CMake INSIDE THE subsystem, not the Windows native one
+    The results are basically the same if CMake is outside the subsystem, but it is NOT
+    enough to define CMAKE_CXX_COMPILER full path to the compiler, but it must be in the path
+
+    """
     cmakelists = textwrap.dedent("""
-        cmake_minimum_required(VERSION 2.8)
-        project(app)
+        cmake_minimum_required(VERSION 3.15)
+        project(app CXX)
+        message(STATUS "MYCMAKE VERSION=${CMAKE_VERSION}")
         add_executable(app main.cpp)
         """)
 
-    def _build(self, client, generator="Unix Makefiles"):
+    def _build(self, client, generator="Unix Makefiles", compiler=None, toolset=None):
         main_cpp = gen_function_cpp(name="main")
         client.save({"CMakeLists.txt": self.cmakelists,
                      "main.cpp": main_cpp})
 
-        client.run_command("cmake "
-                           " -DCMAKE_C_FLAGS=\"-Wl,-verbose\""
-                           " -DCMAKE_CXX_FLAGS=\"-Wl,-verbose\""
-                           " -DCMAKE_SH=\"CMAKE_SH-NOTFOUND\" -G \"%s\" ." % generator)
+        cmake_compiler = ""
+        if compiler:
+            cmake_compiler += " -DCMAKE_C_COMPILER={}".format(compiler)
+            compilerpp = "clang++" if compiler == "clang" else "g++"
+            cmake_compiler += " -DCMAKE_CXX_COMPILER={}".format(compilerpp)
+            cmake_compiler += " -DCMAKE_RC_COMPILER={}".format(compiler)
+        toolset = "-T {}".format(toolset) if toolset else ""
+        client.run_command("cmake {} {}"
+                           " -DCMAKE_SH=\"CMAKE_SH-NOTFOUND\" -G \"{}\" .".format(cmake_compiler,
+                                                                                  toolset,
+                                                                                  generator))
         client.run_command("cmake --build .")
-        client.run_command("app")
+        app = "app" if "Visual" not in generator else r"Debug\app"
+        client.run_command(app)
 
     @pytest.mark.tool_msys2
     def test_msys(self):
         """
-        native MSYS environment, binaries depend on MSYS runtime (msys-2.0.dll)
-        posix-compatible, intended to be run only in MSYS environment (not in pure Windows)
+        pacman -S cmake
         """
         client = TestClient()
-        # pacman -S gcc
         self._build(client)
-
-        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None)
-
-        assert "__MINGW32__" not in client.out
-        assert "__MINGW64__" not in client.out
-        assert "__MSYS__" in client.out
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None, subsystem="msys2")
+        check_vs_runtime("app.exe", client, "15", "Debug", subsystem="msys2")
 
     @pytest.mark.tool_msys2
     @pytest.mark.tool_mingw64
     def test_mingw64(self):
         """
-        64-bit GCC, binaries for generic Windows (no dependency on MSYS runtime)
+        $ pacman -S mingw-w64-x86_64-cmake
         """
         client = TestClient()
-        # pacman -S mingw-w64-x86_64-gcc
         self._build(client, generator="MinGW Makefiles")
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None, subsystem="mingw64")
+        check_vs_runtime("app.exe", client, "15", "Debug", subsystem="mingw64")
 
-        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None)
+    @pytest.mark.tool_msys2
+    @pytest.mark.tool_msys2_clang64
+    @pytest.mark.skip(reason="This doesn't work, seems CMake issue")
+    def test_msys2_clang64(self):
+        """
+        FAILS WITH:
+        System is unknown to cmake, create:
+        Platform/MINGW64_NT-10.0-19044 to use this system,
+        """
+        client = TestClient()
+        self._build(client, generator="Unix Makefiles")
+        check_exe_run(client.out, "main", "clang", None, "Debug", "x86_64", None,
+                      subsystem="mingw64")
+        check_vs_runtime("app.exe", client, "15", "Debug", subsystem="clang64")
 
-        assert "__MINGW64__" in client.out
-        assert "__CYGWIN__" not in client.out
-        assert "__MSYS__" not in client.out
+    @pytest.mark.tool_msys2
+    @pytest.mark.tool_msys2_clang64
+    @pytest.mark.tool_cmake(version="3.19")
+    def test_msys2_clang64_external(self):
+        """
+        Exactly the same as the previous tests, but with a native cmake 3.19 (higher priority)
+        """
+        client = TestClient()
+        self._build(client)
+        assert "MYCMAKE VERSION=3.19" in client.out
+        check_exe_run(client.out, "main", "clang", None, "Debug", "x86_64", None,
+                      subsystem="mingw64")
+        check_vs_runtime("app.exe", client, "15", "Debug", subsystem="clang64")
+
+    @pytest.mark.tool_msys2
+    @pytest.mark.tool_msys2_mingw64_clang64
+    def test_msys2_mingw64_clang64(self):
+        """
+        """
+        client = TestClient()
+        # IMPORTANT: Need to redefine the CXX, otherwise CMake will use GCC by default
+        with environment_append({"CXX": "clang++"}):
+            self._build(client, generator="MinGW Makefiles")
+        check_exe_run(client.out, "main", "clang", None, "Debug", "x86_64", None,
+                      subsystem="mingw64")
+        check_vs_runtime("app.exe", client, "15", "Debug", subsystem="mingw64")
 
     @pytest.mark.tool_msys2
     @pytest.mark.tool_mingw32
     def test_mingw32(self):
         """
-        32-bit GCC, binaries for generic Windows (no dependency on MSYS runtime)
+        $ pacman -S mingw-w64-i686-cmake
         """
         client = TestClient()
-        # pacman -S mingw-w64-i686-gcc
         self._build(client, generator="MinGW Makefiles")
-
-        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86", None)
-
-        assert "__MINGW32__" in client.out
-        assert "__CYGWIN__" not in client.out
-        assert "__MSYS__" not in client.out
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86", None, subsystem="mingw32")
+        check_vs_runtime("app.exe", client, "15", "Debug", subsystem="mingw32")
 
     @pytest.mark.tool_cygwin
     def test_cygwin(self):
         """
-        Cygwin environment, binaries depend on Cygwin runtime (cygwin1.dll)
-        posix-compatible, intended to be run only in Cygwin environment (not in pure Windows)
+        Needs to install cmake from the cygwin setup.exe
         """
         client = TestClient()
         # install "gcc-c++" and "make" packages
         self._build(client)
-        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None)
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86_64", None, subsystem="cygwin")
+        check_vs_runtime("app.exe", client, "15", "Debug", subsystem="cygwin")
+
+    @pytest.mark.tool_clang
+    def test_clang(self):
+        """
+        native, LLVM/Clang compiler
+        Installing the binary from LLVM site
+        https://github.com/llvm/llvm-project/releases/tag/llvmorg-14.0.6
+        """
+        client = TestClient()
+        self._build(client, generator="Ninja", compiler="clang")
+        check_exe_run(client.out, "main", "clang", None, "Debug", "x86_64", None, subsystem=None)
+        check_vs_runtime("app.exe", client, "15", "Debug", subsystem=None)
 
-        assert "__CYGWIN__" in client.out
-        assert "__MINGW32__" not in client.out
-        assert "__MINGW64__" not in client.out
-        assert "__MSYS__" not in client.out
+    @pytest.mark.tool_cmake(version="3.23")
+    @pytest.mark.tool_visual_studio(version="17")
+    def test_vs_clang(self):
+        """
+        native, LLVM/Clang compiler installed with VS 2022 -T ClangCL
+        """
+        # IMPORTANT: VS CLang not found if in another unit
+        folder = tempfile.mkdtemp(suffix='conans')
+        client = TestClient(current_folder=folder)
+        self._build(client, generator="Visual Studio 17 2022", toolset="ClangCL")
+        check_exe_run(client.out, "main", "clang", None, "Debug", "x86_64", None, subsystem=None)
+        check_vs_runtime("Debug/app.exe", client, "15", "Debug", subsystem=None)
diff --git a/conans/test/functional/toolchains/cmake/test_cmake.py b/conans/test/functional/toolchains/cmake/test_cmake.py
index 0c38395fb..e84fc0981 100644
--- a/conans/test/functional/toolchains/cmake/test_cmake.py
+++ b/conans/test/functional/toolchains/cmake/test_cmake.py
@@ -350,7 +350,7 @@ class WinTest(Base):
                        "MYVAR_CONFIG": "MYVAR_{}".format(build_type.upper()),
                        "MYDEFINE": "MYDEF_VALUE",
                        "MYDEFINE_CONFIG": "MYDEF_{}".format(build_type.upper())
-                       })
+                       }, subsystem="mingw64")
 
         self._modify_code()
         time.sleep(2)
diff --git a/conans/test/functional/toolchains/cmake/test_ninja.py b/conans/test/functional/toolchains/cmake/test_ninja.py
index fbd7df03f..0a15f7d9e 100644
--- a/conans/test/functional/toolchains/cmake/test_ninja.py
+++ b/conans/test/functional/toolchains/cmake/test_ninja.py
@@ -171,7 +171,8 @@ def test_locally_build_gcc(build_type, shared, client):
 
     client.run_command("myapp.exe")
     # TODO: Need full gcc version check
-    check_exe_run(client.out, ["main", "hello"], "gcc", None, build_type, "x86_64", cppstd=None)
+    check_exe_run(client.out, ["main", "hello"], "gcc", None, build_type, "x86_64", cppstd=None,
+                  subsystem="mingw64")
 
 
 @pytest.mark.skipif(platform.system() != "Darwin", reason="Requires apple-clang")
diff --git a/conans/test/functional/toolchains/gnu/autotools/test_basic.py b/conans/test/functional/toolchains/gnu/autotools/test_basic.py
index 3d4a41f96..b7c507c21 100644
--- a/conans/test/functional/toolchains/gnu/autotools/test_basic.py
+++ b/conans/test/functional/toolchains/gnu/autotools/test_basic.py
@@ -53,11 +53,12 @@ def test_autotools():
     client.run("build .")
     client.run_command("./main")
     cxx11_abi = 0 if platform.system() == "Linux" else None
-    check_exe_run(client.out, "main", "gcc", None, "Release", "x86_64", None, cxx11_abi=cxx11_abi)
+    compiler = "gcc" if platform.system() == "Linux" else "clang"
+    check_exe_run(client.out, "main", compiler, None, "Release", "x86_64", None, cxx11_abi=cxx11_abi)
     assert "hello/0.1: Hello World Release!" in client.out
 
 
-def build_windows_subsystem(profile, make_program):
+def build_windows_subsystem(profile, make_program, subsystem):
     """ The AutotoolsDeps can be used also in pure Makefiles, if the makefiles follow
     the Autotools conventions
     """
@@ -99,7 +100,7 @@ def build_windows_subsystem(profile, make_program):
     client.run_command(cmd)
     client.run_command("app")
     # TODO: fill compiler version when ready
-    check_exe_run(client.out, "main", "gcc", None, "Release", "x86_64", None)
+    check_exe_run(client.out, "main", "gcc", None, "Release", "x86_64", None, subsystem=subsystem)
     assert "hello/0.1: Hello World Release!" in client.out
 
     client.save({"app.cpp": gen_function_cpp(name="main", msg="main2",
@@ -111,7 +112,8 @@ def build_windows_subsystem(profile, make_program):
     client.run("build .")
     client.run_command("app")
     # TODO: fill compiler version when ready
-    check_exe_run(client.out, "main2", "gcc", None, "Release", "x86_64", None, cxx11_abi=0)
+    check_exe_run(client.out, "main2", "gcc", None, "Release", "x86_64", None, cxx11_abi=0,
+                  subsystem=subsystem)
     assert "hello/0.1: Hello World Release!" in client.out
     return client.out
 
@@ -129,10 +131,7 @@ def test_autotoolsdeps_cygwin():
         arch=x86_64
         build_type=Release
         """)
-    out = build_windows_subsystem(gcc, make_program="make")
-    assert "__MSYS__" not in out
-    assert "MINGW" not in out
-    assert "main2 __CYGWIN__1" in out
+    build_windows_subsystem(gcc, make_program="make", subsystem="cygwin")
 
 
 @pytest.mark.tool_mingw64
@@ -147,13 +146,13 @@ def test_autotoolsdeps_mingw_msys():
         arch=x86_64
         build_type=Release
         """)
-    out = build_windows_subsystem(gcc, make_program="mingw32-make")
-    assert "__MSYS__" not in out
-    assert "main2 __MINGW64__1" in out
+    build_windows_subsystem(gcc, make_program="mingw32-make", subsystem="mingw64")
 
 
 @pytest.mark.tool_msys2
 @pytest.mark.skipif(platform.system() != "Windows", reason="Needs windows")
+# If we use the cmake inside msys2, it fails, so better force our own cmake
+@pytest.mark.tool_cmake
 def test_autotoolsdeps_msys():
     gcc = textwrap.dedent("""
         [settings]
@@ -165,11 +164,7 @@ def test_autotoolsdeps_msys():
         arch=x86_64
         build_type=Release
         """)
-    out = build_windows_subsystem(gcc, make_program="make")
-    # Msys2 is a rewrite of Msys, using Cygwin
-    assert "MINGW" not in out
-    assert "main2 __MSYS__1" in out
-    assert "main2 __CYGWIN__1" in out
+    build_windows_subsystem(gcc, make_program="make", subsystem="msys2")
 
 
 @pytest.mark.skipif(platform.system() not in ["Linux", "Darwin"], reason="Requires Autotools")
diff --git a/conans/test/functional/utils.py b/conans/test/functional/utils.py
index 9fe753eab..b4d121735 100644
--- a/conans/test/functional/utils.py
+++ b/conans/test/functional/utils.py
@@ -2,18 +2,64 @@ from conan.tools.microsoft.visual import vcvars_command
 
 
 def check_vs_runtime(artifact, client, vs_version, build_type, architecture="amd64",
-                     static_runtime=False):
+                     static_runtime=False, subsystem=None):
     vcvars = vcvars_command(version=vs_version, architecture=architecture)
     normalized_path = artifact.replace("/", "\\")
     static = artifact.endswith(".a") or artifact.endswith(".lib")
     if not static:
         cmd = ('%s && dumpbin /nologo /dependents "%s"' % (vcvars, normalized_path))
         client.run_command(cmd)
-        if static_runtime:
+        if subsystem:
+            assert "KERNEL32.dll" in client.out
+            if subsystem in ("mingw32", "mingw64"):
+                assert "msvcrt.dll" in client.out
+                if static_runtime:
+                    assert "libstdc++-6.dll" not in client.out
+                else:
+                    assert "libstdc++-6.dll" in client.out
+                if subsystem == "mingw32":
+                    if static_runtime:
+                        assert "libgcc_s_dw2-1.dll" not in client.out
+                    else:
+                        assert "libgcc_s_dw2-1.dll" in client.out
+            elif subsystem == "msys2":
+                assert "msys-2.0.dll" in client.out
+                if static_runtime:
+                    assert "msys-stdc++-6.dll" not in client.out
+                else:
+                    assert "msys-stdc++-6.dll" in client.out
+            elif subsystem == "cygwin":
+                assert "cygwin1.dll" in client.out
+                if static_runtime:
+                    assert "cygstdc++-6.dll" not in client.out
+                else:
+                    assert "cygstdc++-6.dll" in client.out
+            elif subsystem == "ucrt64":
+                assert "api-ms-win-crt-" in client.out
+                if static_runtime:
+                    assert "libstdc++-6.dll" not in client.out
+                else:
+                    assert "libstdc++-6.dll" in client.out
+            elif subsystem == "clang64":
+                assert "api-ms-win-crt-" in client.out
+                if static_runtime:
+                    assert "libunwind.dll" not in client.out
+                    assert "libc++.dll" not in client.out
+                else:
+                    assert "libunwind.dll" in client.out
+                    assert "libc++.dll" in client.out
+            else:
+                raise Exception("unknown {}".format(subsystem))
+        elif static_runtime:
             assert "KERNEL32.dll" in client.out
             assert "MSVC" not in client.out
             assert "VCRUNTIME" not in client.out
         else:
+            assert "KERNEL32.dll" in client.out
+            if build_type == "Debug":
+                assert "ucrtbased" in client.out
+            else:
+                assert "api-ms-win-crt-" in client.out
             if vs_version in ["15", "16", "17"]:  # UCRT
                 debug = "D" if build_type == "Debug" else ""
                 assert "MSVCP140{}.dll".format(debug) in client.out
@@ -29,7 +75,7 @@ def check_vs_runtime(artifact, client, vs_version, build_type, architecture="amd
 
 
 def check_exe_run(output, names, compiler, version, build_type, arch, cppstd, definitions=None,
-                  cxx11_abi=None):
+                  cxx11_abi=None, subsystem=None):
     output = str(output)
     names = names if isinstance(names, list) else [names]
 
@@ -51,12 +97,13 @@ def check_exe_run(output, names, compiler, version, build_type, arch, cppstd, de
         elif compiler in ["gcc", "clang", "apple-clang"]:
             if compiler == "gcc":
                 assert "{} __GNUC__".format(name) in output
+                assert "clang" not in output
                 if version:  # FIXME: At the moment, the GCC version is not controlled, will change
                     major, minor = version.split(".")[0:2]
                     assert "{} __GNUC__{}".format(name, major) in output
                     assert "{} __GNUC_MINOR__{}".format(name, minor) in output
             elif compiler == "clang":
-                assert "{} __clang__".format(name) in output
+                assert "{} __clang_".format(name) in output
                 if version:
                     major, minor = version.split(".")[0:2]
                     assert "{} __clang_major__{}".format(name, major) in output
@@ -88,3 +135,28 @@ def check_exe_run(output, names, compiler, version, build_type, arch, cppstd, de
         if definitions:
             for k, v in definitions.items():
                 assert "{}: {}".format(k, v) in output
+
+        if subsystem:
+            if subsystem == "msys2":
+                assert "__MSYS__" in output
+                assert "__CYGWIN__" in output
+                assert "MINGW" not in output
+            elif subsystem in ("mingw32", "mingw64"):
+                assert "__MINGW32__" in output
+                assert "__CYGWIN__" not in output
+                assert "__MSYS__" not in output
+                if subsystem == "mingw64":
+                    assert "__MINGW64__" in output
+                else:
+                    assert "MING64" not in output
+            elif subsystem == "cygwin":
+                assert "__CYGWIN__" in output
+                assert "__MINGW32__" not in output
+                assert "__MINGW64__" not in output
+                assert "__MSYS__" not in output
+            else:
+                raise Exception("unknown subsystem {}".format(subsystem))
+        else:
+            assert "CYGWIN" not in output
+            assert "MINGW" not in output
+            assert "MSYS" not in output
diff --git a/conans/test/unittests/tools/files/test_downloads.py b/conans/test/unittests/tools/files/test_downloads.py
index 30675c976..1ec8077ca 100644
--- a/conans/test/unittests/tools/files/test_downloads.py
+++ b/conans/test/unittests/tools/files/test_downloads.py
@@ -1,4 +1,5 @@
 import os
+import platform
 
 import pytest
 import requests
@@ -157,6 +158,32 @@ class TestDownload:
         assert "Waiting" not in str(conanfile.output)
         assert "retry" not in str(conanfile.output)
 
+    def test_download_localfile(self):
+        conanfile = ConanFileMock()
+        conanfile._conan_requester = requests
+
+        file_location =  os.path.join(temp_folder(), "file.txt")
+        save(file_location, "this is some content")
+
+        file_url = f"file:///{file_location}"
+        file_md5 = "736db904ad222bf88ee6b8d103fceb8e"
+        
+        dest = os.path.join(temp_folder(), "downloaded_file.txt")
+        download(conanfile, file_url, dest, md5=file_md5)
+        content = load(dest)
+        assert "this is some content" == content
+
+    def test_download_localfile_notfound(self):
+        conanfile = ConanFileMock()
+        conanfile._conan_requester = requests
+
+        file_url = "file:///path/to/missing/file.txt"
+        dest = os.path.join(temp_folder(), "file.txt")
+
+        with pytest.raises(FileNotFoundError) as exc:
+            download(conanfile, file_url, dest)
+
+        assert "No such file" in str(exc.value)
 
 @pytest.fixture()
 def bottle_server_zip():