Repository: conan-io/conan
Pull request #11911 (2022-08-18 15:03:22)

Add export_conandata_patches utility
Changelog: Feature: Add an utility to export easily all patch
Docs: https://github.com/conan-io/docs/pull/XXXX

Closes: https://github.com/conan-io/conan/issues/11770
Closes: https://github.com/conan-io/conan/issues/11923

This issue has no test because I didn't managed to make it work but you can see this as a quick proposal before going further in the implementation.

- [ ] Refer to the issue that supports this Pull Request.
- [x] If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
- [x] I've read the [Contributing guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [x] I've followed the PEP8 style guides for Python code.
- [ ] I've opened another PR in the Conan docs repo to the ``develop`` branch, documenting this one. 

<sup>**Note:** By default this PR will skip the slower tests and will use a limited set of python versions. Check [here](https://github.com/conan-io/conan/blob/develop/.github/PR_INCREASE_TESTING.md) how to increase the testing level by writing some tags in the current PR body text.</sup>


MartinDelille: @jgsogo I'll be happy to have your feedback on this feature that would replace this two lines by a simpler `export_conandata_patches(self)`. Feel free to ping anyone who would be interested in this.
jgsogo: Anyway, I don't know if there's already a PR for it. When I'm back on my computer I'll have a look to the implementation 
SpaceIm: https://github.com/conan-io/conan/issues/11770
memsharded: We would like this to be in 1.52 soon, so I have contributed a review, including completing the test and more error checking.

Patch:
diff --git a/conan/tools/apple/__init__.py b/conan/tools/apple/__init__.py
index e205ff0fc..22f18bb12 100644
--- a/conan/tools/apple/__init__.py
+++ b/conan/tools/apple/__init__.py
@@ -3,8 +3,7 @@
 # from conan.tools.apple.apple import apple_dot_clean
 # from conan.tools.apple.apple import apple_sdk_name
 # from conan.tools.apple.apple import apple_deployment_target_flag
-# from conan.tools.apple.apple import to_apple_arch
-from conan.tools.apple.apple import fix_apple_shared_install_name, is_apple_os
+from conan.tools.apple.apple import fix_apple_shared_install_name, is_apple_os, to_apple_arch
 from conan.tools.apple.xcodedeps import XcodeDeps
 from conan.tools.apple.xcodebuild import XcodeBuild
 from conan.tools.apple.xcodetoolchain import XcodeToolchain
diff --git a/conan/tools/apple/apple.py b/conan/tools/apple/apple.py
index eac71094c..b796de342 100644
--- a/conan/tools/apple/apple.py
+++ b/conan/tools/apple/apple.py
@@ -3,6 +3,7 @@
 import os
 
 from conans.util.runners import check_output_runner
+from conans.client.tools.apple import to_apple_arch as _to_apple_arch
 
 
 def is_apple_os(conanfile):
@@ -11,16 +12,10 @@ def is_apple_os(conanfile):
     return str(os_) in ['Macos', 'iOS', 'watchOS', 'tvOS']
 
 
-def to_apple_arch(arch):
+def to_apple_arch(conanfile):
     """converts conan-style architecture into Apple-style arch"""
-    return {'x86': 'i386',
-            'x86_64': 'x86_64',
-            'armv7': 'armv7',
-            'armv8': 'arm64',
-            'armv8_32': 'arm64_32',
-            'armv8.3': 'arm64e',
-            'armv7s': 'armv7s',
-            'armv7k': 'armv7k'}.get(str(arch))
+    arch_ = conanfile.settings.get_safe("arch")
+    return _to_apple_arch(arch_)
 
 
 def _guess_apple_sdk_name(os_, arch):
diff --git a/conan/tools/apple/xcodebuild.py b/conan/tools/apple/xcodebuild.py
index a822a57a7..10403d753 100644
--- a/conan/tools/apple/xcodebuild.py
+++ b/conan/tools/apple/xcodebuild.py
@@ -1,6 +1,6 @@
 import os
 
-from conan.tools.apple.apple import to_apple_arch
+from conan.tools.apple import to_apple_arch
 from conans.errors import ConanException
 
 
@@ -8,7 +8,7 @@ class XcodeBuild(object):
     def __init__(self, conanfile):
         self._conanfile = conanfile
         self._build_type = conanfile.settings.get_safe("build_type")
-        self._arch = to_apple_arch(conanfile.settings.get_safe("arch"))
+        self._arch = to_apple_arch(self._conanfile)
         self._sdk = conanfile.settings.get_safe("os.sdk") or ""
         self._sdk_version = conanfile.settings.get_safe("os.sdk_version") or ""
 
diff --git a/conan/tools/apple/xcodedeps.py b/conan/tools/apple/xcodedeps.py
index 7f3f87d70..4838df7a5 100644
--- a/conan/tools/apple/xcodedeps.py
+++ b/conan/tools/apple/xcodedeps.py
@@ -7,7 +7,7 @@ from jinja2 import Template
 from conan.tools._check_build_profile import check_using_build_profile
 from conans.errors import ConanException
 from conans.util.files import load, save
-from conan.tools.apple.apple import to_apple_arch
+from conans.client.tools.apple import to_apple_arch
 
 GLOBAL_XCCONFIG_TEMPLATE = textwrap.dedent("""\
     // Includes both the toolchain and the dependencies
@@ -61,6 +61,7 @@ class XcodeDeps(object):
     general_name = "conandeps.xcconfig"
 
     _conf_xconfig = textwrap.dedent("""\
+        PACKAGE_ROOT_{{pkg_name}}{{condition}} = {{root}}
         // Compiler options for {{pkg_name}}::{{comp_name}}
         HEADER_SEARCH_PATHS_{{pkg_name}}_{{comp_name}}{{condition}} = {{include_dirs}}
         GCC_PREPROCESSOR_DEFINITIONS_{{pkg_name}}_{{comp_name}}{{condition}} = {{definitions}}
@@ -122,7 +123,7 @@ class XcodeDeps(object):
         for generator_file, content in generator_files.items():
             save(generator_file, content)
 
-    def _conf_xconfig_file(self, pkg_name, comp_name, transitive_cpp_infos):
+    def _conf_xconfig_file(self, pkg_name, comp_name, package_folder, transitive_cpp_infos):
         """
         content for conan_poco_x86_release.xcconfig, containing the activation
         """
@@ -133,6 +134,7 @@ class XcodeDeps(object):
         fields = {
             'pkg_name': pkg_name,
             'comp_name': comp_name,
+            'root': package_folder,
             'include_dirs': " ".join('"{}"'.format(p) for p in _merged_vars("includedirs")),
             'lib_dirs': " ".join('"{}"'.format(p) for p in _merged_vars("libdirs")),
             'libs': " ".join("-l{}".format(lib) for lib in _merged_vars("libs")),
@@ -197,13 +199,13 @@ class XcodeDeps(object):
                                                GLOBAL_XCCONFIG_TEMPLATE,
                                                [self.general_name])
 
-    def get_content_for_component(self, pkg_name, component_name, transitive_internal, transitive_external):
+    def get_content_for_component(self, pkg_name, component_name, package_folder, transitive_internal, transitive_external):
         result = {}
 
         conf_name = _xcconfig_settings_filename(self._conanfile.settings)
 
         props_name = "conan_{}_{}{}.xcconfig".format(pkg_name, component_name, conf_name)
-        result[props_name] = self._conf_xconfig_file(pkg_name, component_name, transitive_internal)
+        result[props_name] = self._conf_xconfig_file(pkg_name, component_name, package_folder, transitive_internal)
 
         # The entry point for each package
         file_dep_name = "conan_{}_{}.xcconfig".format(pkg_name, component_name)
@@ -262,6 +264,7 @@ class XcodeDeps(object):
                     transitive_external = list(OrderedDict.fromkeys(transitive_external).keys())
 
                     component_content = self.get_content_for_component(dep_name, comp_name,
+                                                                       dep.package_folder,
                                                                        transitive_internal,
                                                                        transitive_external)
                     include_components_names.append((dep_name, comp_name))
@@ -279,7 +282,7 @@ class XcodeDeps(object):
                         public_deps.append((_format_name(d.ref.name),) * 2)
 
                 required_components = dep.cpp_info.required_components if dep.cpp_info.required_components else public_deps
-                root_content = self.get_content_for_component(dep_name, dep_name, [dep.cpp_info],
+                root_content = self.get_content_for_component(dep_name, dep_name, dep.package_folder, [dep.cpp_info],
                                                               required_components)
                 include_components_names.append((dep_name, dep_name))
                 result.update(root_content)
diff --git a/conan/tools/apple/xcodetoolchain.py b/conan/tools/apple/xcodetoolchain.py
index bc08b2a3e..892aac9f4 100644
--- a/conan/tools/apple/xcodetoolchain.py
+++ b/conan/tools/apple/xcodetoolchain.py
@@ -2,7 +2,7 @@ import textwrap
 
 from conan.tools._check_build_profile import check_using_build_profile
 from conan.tools._compilers import cppstd_flag
-from conan.tools.apple.apple import to_apple_arch
+from conan.tools.apple import to_apple_arch
 from conan.tools.apple.xcodedeps import GLOBAL_XCCONFIG_FILENAME, GLOBAL_XCCONFIG_TEMPLATE, \
     _add_includes_to_file_or_create, _xcconfig_settings_filename, _xcconfig_conditional
 from conans.util.files import save
@@ -36,7 +36,7 @@ class XcodeToolchain(object):
     def __init__(self, conanfile):
         self._conanfile = conanfile
         arch = conanfile.settings.get_safe("arch")
-        self.architecture = to_apple_arch(arch) or arch
+        self.architecture = to_apple_arch(self._conanfile) or arch
         self.configuration = conanfile.settings.build_type
         self.sdk = conanfile.settings.get_safe("os.sdk")
         self.sdk_version = conanfile.settings.get_safe("os.sdk_version")
diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py
index 03449a059..2b710d190 100644
--- a/conan/tools/cmake/presets.py
+++ b/conan/tools/cmake/presets.py
@@ -133,7 +133,8 @@ def write_cmake_presets(conanfile, toolchain_file, generator, cache_variables):
         if "CMAKE_SH" not in cache_variables:
             cache_variables["CMAKE_SH"] = "CMAKE_SH-NOTFOUND"
 
-        cmake_make_program = conanfile.conf.get("tools.gnu:make_program", default=cache_variables.get("CMAKE_MAKE_PROGRAM"))
+        cmake_make_program = conanfile.conf.get("tools.gnu:make_program",
+                                                default=cache_variables.get("CMAKE_MAKE_PROGRAM"))
         if cmake_make_program:
             cmake_make_program = cmake_make_program.replace("\\", "/")
             cache_variables["CMAKE_MAKE_PROGRAM"] = cmake_make_program
@@ -141,6 +142,10 @@ def write_cmake_presets(conanfile, toolchain_file, generator, cache_variables):
     if "CMAKE_POLICY_DEFAULT_CMP0091" not in cache_variables:
         cache_variables["CMAKE_POLICY_DEFAULT_CMP0091"] = "NEW"
 
+    if "BUILD_TESTING" not in cache_variables:
+        if conanfile.conf.get("tools.build:skip_test", check_type=bool):
+            cache_variables["BUILD_TESTING"] = "OFF"
+
     preset_path = os.path.join(conanfile.generators_folder, "CMakePresets.json")
     multiconfig = is_multi_configuration(generator)
 
diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py
index 3e36e52e8..52608545e 100644
--- a/conan/tools/cmake/toolchain/blocks.py
+++ b/conan/tools/cmake/toolchain/blocks.py
@@ -405,8 +405,7 @@ class AppleSystemBlock(Block):
         if not is_apple_os(self._conanfile):
             return None
 
-        arch = self._conanfile.settings.get_safe("arch")
-        host_architecture = to_apple_arch(arch)
+        host_architecture = to_apple_arch(self._conanfile)
         host_os_version = self._conanfile.settings.get_safe("os.version")
         host_sdk_name = self._apple_sdk_name()
         is_debug = self._conanfile.settings.get_safe('build_type') == "Debug"
@@ -808,7 +807,7 @@ class GenericSystemBlock(Block):
                     system_name = {'Macos': 'Darwin'}.get(os_host, os_host)
                     #  CMAKE_SYSTEM_VERSION for Apple sets the sdk version, not the os version
                     _system_version = self._conanfile.settings.get_safe("os.sdk_version")
-                    _system_processor = to_apple_arch(arch_host)
+                    _system_processor = to_apple_arch(self._conanfile)
                 elif os_host != 'Android':
                     system_name = self._get_generic_system_name()
                     _system_version = self._conanfile.settings.get_safe("os.version")
diff --git a/conan/tools/files/__init__.py b/conan/tools/files/__init__.py
index 2d9b32036..2517002f9 100644
--- a/conan/tools/files/__init__.py
+++ b/conan/tools/files/__init__.py
@@ -1,7 +1,7 @@
 from conan.tools.files.files import load, save, mkdir, rmdir, rm, ftp_download, download, get, \
     rename, chdir, unzip, replace_in_file, collect_libs, check_md5, check_sha1, check_sha256
 
-from conan.tools.files.patches import patch, apply_conandata_patches
+from conan.tools.files.patches import patch, apply_conandata_patches, export_conandata_patches
 from conan.tools.files.cpp_package import CppPackage
 from conan.tools.files.packager import AutoPackager
 from conan.tools.files.symlinks import symlinks
diff --git a/conan/tools/files/files.py b/conan/tools/files/files.py
index e8eac166d..e46e02b2f 100644
--- a/conan/tools/files/files.py
+++ b/conan/tools/files/files.py
@@ -78,7 +78,7 @@ def rm(conanfile, pattern, folder, recursive=False):
             break
 
 
-def get(conanfile, url, md5='', sha1='', sha256='', destination=".", filename="",
+def get(conanfile, url, md5=None, sha1=None, sha256=None, destination=".", filename="",
         keep_permissions=False, pattern=None, verify=True, retry=None, retry_wait=None,
         auth=None, headers=None, strip_root=False):
     """ high level downloader + unzipper + (optional hash checker) + delete temporary zip
@@ -124,7 +124,7 @@ def ftp_download(conanfile, ip, filename, login='', password=''):
 
 
 def download(conanfile, url, filename, verify=True, retry=None, retry_wait=None,
-             auth=None, headers=None, md5='', sha1='', sha256=''):
+             auth=None, headers=None, md5=None, sha1=None, sha256=None):
     """Retrieves a file from a given URL into a file with a given filename.
        It uses certificates from a list of known verifiers for https downloads,
        but this can be optionally disabled.
@@ -167,7 +167,8 @@ 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
         if file_url.startswith("file:"):
-            _copy_local_file_from_uri(conanfile, url=file_url, file_path=filename, md5=md5, sha1=sha1, sha256=sha256)
+            _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,
@@ -188,17 +189,19 @@ 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:
+    if md5 is not None:
         check_md5(conanfile, file_path, md5)
-    if sha1:
+    if sha1 is not None:
         check_sha1(conanfile, file_path, sha1)
-    if sha256:
+    if sha256 is not None:
         check_sha256(conanfile, file_path, sha256)
 
+
 def _path_from_file_uri(uri):
     path = urlparse(uri).path
     return url2pathname(path)
diff --git a/conan/tools/files/patches.py b/conan/tools/files/patches.py
index 7c5587e61..14753403b 100644
--- a/conan/tools/files/patches.py
+++ b/conan/tools/files/patches.py
@@ -1,14 +1,11 @@
 import logging
 import os
+import shutil
 
 import patch_ng
 
 from conans.errors import ConanException
-
-try:
-    from collections.abc import Iterable
-except ImportError:
-    from collections import Iterable
+from conans.util.files import mkdir
 
 
 class PatchLogHandler(logging.Handler):
@@ -97,16 +94,21 @@ def apply_conandata_patches(conanfile):
         - patch_file: "patches/0001-buildflatbuffers-cmake.patch"
     ```
     """
+    if conanfile.conan_data is None:
+        raise ConanException("conandata.yml not defined")
 
     patches = conanfile.conan_data.get('patches')
+    if patches is None:
+        conanfile.output.info("apply_conandata_patches(): No patches defined in conandata")
+        return
 
     if isinstance(patches, dict):
         assert conanfile.version, "Can only be applied if conanfile.version is already defined"
         entries = patches.get(conanfile.version, [])
-    elif isinstance(patches, Iterable):
+    elif isinstance(patches, list):
         entries = patches
     else:
-        return
+        raise ConanException("conandata.yml 'patches' should be a list or a dict {version: list}")
     for it in entries:
         if "patch_file" in it:
             # The patch files are located in the root src
@@ -118,3 +120,33 @@ def apply_conandata_patches(conanfile):
         else:
             raise ConanException("The 'conandata.yml' file needs a 'patch_file' or 'patch_string'"
                                  " entry for every patch to be applied")
+
+
+def export_conandata_patches(conanfile):
+    """
+    Exports patches stored in 'conanfile.conan_data' (read from 'conandata.yml' file). It will export
+    all the patches under 'patches' entry that matches the given 'conanfile.version'. If versions are
+    not defined in 'conandata.yml' it will export all the patches directly under 'patches' keyword.
+    """
+    if conanfile.conan_data is None:
+        raise ConanException("conandata.yml not defined")
+
+    patches = conanfile.conan_data.get('patches')
+    if patches is None:
+        conanfile.output.info("export_conandata_patches(): No patches defined in conandata")
+        return
+
+    if isinstance(patches, dict):
+        assert conanfile.version, "Can only be exported if conanfile.version is already defined"
+        entries = patches.get(conanfile.version, [])
+    elif isinstance(patches, list):
+        entries = patches
+    else:
+        raise ConanException("conandata.yml 'patches' should be a list or a dict {version: list}")
+    for it in entries:
+        patch_file = it.get("patch_file")
+        if patch_file:
+            src = os.path.join(conanfile.recipe_folder, patch_file)
+            dst = os.path.join(conanfile.export_sources_folder, patch_file)
+            mkdir(os.path.dirname(dst))
+            shutil.copy2(src, dst)
diff --git a/conan/tools/gnu/autotoolstoolchain.py b/conan/tools/gnu/autotoolstoolchain.py
index a46ed30cf..c747f7903 100644
--- a/conan/tools/gnu/autotoolstoolchain.py
+++ b/conan/tools/gnu/autotoolstoolchain.py
@@ -63,7 +63,7 @@ class AutotoolsToolchain:
             # Apple Stuff
             if os_build == "Macos":
                 sdk_path = apple_sdk_path(conanfile)
-                apple_arch = to_apple_arch(self._conanfile.settings.get_safe("arch"))
+                apple_arch = to_apple_arch(self._conanfile)
                 # https://man.archlinux.org/man/clang.1.en#Target_Selection_Options
                 self.apple_arch_flag = "-arch {}".format(apple_arch) if apple_arch else None
                 # -isysroot makes all includes for your library relative to the build directory
diff --git a/conan/tools/meson/toolchain.py b/conan/tools/meson/toolchain.py
index fa5f52cf3..2daa47d2a 100644
--- a/conan/tools/meson/toolchain.py
+++ b/conan/tools/meson/toolchain.py
@@ -244,7 +244,7 @@ class MesonToolchain(object):
             raise ConanException("Please, specify a suitable value for os.sdk.")
 
         # Calculating the main Apple flags
-        arch = to_apple_arch(self._conanfile.settings.get_safe("arch"))
+        arch = to_apple_arch(self._conanfile)
         self.apple_arch_flag = ["-arch", arch] if arch else []
         self.apple_isysroot_flag = ["-isysroot", sdk_path] if sdk_path else []
         self.apple_min_version_flag = [apple_min_version_flag(self._conanfile)]
diff --git a/conan/tools/microsoft/visual.py b/conan/tools/microsoft/visual.py
index d3f948ad5..307efff1e 100644
--- a/conan/tools/microsoft/visual.py
+++ b/conan/tools/microsoft/visual.py
@@ -226,12 +226,17 @@ def _vcvars_vers(conanfile, compiler, vs_version):
     return vcvars_ver
 
 
-def is_msvc(conanfile):
+def is_msvc(conanfile, build_context=False):
     """ Validate if current compiler in setttings is 'Visual Studio' or 'msvc'
     :param conanfile: ConanFile instance
+    :param build_context: If True, will use the settings from the build context, not host ones
     :return: True, if the host compiler is related to Visual Studio, otherwise, False.
     """
-    settings = conanfile.settings
+    # FIXME: 2.0: remove "hasattr()" condition
+    if not build_context or not hasattr(conanfile, "settings_build"):
+        settings = conanfile.settings
+    else:
+        settings = conanfile.settings_build
     return settings.get_safe("compiler") in ["Visual Studio", "msvc"]
 
 
diff --git a/conans/client/conf/config_installer.py b/conans/client/conf/config_installer.py
index 4e54c6b9b..434868531 100644
--- a/conans/client/conf/config_installer.py
+++ b/conans/client/conf/config_installer.py
@@ -108,9 +108,10 @@ def _process_file(directory, filename, config, cache, output, folder):
         finally:
             _filecopy(directory, filename, cache.cache_folder)
             migrate_registry_file(cache, output)
+    # FIXME: remove in 2.0
     elif filename == "remotes.json":
-        # Fix for Conan 2.0
-        raise ConanException("remotes.json install is not supported yet. Use 'remotes.txt'")
+        output.info("Defining remotes from remotes.json")
+        _filecopy(directory, filename, cache.cache_folder)
     else:
         # This is ugly, should be removed in Conan 2.0
         if filename in ("README.md", "LICENSE.txt") and not directory.startswith("templates"):
@@ -137,6 +138,7 @@ def _process_folder(config, folder, cache, output):
         raise ConanException("No such directory: '%s'" % str(folder))
     if config.source_folder:
         folder = os.path.join(folder, config.source_folder)
+
     for root, dirs, files in walk(folder):
         dirs[:] = [d for d in dirs if d != ".git"]
         for f in files:
diff --git a/conans/client/downloaders/file_downloader.py b/conans/client/downloaders/file_downloader.py
index 659461810..0611d73af 100644
--- a/conans/client/downloaders/file_downloader.py
+++ b/conans/client/downloaders/file_downloader.py
@@ -16,11 +16,11 @@ from conans.util.tracer import log_download
 
 
 def check_checksum(file_path, md5, sha1, sha256):
-    if md5:
+    if md5 is not None:
         check_md5(file_path, md5)
-    if sha1:
+    if sha1 is not None:
         check_sha1(file_path, sha1)
-    if sha256:
+    if sha256 is not None:
         check_sha256(file_path, sha256)
 
 
diff --git a/conans/client/loader.py b/conans/client/loader.py
index d1d89130d..804d73b50 100644
--- a/conans/client/loader.py
+++ b/conans/client/loader.py
@@ -2,6 +2,7 @@ import fnmatch
 import imp
 import inspect
 import os
+import re
 import sys
 import types
 import uuid
@@ -436,13 +437,20 @@ def _parse_conanfile(conan_file_path):
         old_modules = list(sys.modules.keys())
         with chdir(current_dir):
             old_dont_write_bytecode = sys.dont_write_bytecode
-            sys.dont_write_bytecode = True
-            loaded = imp.load_source(module_id, conan_file_path)
-            sys.dont_write_bytecode = old_dont_write_bytecode
-
-        required_conan_version = getattr(loaded, "required_conan_version", None)
-        if required_conan_version:
-            validate_conan_version(required_conan_version)
+            try:
+                sys.dont_write_bytecode = True
+                # FIXME: imp is deprecated in favour of implib
+                loaded = imp.load_source(module_id, conan_file_path)
+                sys.dont_write_bytecode = old_dont_write_bytecode
+            except ImportError:
+                version_txt = _get_required_conan_version_without_loading(conan_file_path)
+                if version_txt:
+                    validate_conan_version(version_txt)
+                raise
+
+            required_conan_version = getattr(loaded, "required_conan_version", None)
+            if required_conan_version:
+                validate_conan_version(required_conan_version)
 
         # These lines are necessary, otherwise local conanfile imports with same name
         # collide, but no error, and overwrite other packages imports!!
@@ -475,3 +483,20 @@ def _parse_conanfile(conan_file_path):
         sys.path.pop(0)
 
     return loaded, module_id
+
+
+def _get_required_conan_version_without_loading(conan_file_path):
+    # First, try to detect the required_conan_version in "text" mode
+    # https://github.com/conan-io/conan/issues/11239
+    contents = load(conan_file_path)
+
+    txt_version = None
+
+    try:
+        found = re.search(r"required_conan_version\s*=\s*(.*)", contents)
+        if found:
+            txt_version = found.group(1).replace('"', "")
+    except:
+        pass
+
+    return txt_version
diff --git a/conans/client/manager.py b/conans/client/manager.py
index 620d58a92..f27fba832 100644
--- a/conans/client/manager.py
+++ b/conans/client/manager.py
@@ -77,6 +77,16 @@ def deps_install(app, ref_or_path, install_folder, base_folder, graph_info, remo
     except ConanException:  # Setting os doesn't exist
         pass
 
+    if hasattr(conanfile, "layout") and not test:
+        conanfile.folders.set_base_folders(conanfile_path, output_folder)
+    else:
+        conanfile.folders.set_base_install(install_folder)
+        conanfile.folders.set_base_imports(install_folder)
+        conanfile.folders.set_base_generators(base_folder)
+
+    if hasattr(conanfile, "layout") and test:
+        install_folder = conanfile.generators_folder
+
     installer = BinaryInstaller(app, recorder=recorder)
     # TODO: Extract this from the GraphManager, reuse same object, check args earlier
     build_modes = BuildMode(build_modes, out)
@@ -95,13 +105,6 @@ def deps_install(app, ref_or_path, install_folder, base_folder, graph_info, remo
                                      interactive=manifest_interactive)
         manifest_manager.print_log()
 
-    if hasattr(conanfile, "layout") and not test:
-        conanfile.folders.set_base_folders(conanfile_path, output_folder)
-    else:
-        conanfile.folders.set_base_install(install_folder)
-        conanfile.folders.set_base_imports(install_folder)
-        conanfile.folders.set_base_generators(base_folder)
-
     output = conanfile.output if root_node.recipe != RECIPE_VIRTUAL else out
 
     if conanfile.info.invalid:
@@ -142,3 +145,5 @@ def deps_install(app, ref_or_path, install_folder, base_folder, graph_info, remo
             deploy_conanfile = neighbours[0].conanfile
             if hasattr(deploy_conanfile, "deploy") and callable(deploy_conanfile.deploy):
                 run_deploy(deploy_conanfile, install_folder)
+
+    return install_folder
diff --git a/conans/client/tools/net.py b/conans/client/tools/net.py
index c11bb9961..f6ba4c562 100644
--- a/conans/client/tools/net.py
+++ b/conans/client/tools/net.py
@@ -6,7 +6,7 @@ from conans.errors import ConanException
 from conans.util.fallbacks import default_output, default_requester
 
 
-def get(url, md5='', sha1='', sha256='', destination=".", filename="", keep_permissions=False,
+def get(url, md5=None, sha1=None, sha256=None, destination=".", filename="", keep_permissions=False,
         pattern=None, requester=None, output=None, verify=True, retry=None, retry_wait=None,
         overwrite=False, auth=None, headers=None, strip_root=False):
     """ high level downloader + unzipper + (optional hash checker) + delete temporary zip
@@ -51,7 +51,7 @@ def ftp_download(ip, filename, login='', password=''):
 
 
 def download(url, filename, verify=True, out=None, retry=None, retry_wait=None, overwrite=False,
-             auth=None, headers=None, requester=None, md5='', sha1='', sha256=''):
+             auth=None, headers=None, requester=None, md5=None, sha1=None, sha256=None):
     """Retrieves a file from a given URL into a file with a given filename.
        It uses certificates from a list of known verifiers for https downloads,
        but this can be optionally disabled.
diff --git a/conans/model/manifest.py b/conans/model/manifest.py
index d323df770..1b260beaf 100644
--- a/conans/model/manifest.py
+++ b/conans/model/manifest.py
@@ -25,7 +25,8 @@ def gather_files(folder):
     symlinks = {}
     keep_python = get_env("CONAN_KEEP_PYTHON_FILES", False)
     for root, dirs, files in walk(folder):
-        dirs[:] = [d for d in dirs if d != "__pycache__"]  # Avoid recursing pycache
+        if not keep_python:
+            dirs[:] = [d for d in dirs if d != "__pycache__"]  # Avoid recursing pycache
         for d in dirs:
             abs_path = os.path.join(root, d)
             if os.path.islink(abs_path):
diff --git a/conans/server/conf/__init__.py b/conans/server/conf/__init__.py
index 9ee1c2e81..2b484447d 100644
--- a/conans/server/conf/__init__.py
+++ b/conans/server/conf/__init__.py
@@ -53,6 +53,7 @@ class ConanServerConfigParser(ConfigParser):
                            "public_port": get_env("CONAN_SERVER_PUBLIC_PORT", None, environment),
                            "host_name": get_env("CONAN_HOST_NAME", None, environment),
                            "custom_authenticator": get_env("CONAN_CUSTOM_AUTHENTICATOR", None, environment),
+                           "custom_authorizer": get_env("CONAN_CUSTOM_AUTHORIZER", None, environment),
                            # "user:pass,user2:pass2"
                            "users": get_env("CONAN_SERVER_USERS", None, environment)}
 
@@ -168,6 +169,13 @@ class ConanServerConfigParser(ConfigParser):
         except ConanException:
             return None
 
+    @property
+    def custom_authorizer(self):
+        try:
+            return self._get_conf_server_string("custom_authorizer")
+        except ConanException:
+            return None
+
     @property
     def users(self):
         def validate_pass_encoding(password):
diff --git a/conans/server/conf/default_server_conf.py b/conans/server/conf/default_server_conf.py
index ac09bae96..41510240f 100644
--- a/conans/server/conf/default_server_conf.py
+++ b/conans/server/conf/default_server_conf.py
@@ -27,6 +27,12 @@ updown_secret: {updown_secret}
 #
 # custom_authenticator: my_authenticator
 
+# Check docs.conan.io to implement a different authorizer plugin for conan_server
+# if custom_authorizer is not specified, the [read_permissions] and [write_permissions]
+# sections will be used to grant/reject access.
+#
+# custom_authorizer: my_authorizer
+
 # name/version@user/channel: user1, user2, user3
 #
 # The rules are applied in order. 
diff --git a/conans/server/launcher.py b/conans/server/launcher.py
index e424b407b..ce663e6ba 100644
--- a/conans/server/launcher.py
+++ b/conans/server/launcher.py
@@ -9,7 +9,7 @@ from conans.server.conf import get_server_store
 from conans.server.crypto.jwt.jwt_credentials_manager import JWTCredentialsManager
 from conans.server.crypto.jwt.jwt_updown_manager import JWTUpDownAuthManager
 from conans.server.migrate import migrate_and_get_server_config
-from conans.server.plugin_loader import load_authentication_plugin
+from conans.server.plugin_loader import load_authentication_plugin, load_authorization_plugin
 from conans.server.rest.server import ConanServer
 
 from conans.server.service.authorize import BasicAuthorizer, BasicAuthenticator
@@ -29,14 +29,19 @@ class ServerLauncher(object):
         server_config = migrate_and_get_server_config(
             user_folder, self.force_migration, server_dir is not None
         )
-        custom_auth = server_config.custom_authenticator
-        if custom_auth:
-            authenticator = load_authentication_plugin(server_folder, custom_auth)
+        custom_authn = server_config.custom_authenticator
+        if custom_authn:
+            authenticator = load_authentication_plugin(server_folder, custom_authn)
         else:
             authenticator = BasicAuthenticator(dict(server_config.users))
 
-        authorizer = BasicAuthorizer(server_config.read_permissions,
-                                     server_config.write_permissions)
+        custom_authz = server_config.custom_authorizer
+        if custom_authz:
+            authorizer = load_authorization_plugin(server_folder, custom_authz)
+        else:
+            authorizer = BasicAuthorizer(server_config.read_permissions,
+                                         server_config.write_permissions)
+
         credentials_manager = JWTCredentialsManager(server_config.jwt_secret,
                                                     server_config.jwt_expire_time)
 
diff --git a/conans/server/plugin_loader.py b/conans/server/plugin_loader.py
index f266c8c6a..255c30249 100644
--- a/conans/server/plugin_loader.py
+++ b/conans/server/plugin_loader.py
@@ -1,18 +1,26 @@
 import os
 
 
-def load_authentication_plugin(server_folder, plugin_name):
+def load_plugin(server_folder, plugin_type, plugin_name):
     try:
         from pluginbase import PluginBase
-        plugin_base = PluginBase(package="plugins/authenticator")
-        plugins_dir = os.path.join(server_folder, "plugins", "authenticator")
+        plugin_base = PluginBase(package="plugins/%s" % plugin_type)
+        plugins_dir = os.path.join(server_folder, "plugins", plugin_type)
         plugin_source = plugin_base.make_plugin_source(
                         searchpath=[plugins_dir])
-        auth = plugin_source.load_plugin(plugin_name).get_class()
+        plugin = plugin_source.load_plugin(plugin_name).get_class()
         # it is necessary to keep a reference to the plugin, otherwise it is removed
         # and some imports fail
-        auth.plugin_source = plugin_source
-        return auth
+        plugin.plugin_source = plugin_source
+        return plugin
     except Exception:
-        print("Error loading authenticator plugin '%s'" % plugin_name)
+        print("Error loading %s plugin '%s'" % (plugin_type, plugin_name))
         raise
+
+
+def load_authentication_plugin(server_folder, plugin_name):
+    return load_plugin(server_folder, "authenticator", plugin_name)
+
+
+def load_authorization_plugin(server_folder, plugin_name):
+    return load_plugin(server_folder, "authorizer", plugin_name)
diff --git a/conans/server/service/authorize.py b/conans/server/service/authorize.py
index 1456133d1..56636f29b 100644
--- a/conans/server/service/authorize.py
+++ b/conans/server/service/authorize.py
@@ -43,6 +43,14 @@ class Authorizer(object):
         """
         raise NotImplemented()
 
+    @abstractmethod
+    def check_delete_conan(self, username, ref):
+        """
+        username: User requesting a recipe's deletion
+        ref: ConanFileReference
+        """
+        raise NotImplemented()
+
     @abstractmethod
     def check_read_package(self, username, pref):
         """
@@ -59,6 +67,14 @@ class Authorizer(object):
         """
         raise NotImplemented()
 
+    @abstractmethod
+    def check_delete_package(self, username, pref):
+        """
+        username: User requesting a package's deletion
+        pref: PackageReference
+        """
+        raise NotImplemented()
+
 
 @six.add_metaclass(ABCMeta)
 class Authenticator(object):


Test patch:
diff --git a/conans/client/cmd/test.py b/conans/client/cmd/test.py
index e7f031bda..4c2f580dd 100644
--- a/conans/client/cmd/test.py
+++ b/conans/client/cmd/test.py
@@ -32,28 +32,28 @@ def install_build_and_test(app, conanfile_abs_path, reference, graph_info,
     if build_modes is None:
         build_modes = ["never"]
     try:
-        deps_install(app=app,
-                     create_reference=reference,
-                     ref_or_path=conanfile_abs_path,
-                     install_folder=test_build_folder,
-                     base_folder=test_build_folder,
-                     remotes=remotes,
-                     graph_info=graph_info,
-                     update=update,
-                     build_modes=build_modes,
-                     manifest_folder=manifest_folder,
-                     manifest_verify=manifest_verify,
-                     manifest_interactive=manifest_interactive,
-                     keep_build=keep_build,
-                     recorder=recorder,
-                     require_overrides=require_overrides,
-                     conanfile_path=os.path.dirname(conanfile_abs_path),
-                     test=True  # To keep legacy test_package_layout
-                     )
+        install_folder = deps_install(app=app,
+                                      create_reference=reference,
+                                      ref_or_path=conanfile_abs_path,
+                                      install_folder=test_build_folder,
+                                      base_folder=test_build_folder,
+                                      remotes=remotes,
+                                      graph_info=graph_info,
+                                      update=update,
+                                      build_modes=build_modes,
+                                      manifest_folder=manifest_folder,
+                                      manifest_verify=manifest_verify,
+                                      manifest_interactive=manifest_interactive,
+                                      keep_build=keep_build,
+                                      recorder=recorder,
+                                      require_overrides=require_overrides,
+                                      conanfile_path=os.path.dirname(conanfile_abs_path),
+                                      test=True  # To keep legacy test_package_layout
+                                      )
         cmd_build(app, conanfile_abs_path, test_build_folder,
                   source_folder=base_folder, build_folder=test_build_folder,
                   package_folder=os.path.join(test_build_folder, "package"),
-                  install_folder=test_build_folder, test=reference)
+                  install_folder=install_folder, test=reference)
     finally:
         if delete_after_build:
             # Required for windows where deleting the cwd is not possible.
diff --git a/conans/test/conftest.py b/conans/test/conftest.py
index be571fd11..5c0cc4996 100644
--- a/conans/test/conftest.py
+++ b/conans/test/conftest.py
@@ -34,6 +34,9 @@ tools_locations = {
 }
 """
 
+MacOS_arm = all([platform.system() == "Darwin", platform.machine() == "arm64"])
+homebrew_root = "/opt/homebrew" if MacOS_arm else "/usr/local"
+
 
 tools_locations = {
     "clang": {"disabled": True},
@@ -47,7 +50,9 @@ tools_locations = {
         "0.28": {
             "path": {
                 # Using chocolatey in Windows -> choco install pkgconfiglite --version 0.28
-                'Windows': "C:/ProgramData/chocolatey/lib/pkgconfiglite/tools/pkg-config-lite-0.28-1/bin"
+                'Windows': "C:/ProgramData/chocolatey/lib/pkgconfiglite/tools/pkg-config-lite-0.28-1/bin",
+                'Darwin': f"{homebrew_root}/bin/pkg-config",
+                'Linux': "/usr/bin/pkg-config"
             }
         }},
     'autotools': {"exe": "autoconf"},
@@ -86,6 +91,14 @@ tools_locations = {
             "path": {'Windows': 'C:/Tools/ninja/1.10.2'}
         }
     },
+    # This is the non-msys2 mingw, which is 32 bits x86 arch
+    'mingw': {
+        "disabled": True,
+        "platform": "Windows",
+        "default": "system",
+        "exe": "mingw32-make",
+        "system": {"path": {'Windows': "C:/mingw"}},
+    },
     'mingw32': {
         "platform": "Windows",
         "default": "system",
@@ -174,16 +187,13 @@ try:
 except ImportError as e:
     user_tool_locations = None
 
-if platform.machine() == "arm64":
-    android_ndk_path = "/opt/homebrew/share/android-ndk"
-else:
-    android_ndk_path = "/usr/local/share/android-ndk"
+
 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': android_ndk_path}}
+    'android_ndk': {'Darwin': {'TEST_CONAN_ANDROID_NDK': f"{homebrew_root}/share/android-ndk"}}
 }
 
 
diff --git a/conans/test/functional/command/config_install_test.py b/conans/test/functional/command/config_install_test.py
index 7d438307e..400ea596d 100644
--- a/conans/test/functional/command/config_install_test.py
+++ b/conans/test/functional/command/config_install_test.py
@@ -333,13 +333,56 @@ class Pkg(ConanFile):
         self.client.run("remote list")
         self.assertEqual("conan.io: https://server.conan.io [Verify SSL: True]\n", self.client.out)
 
-    def test_install_remotes_json_error(self):
+    def test_install_remotes_json(self):
         folder = temp_folder()
-        save_files(folder, {"remotes.json": ""})
-        self.client.run('config install "%s"' % folder, assert_error=True)
-        self.assertIn("ERROR: Failed conan config install: "
-                      "remotes.json install is not supported yet. Use 'remotes.txt'",
-                      self.client.out)
+
+        remotes_json = textwrap.dedent("""
+            {
+                "remotes": [
+                    { "name": "repojson1", "url": "https://repojson1.net", "verify_ssl": false },
+                    { "name": "repojson2", "url": "https://repojson2.com", "verify_ssl": true }
+                ]
+            }
+        """)
+
+        remotes_txt = textwrap.dedent("""\
+            repotxt1 https://repotxt1.net False
+            repotxt2 https://repotxt2.com True
+        """)
+
+        # remotes.txt and json try to define both the remotes,
+        # could lead to unpredictable results
+        save_files(folder, {"remotes.json": remotes_json,
+                            "remotes.txt": remotes_txt})
+
+        self.client.run(f'config install "{folder}"')
+        assert "Defining remotes from remotes.json" in self.client.out
+        assert "Defining remotes from remotes.txt" in self.client.out
+
+        # If there's only a remotes.txt it's the one installed
+        folder = temp_folder()
+        save_files(folder, {"remotes.txt": remotes_txt})
+
+        self.client.run(f'config install "{folder}"')
+
+        assert "Defining remotes from remotes.txt" in self.client.out
+
+        self.client.run('remote list')
+
+        assert "repotxt1: https://repotxt1.net [Verify SSL: False]" in self.client.out
+        assert "repotxt2: https://repotxt2.com [Verify SSL: True]" in self.client.out
+
+        # If there's only a remotes.json it's the one installed
+        folder = temp_folder()
+        save_files(folder, {"remotes.json": remotes_json})
+
+        self.client.run(f'config install "{folder}"')
+        assert "Defining remotes from remotes.json" in self.client.out
+
+        self.client.run('remote list')
+
+        assert "repojson1: https://repojson1.net [Verify SSL: False]" in self.client.out
+        assert "repojson2: https://repojson2.com [Verify SSL: True]" in self.client.out
 
     def test_without_profile_folder(self):
         shutil.rmtree(self.client.cache.profiles_path)
diff --git a/conans/test/functional/command/test_command_test.py b/conans/test/functional/command/test_command_test.py
index ff97e0b37..970e50aaf 100644
--- a/conans/test/functional/command/test_command_test.py
+++ b/conans/test/functional/command/test_command_test.py
@@ -1,3 +1,4 @@
+import os
 import time
 import unittest
 
@@ -25,3 +26,5 @@ class ConanTestTest(unittest.TestCase):
         client.run("test test_package hello/0.1@lasote/stable -s hello:build_type=Debug "
                    "--build missing")
         self.assertIn('hello/0.1: Hello World Debug!', client.out)
+        assert os.path.exists(os.path.join(client.current_folder, "test_package",
+                                           "build", "generators", "conaninfo.txt"))
diff --git a/conans/test/functional/layout/test_exports_sources.py b/conans/test/functional/layout/test_exports_sources.py
index 2602c70eb..8ee76788d 100644
--- a/conans/test/functional/layout/test_exports_sources.py
+++ b/conans/test/functional/layout/test_exports_sources.py
@@ -5,7 +5,7 @@ from conans.test.utils.tools import TestClient
 
 def test_exports_sources_patch():
     """
-    tests that using ``self.base_source_folder`` we can access both from the source() and build()
+    tests that using ``self.export_sources_folder`` we can access both from the source() and build()
     methods the folder where the exported sources (patches, new build files) are. And maintain
     a local flow without exports copies
     """
diff --git a/conans/test/functional/subsystems_build_test.py b/conans/test/functional/subsystems_build_test.py
index 4faa08d36..eade2dc34 100644
--- a/conans/test/functional/subsystems_build_test.py
+++ b/conans/test/functional/subsystems_build_test.py
@@ -85,12 +85,12 @@ class TestSubsystems:
 class TestSubsystemsBuild:
 
     @staticmethod
-    def _build(client, static_runtime=None):
+    def _build(client, static_runtime=None, make="make"):
         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(make)
         client.run_command("app")
 
     @pytest.mark.parametrize("static", [True, False])
@@ -111,6 +111,22 @@ class TestSubsystemsBuild:
         check_vs_runtime("app.exe", client, "15", "Debug", static_runtime=static,
                          subsystem="msys2")
 
+    @pytest.mark.parametrize("static", [True, False])
+    @pytest.mark.tool_mingw
+    def test_mingw(self, static):
+        """
+        This will work if you installed the Mingw toolchain outside msys2, from
+        https://sourceforge.net/projects/mingw/, and installed gcc, autotools, mingw32-make, etc
+
+        But this doesn't contain "make", only "mingw32-make"
+        """
+        client = TestClient()
+        self._build(client, static_runtime=static, make="mingw32-make")
+
+        check_exe_run(client.out, "main", "gcc", None, "Debug", "x86", None, subsystem="mingw32")
+        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_mingw64
diff --git a/conans/test/functional/test_third_party_patch_flow.py b/conans/test/functional/test_third_party_patch_flow.py
index 5b2efb8bd..9065494d4 100644
--- a/conans/test/functional/test_third_party_patch_flow.py
+++ b/conans/test/functional/test_third_party_patch_flow.py
@@ -44,6 +44,10 @@ def test_third_party_patch_flow():
                  "conandata.yml": ""})
     client.run("install .")
     client.run("source .")
+    assert "apply_conandata_patches(): No patches defined in conandata" in client.out
+
+    client.save({"conandata.yml": "patches: {}"})
+    client.run("source .")
     client.run("build .", assert_error=True)
     assert "MISTAKE1 BUILD!" in client.out
 
diff --git a/conans/test/functional/toolchains/gnu/autotools/test_apple_toolchain.py b/conans/test/functional/toolchains/gnu/autotools/test_apple_toolchain.py
index 5836c8e04..c9217eac7 100644
--- a/conans/test/functional/toolchains/gnu/autotools/test_apple_toolchain.py
+++ b/conans/test/functional/toolchains/gnu/autotools/test_apple_toolchain.py
@@ -4,7 +4,7 @@ import platform
 
 import pytest
 
-from conan.tools.apple.apple import to_apple_arch
+from conans.client.tools.apple import to_apple_arch
 from conans.test.assets.autotools import gen_makefile
 from conans.test.assets.sources import gen_function_h, gen_function_cpp
 from conans.test.utils.tools import TestClient
diff --git a/conans/test/functional/toolchains/gnu/test_v2_autotools_template.py b/conans/test/functional/toolchains/gnu/test_v2_autotools_template.py
index 0e72f4d03..81685594a 100644
--- a/conans/test/functional/toolchains/gnu/test_v2_autotools_template.py
+++ b/conans/test/functional/toolchains/gnu/test_v2_autotools_template.py
@@ -93,7 +93,7 @@ def test_autotools_relocatable_libs_darwin():
     shutil.move(os.path.join(package_folder, "lib"), os.path.join(client.current_folder, "tempfolder"))
     # will fail because rpath does not exist
     client.run_command("test_package/build-release/main", assert_error=True)
-    assert "Library not loaded: @rpath/libhello.0.dylib" in client.out
+    assert "Library not loaded: @rpath/libhello.0.dylib" in str(client.out).replace("'", "")
 
     # Use DYLD_LIBRARY_PATH and should run
     client.run_command("DYLD_LIBRARY_PATH={} test_package/build-release/main".format(os.path.join(client.current_folder, "tempfolder")))
diff --git a/conans/test/functional/tools/test_files.py b/conans/test/functional/tools/test_files.py
index 02fa7e516..6951636c3 100644
--- a/conans/test/functional/tools/test_files.py
+++ b/conans/test/functional/tools/test_files.py
@@ -340,3 +340,61 @@ def test_relate_base_path_all_versions(mock_patch_ng):
 
     assert mock_patch_ng.apply_args[0].endswith(os.path.join('source_subfolder', "relative_dir"))
     assert mock_patch_ng.apply_args[1:] == (0, False)
+
+
+def test_export_conandata_patches(mock_patch_ng):
+    conanfile = textwrap.dedent("""
+        import os
+        from conan import ConanFile
+        from conan.tools.files import export_conandata_patches, load
+
+        class Pkg(ConanFile):
+            name = "mypkg"
+            version = "1.0"
+
+            def layout(self):
+                self.folders.source = "source_subfolder"
+
+            def export_sources(self):
+                export_conandata_patches(self)
+
+            def source(self):
+                self.output.info(load(self, os.path.join(self.export_sources_folder,
+                                                         "patches/mypatch.patch")))
+        """)
+    conandata_yml = textwrap.dedent("""
+        patches:
+          - patch_file: "patches/mypatch.patch"
+    """)
+
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run("create .", assert_error=True)
+    assert "conandata.yml not defined" in client.out
+    # Empty conandata
+    client.save({"conandata.yml": ""})
+    client.run("create .", assert_error=True)
+    assert "export_conandata_patches(): No patches defined in conandata" in client.out
+    assert "ERROR: mypkg/1.0: Error in source() method, line 18" in client.out
+    # wrong patches
+    client.save({"conandata.yml": "patches: 123"})
+    client.run("create .", assert_error=True)
+    assert "conandata.yml 'patches' should be a list or a dict"  in client.out
+
+    # No patch found
+    client.save({"conandata.yml": conandata_yml})
+    client.run("create .", assert_error=True)
+    assert "No such file or directory" in client.out
+
+    client.save({"patches/mypatch.patch": "mypatch!!!"})
+    client.run("create .")
+    assert "mypkg/1.0: mypatch!!!" in client.out
+
+    conandata_yml = textwrap.dedent("""
+        patches:
+            "1.0":
+                - patch_file: "patches/mypatch.patch"
+    """)
+    client.save({"conandata.yml": conandata_yml})
+    client.run("create .")
+    assert "mypkg/1.0: mypatch!!!" in client.out
diff --git a/conans/test/integration/conanfile/required_conan_version_test.py b/conans/test/integration/conanfile/required_conan_version_test.py
index 7f0c11f25..f0261c11b 100644
--- a/conans/test/integration/conanfile/required_conan_version_test.py
+++ b/conans/test/integration/conanfile/required_conan_version_test.py
@@ -35,3 +35,45 @@ class RequiredConanVersionTest(unittest.TestCase):
         client.run("install pkg/1.0@", assert_error=True)
         self.assertIn("Current Conan version (%s) does not satisfy the defined one (>=100.0)"
                       % __version__, client.out)
+
+    def test_required_conan_version_with_loading_issues(self):
+        # https://github.com/conan-io/conan/issues/11239
+        client = TestClient()
+        conanfile = textwrap.dedent("""
+                    from conan import missing_import
+
+                    required_conan_version = ">=100.0"
+
+                    class Lib(ConanFile):
+                        pass
+                    """)
+        client.save({"conanfile.py": conanfile})
+        client.run("export . pkg/1.0@", assert_error=True)
+        self.assertIn("Current Conan version (%s) does not satisfy the defined one (>=100.0)"
+                      % __version__, client.out)
+
+        # Assigning required_conan_version without spaces
+        conanfile = textwrap.dedent("""
+                            from conan import missing_import
+
+                            required_conan_version=">=100.0"
+
+                            class Lib(ConanFile):
+                                pass
+                            """)
+        client.save({"conanfile.py": conanfile})
+        client.run("export . pkg/1.0@", assert_error=True)
+        self.assertIn("Current Conan version (%s) does not satisfy the defined one (>=100.0)"
+                      % __version__, client.out)
+
+        # If the range is correct, everything works, of course
+        conanfile = textwrap.dedent("""
+                            from conan import ConanFile
+
+                            required_conan_version = ">1.0.0"
+
+                            class Lib(ConanFile):
+                                pass
+                            """)
+        client.save({"conanfile.py": conanfile})
+        client.run("export . pkg/1.0@")
diff --git a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
index d22ee397a..8b6f35791 100644
--- a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -466,7 +466,7 @@ def test_toolchain_cache_variables():
     client.save({"conanfile.py": conanfile})
     with mock.patch("platform.system", mock.MagicMock(return_value="Windows")):
         client.run("install . mylib/1.0@ -c tools.cmake.cmaketoolchain:generator='MinGW Makefiles' "
-                   "-c tools.gnu:make_program='MyMake'")
+                   "-c tools.gnu:make_program='MyMake' -c tools.build:skip_test=True")
     presets = json.loads(client.load("CMakePresets.json"))
     cache_variables = presets["configurePresets"][0]["cacheVariables"]
     assert cache_variables["foo"] == 'ON'
@@ -475,6 +475,7 @@ def test_toolchain_cache_variables():
     assert cache_variables["CMAKE_SH"] == "THIS VALUE HAS PRIORITY"
     assert cache_variables["CMAKE_POLICY_DEFAULT_CMP0091"] == "THIS VALUE HAS PRIORITY"
     assert cache_variables["CMAKE_MAKE_PROGRAM"] == "MyMake"
+    assert cache_variables["BUILD_TESTING"] == 'OFF'
 
 
 def test_android_c_library():
diff --git a/conans/test/unittests/model/manifest_test.py b/conans/test/unittests/model/manifest_test.py
index 0945a0650..4a88b65b1 100644
--- a/conans/test/unittests/model/manifest_test.py
+++ b/conans/test/unittests/model/manifest_test.py
@@ -1,6 +1,6 @@
 import os
 
-
+from conans.client.tools import environment_append
 from conans.model.manifest import FileTreeManifest
 from conans.test.utils.test_files import temp_folder
 from conans.util.files import load, md5, save
@@ -49,3 +49,17 @@ class TestManifest:
         save(os.path.join(tmp_dir, "conanmanifest.txt"), "1478122267\nsome: file.py: 123\n")
         read_manifest = FileTreeManifest.load(tmp_dir)
         assert read_manifest.file_sums["some: file.py"] == "123"
+
+
+def test_pycache_included():
+    tmp_dir = temp_folder()
+    files = {"__pycache__/damn.py": "binarythings",
+             "pythonfile.pyc": "binarythings3"}
+    for filename, content in files.items():
+        save(os.path.join(tmp_dir, filename), content)
+
+    with environment_append({"CONAN_KEEP_PYTHON_FILES": "1"}):
+        manifest = FileTreeManifest.create(tmp_dir)
+    manifest = repr(manifest)
+    assert "pythonfile.pyc" in manifest
+    assert "__pycache__/damn.py" in manifest
diff --git a/conans/test/unittests/tools/apple/test_apple_tools.py b/conans/test/unittests/tools/apple/test_apple_tools.py
new file mode 100644
index 000000000..4146d0e50
--- /dev/null
+++ b/conans/test/unittests/tools/apple/test_apple_tools.py
@@ -0,0 +1,24 @@
+from conans.test.utils.mocks import ConanFileMock, MockSettings
+from conan.tools.apple import is_apple_os, to_apple_arch
+
+def test_tools_apple_is_apple_os():
+    conanfile = ConanFileMock()
+    
+    conanfile.settings = MockSettings({"os": "Macos"})
+    assert is_apple_os(conanfile) == True
+
+    conanfile.settings = MockSettings({"os": "watchOS"})
+    assert is_apple_os(conanfile) == True
+
+    conanfile.settings = MockSettings({"os": "Windows"})
+    assert is_apple_os(conanfile) == False
+
+    
+def test_tools_apple_to_apple_arch():
+    conanfile = ConanFileMock()
+    
+    conanfile.settings = MockSettings({"arch": "armv8"})
+    assert to_apple_arch(conanfile) == "arm64"
+
+    conanfile.settings = MockSettings({"arch": "x86_64"})
+    assert to_apple_arch(conanfile) == "x86_64"
diff --git a/conans/test/unittests/tools/microsoft/test_msbuild.py b/conans/test/unittests/tools/microsoft/test_msbuild.py
index 89afb227a..82bceeaf9 100644
--- a/conans/test/unittests/tools/microsoft/test_msbuild.py
+++ b/conans/test/unittests/tools/microsoft/test_msbuild.py
@@ -197,6 +197,21 @@ def test_is_msvc(compiler, expected):
     assert is_msvc(conanfile) == expected
 
 
+def test_is_msvc_build():
+    settings = Settings({"build_type": ["Release"],
+                         "compiler": ["gcc", "msvc"],
+                         "os": ["Windows"],
+                         "arch": ["x86_64"]})
+    conanfile = ConanFile(Mock(), None)
+    conanfile.settings = "os", "compiler", "build_type", "arch"
+    conanfile.initialize(settings, EnvValues())
+    conanfile.settings.compiler = "gcc"
+    conanfile.settings_build = conanfile.settings.copy()
+    conanfile.settings_build.compiler = "msvc"
+    assert is_msvc(conanfile) is False
+    assert is_msvc(conanfile, build_context=True) is True
+
+
 @pytest.mark.parametrize("compiler,shared,runtime,build_type,expected", [
     ("Visual Studio", True, "MT", "Release", True),
     ("msvc", True, "static", "Release", True),