Repository: conan-io/conan
Pull request #11818 (2022-08-09 07:30:35)

xcodedeps: provide package root dir
If one wants to access other folders than the provided ones (like
resources), the provided variables dont contain enough information
to access these folders.

Provide a new variable, `PACKAGE_ROOT_<package>_<component>` pointing
to the root directory of the package allowing to use it within xcode.

We have to pass the package folder as another parameter, because the
`transitive_cpp_info` variable does not contain the `rootpath`
attribute and we also cant access the package_folder attribute from
`self._conanfile.dependencies.package_folder` directly because
the package name is already formatted via `_format_name`.

Changelog: (Feature): XcodeDeps: Add `PACKAGE_ROOT_<package>_<component>` to generated xcconfig files
Docs: https://github.com/conan-io/docs/pull/XXXX

- [ ] Refer to the issue that supports this Pull Request.
- [ ] 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>


czoido: Hi @the-nic,

Thanks for the contribution.

I think that maybe it would be better to use the resdirs information from the cppinfo so that if a package have a directory for resources it is available. And a variable something like `RES_PATHS_libname_libname[...][...][...] = ...`

Is there a case where you need to access directly the package folder that is not covered by the case that you add the resdir to the cppinfo and have access to it?

the-nic: > I think that maybe it would be better to use the resdirs information from the cppinfo so that if a package have a directory for resources it is available. And a variable something like `RESOURCE_PATHS_libname_libname[...][...][...] = ...`

Certainly, this would be a good addition, too.

> Is there a case where you need to access directly the package folder that is not covered by the case that you add the resdir to the cppinfo and have access to it?

The problem is, these are often multiple paths and if I have to reference a specific file from the package, it its impossible to do without the single root path. Additionally, the cmake generator also provides a package root path, so they become a bit more on par.
the-nic: > Hi @the-nic, I was thinking that probably it would be better to define this variable in the file that includes all components for the package instead here, and not with the component suffix but just as: `PACKAGE_ROOT_<package>` because that information will be the same for all the components. WDYT?

Good point, I'll update the PR
the-nic: Hi @czoido I tried your suggestion right now and the problem is that the even though the information is the same for all components, its not the same for all configurations. And only the component files seem to be configuration-aware. Otherwise, we'd have to update the package file on each time a new configuration is added...
czoido: Hi @the-nic,
Sorry, you are right, I did not take that into account. Then maybe it's better to just leave them in the components files, but remove the `_{{comp_name}}` and let them be defined in multiple times (I suppose it won't be a problem, but I had to test that first). 
the-nic: @czoido  I have removed the component name from the root dir variable.
czoido: Thanks a lot @the-nic,
I'll do some checks and then I think it's good to merge.
Maybe in the future if we add more variables at package level it would be worth to make a separate file that defines them all and that is included from the file that includes the components, but if the multiple definition is not a problem I think this is good enough for the moment.
Thanks!

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/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/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/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/model/requires.py b/conans/model/requires.py
index 7f577236f..e36cfabe9 100644
--- a/conans/model/requires.py
+++ b/conans/model/requires.py
@@ -180,7 +180,7 @@ class Requirements(OrderedDict):
             new_reqs[name] = req
         return new_reqs
 
-    def __call__(self, reference, private=False, override=False):
+    def __call__(self, reference, private=False, override=False, **kwargs):
         self.add(reference, private, override)
 
     def __repr__(self):
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/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/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/integration/conan_v2/test_requires_traits.py b/conans/test/integration/conan_v2/test_requires_traits.py
new file mode 100644
index 000000000..17414a3ea
--- /dev/null
+++ b/conans/test/integration/conan_v2/test_requires_traits.py
@@ -0,0 +1,24 @@
+import textwrap
+
+from conans.test.assets.genconanfile import GenConanfile
+from conans.test.utils.tools import TestClient
+
+
+def test_requires_traits():
+    """ traits can be passed to self.requires(), but:
+    - They have absolutely no effect
+    - They are not checked to be correct or match expected 2.0 traits
+    """
+    c = TestClient()
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            def requirements(self):
+                self.requires("dep/1.0", transitive_headers=True)
+        """)
+    c.save({"dep/conanfile.py": GenConanfile("dep", "1.0"),
+            "consumer/conanfile.py": conanfile})
+    c.run("create dep")
+    # This should not crash
+    c.run("install consumer")
+    assert "dep/1.0" in c.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"