Repository: conan-io/conan
Pull request #16449 (2024-06-10 08:07:55)

Qbs: add explicit resolve method
Most tools have a separate configuration step and Qbs is no different. It's strange that the API doesn't provide a separate method for this. Add it now so that users can understand better what went wrong during which stage.

Changelog: (Feature): Added resolve() method to the Qbs toolchain 
Docs: Omit

- [ ] 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/develop2/.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.



Patch:
diff --git a/conan/api/conan_api.py b/conan/api/conan_api.py
index 83760430c..0a922501f 100644
--- a/conan/api/conan_api.py
+++ b/conan/api/conan_api.py
@@ -22,7 +22,7 @@ from conans.client.migrations import ClientMigrator
 from conans.client.userio import init_colorama
 from conans.errors import ConanException
 from conans.model.version import Version
-from conans.paths import get_conan_user_home
+from conan.internal.paths import get_conan_user_home
 
 
 class ConanAPI(object):
diff --git a/conan/cli/commands/build.py b/conan/cli/commands/build.py
index 2212e5f79..bf1543a86 100644
--- a/conan/cli/commands/build.py
+++ b/conan/cli/commands/build.py
@@ -23,7 +23,8 @@ def build(conan_api, parser, *args):
     parser.add_argument("-of", "--output-folder",
                         help='The root output folder for generated and build files')
     parser.add_argument("-d", "--deployer", action="append",
-                        help='Deploy using the provided deployer to the output folder')
+                        help="Deploy using the provided deployer to the output folder. "
+                             "Built-in deployers: 'full_deploy', 'direct_deploy', 'runtime_deploy'")
     parser.add_argument("--deployer-folder",
                         help="Deployer output folder, base build folder by default if not set")
     parser.add_argument("--build-require", action='store_true', default=False,
diff --git a/conan/cli/commands/graph.py b/conan/cli/commands/graph.py
index c5fb5f168..85d7cb510 100644
--- a/conan/cli/commands/graph.py
+++ b/conan/cli/commands/graph.py
@@ -175,7 +175,10 @@ def graph_info(conan_api, parser, subparser, *args):
     subparser.add_argument("--package-filter", action="append",
                            help='Print information only for packages that match the patterns')
     subparser.add_argument("-d", "--deployer", action="append",
-                           help='Deploy using the provided deployer to the output folder')
+                           help="Deploy using the provided deployer to the output folder. "
+                                "Built-in deployers: 'full_deploy', 'direct_deploy'. Deployers "
+                                "will only deploy recipes, as 'conan graph info' do not retrieve "
+                                "binaries")
     subparser.add_argument("-df", "--deployer-folder",
                            help="Deployer output folder, base build folder by default if not set")
     subparser.add_argument("--build-require", action='store_true', default=False,
diff --git a/conan/cli/commands/install.py b/conan/cli/commands/install.py
index cad3168a5..703e022ea 100644
--- a/conan/cli/commands/install.py
+++ b/conan/cli/commands/install.py
@@ -30,7 +30,8 @@ def install(conan_api, parser, *args):
     parser.add_argument("-of", "--output-folder",
                         help='The root output folder for generated and build files')
     parser.add_argument("-d", "--deployer", action="append",
-                        help='Deploy using the provided deployer to the output folder')
+                        help="Deploy using the provided deployer to the output folder. "
+                             "Built-in deployers: 'full_deploy', 'direct_deploy', 'runtime_deploy'")
     parser.add_argument("--deployer-folder",
                         help="Deployer output folder, base build folder by default if not set")
     parser.add_argument("--deployer-package", action="append",
diff --git a/conan/internal/api/new/cmake_lib.py b/conan/internal/api/new/cmake_lib.py
index 56ea66935..a92a93052 100644
--- a/conan/internal/api/new/cmake_lib.py
+++ b/conan/internal/api/new/cmake_lib.py
@@ -32,7 +32,18 @@ class {{package_name}}Recipe(ConanFile):
 
     def layout(self):
         cmake_layout(self)
-
+    {% if requires is defined %}
+    def requirements(self):
+        {% for require in requires -%}
+        self.requires("{{ require }}")
+        {% endfor %}
+    {%- endif %}
+    {%- if tool_requires is defined %}
+    def build_requirements(self):
+        {% for require in tool_requires -%}
+        self.tool_requires("{{ require }}")
+        {% endfor %}
+    {%- endif %}
     def generate(self):
         deps = CMakeDeps(self)
         deps.generate()
@@ -51,20 +62,6 @@ class {{package_name}}Recipe(ConanFile):
     def package_info(self):
         self.cpp_info.libs = ["{{name}}"]
 
-    {% if requires is defined -%}
-    def requirements(self):
-        {% for require in requires -%}
-        self.requires("{{ require }}")
-        {% endfor %}
-    {%- endif %}
-
-    {% if tool_requires is defined -%}
-    def build_requirements(self):
-        {% for require in tool_requires -%}
-        self.tool_requires("{{ require }}")
-        {% endfor %}
-    {%- endif %}
-
 '''
 
 cmake_v2 = """cmake_minimum_required(VERSION 3.15)
@@ -270,12 +267,6 @@ project(PackageTest CXX)
 
 find_package({{name}} CONFIG REQUIRED)
 
-{% if requires is defined -%}
-{% for require in requires -%}
-find_package({{as_name(require)}} CONFIG REQUIRED)
-{% endfor %}
-{%- endif %}
-
 add_executable(example src/example.cpp)
 target_link_libraries(example {{name}}::{{name}})
 """
diff --git a/conan/internal/api/new/qbs_lib.py b/conan/internal/api/new/qbs_lib.py
index 7af4b071d..a933b5435 100644
--- a/conan/internal/api/new/qbs_lib.py
+++ b/conan/internal/api/new/qbs_lib.py
@@ -21,6 +21,7 @@ class {{package_name}}Recipe(ConanFile):
         qbs = Qbs(self)
         qbs_config = {"products.{{name}}.isShared": "true" if self.options.shared else "false"}
         qbs.add_configuration("default", qbs_config)
+        qbs.resolve()
         qbs.build()
 
     def package(self):
@@ -67,6 +68,7 @@ class {{package_name}}TestConan(ConanFile):
 
     def build(self):
         qbs = Qbs(self)
+        qbs.resolve()
         qbs.build()
         qbs.install()
 
diff --git a/conan/internal/cache/conan_reference_layout.py b/conan/internal/cache/conan_reference_layout.py
index 3a4aab9b0..9933dcbbb 100644
--- a/conan/internal/cache/conan_reference_layout.py
+++ b/conan/internal/cache/conan_reference_layout.py
@@ -2,7 +2,7 @@ import os
 from contextlib import contextmanager
 
 from conans.model.manifest import FileTreeManifest
-from conans.paths import CONANFILE, DATA_YML
+from conan.internal.paths import CONANFILE, DATA_YML
 from conans.util.files import set_dirty, clean_dirty, is_dirty, rmdir
 
 
diff --git a/conan/internal/deploy.py b/conan/internal/deploy.py
index 9631645ff..d5f04693d 100644
--- a/conan/internal/deploy.py
+++ b/conan/internal/deploy.py
@@ -1,3 +1,4 @@
+import filecmp
 import os
 import shutil
 
@@ -31,7 +32,8 @@ def _find_deployer(d, cache_deploy_folder):
     if os.path.isfile(cache_path):
         return _load(cache_path)
     builtin_deploy = {"full_deploy.py": full_deploy,
-                      "direct_deploy.py": direct_deploy}.get(d)
+                      "direct_deploy.py": direct_deploy,
+                      "runtime_deploy.py": runtime_deploy}.get(d)
     if builtin_deploy is not None:
         return builtin_deploy
     raise ConanException(f"Cannot find deployer '{d}'")
@@ -87,6 +89,75 @@ def full_deploy(graph, output_folder):
         _deploy_single(dep, conanfile, output_folder, folder_name)
 
 
+def runtime_deploy(graph, output_folder):
+    """
+    Deploy all the shared libraries and the executables of the dependencies in a flat directory.
+    """
+    conanfile = graph.root.conanfile
+    output = ConanOutput(scope="runtime_deploy")
+    output.info(f"Deploying dependencies runtime to folder: {output_folder}")
+    output.warning("This deployer is experimental and subject to change. "
+                   "Please give feedback at https://github.com/conan-io/conan/issues")
+    mkdir(output_folder)
+    symlinks = conanfile.conf.get("tools.deployer:symlinks", check_type=bool, default=True)
+    for _, dep in conanfile.dependencies.host.items():
+        if dep.package_folder is None:
+            output.warning(f"{dep.ref} does not have any package folder, skipping binary")
+            continue
+        count = 0
+        cpp_info = dep.cpp_info.aggregated_components()
+        for bindir in cpp_info.bindirs:
+            if not os.path.isdir(bindir):
+                output.warning(f"{dep.ref} {bindir} does not exist")
+                continue
+            count += _flatten_directory(dep, bindir, output_folder, symlinks)
+
+        for libdir in cpp_info.libdirs:
+            if not os.path.isdir(libdir):
+                output.warning(f"{dep.ref} {libdir} does not exist")
+                continue
+            count += _flatten_directory(dep, libdir, output_folder, symlinks, [".dylib", ".so"])
+
+        output.info(f"Copied {count} files from {dep.ref}")
+    conanfile.output.success(f"Runtime deployed to folder: {output_folder}")
+
+
+def _flatten_directory(dep, src_dir, output_dir, symlinks, extension_filter=None):
+    """
+    Copy all the files from the source directory in a flat output directory.
+    An optional string, named extension_filter, can be set to copy only the files with
+    the listed extensions.
+    """
+    file_count = 0
+
+    output = ConanOutput(scope="runtime_deploy")
+    for src_dirpath, _, src_filenames in os.walk(src_dir, followlinks=symlinks):
+        for src_filename in src_filenames:
+            if extension_filter and not any(src_filename.endswith(ext) for ext in extension_filter):
+                continue
+
+            src_filepath = os.path.join(src_dirpath, src_filename)
+            dest_filepath = os.path.join(output_dir, src_filename)
+            if os.path.exists(dest_filepath):
+                if filecmp.cmp(src_filepath, dest_filepath):  # Be efficient, do not copy
+                    output.verbose(f"{dest_filepath} exists with same contents, skipping copy")
+                    continue
+                else:
+                    output.warning(f"{dest_filepath} exists and will be overwritten")
+
+            try:
+                file_count += 1
+                shutil.copy2(src_filepath, dest_filepath, follow_symlinks=symlinks)
+                output.verbose(f"Copied {src_filepath} into {output_dir}")
+            except Exception as e:
+                if "WinError 1314" in str(e):
+                    ConanOutput().error("runtime_deploy: Windows symlinks require admin privileges "
+                                        "or 'Developer mode = ON'", error_type="exception")
+                raise ConanException(f"runtime_deploy: Copy of '{dep}' files failed: {e}.\nYou can "
+                                     f"use 'tools.deployer:symlinks' conf to disable symlinks")
+    return file_count
+
+
 def _deploy_single(dep, conanfile, output_folder, folder_name):
     new_folder = os.path.join(output_folder, folder_name)
     rmdir(new_folder)
diff --git a/conan/tools/apple/xcodetoolchain.py b/conan/tools/apple/xcodetoolchain.py
index 5af04498a..c9e565c43 100644
--- a/conan/tools/apple/xcodetoolchain.py
+++ b/conan/tools/apple/xcodetoolchain.py
@@ -109,7 +109,7 @@ class XcodeToolchain(object):
 
     @property
     def _check_if_extra_flags(self):
-        return self._global_cflags or self._global_cxxflags or self._global_ldflags
+        return self._global_cflags or self._global_cxxflags or self._global_ldflags or self._global_defines
 
     @property
     def _flags_xcconfig_content(self):
diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py
index 34021ece8..6d15ac0b1 100644
--- a/conan/tools/cmake/toolchain/blocks.py
+++ b/conan/tools/cmake/toolchain/blocks.py
@@ -564,8 +564,8 @@ class FindFiles(Block):
         return host_runtime_dirs
 
     def _join_paths(self, paths):
-        paths = [relativize_path(p, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}") for p in paths]
         paths = [p.replace('\\', '/').replace('$', '\\$').replace('"', '\\"') for p in paths]
+        paths = [relativize_path(p, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}") for p in paths]
         return " ".join([f'"{p}"' for p in paths])
 
     def context(self):
diff --git a/conan/tools/qbs/qbs.py b/conan/tools/qbs/qbs.py
index be3f8ebac..b6159e2bc 100644
--- a/conan/tools/qbs/qbs.py
+++ b/conan/tools/qbs/qbs.py
@@ -44,7 +44,26 @@ class Qbs(object):
             '--file', self._project_file,
         ]
 
+    def resolve(self, parallel=True):
+        args = self._get_common_arguments()
+
+        if parallel:
+            args.extend(['--jobs', '%s' % self.jobs])
+        else:
+            args.extend(['--jobs', '1'])
+
+        if self.profile:
+            args.append('profile:%s' % self.profile)
+
+        for name in self._configuration:
+            config = self._configuration[name]
+            args.extend(_configuration_dict_to_commandlist(name, config))
+
+        cmd = 'qbs resolve %s' % cmd_args_to_string(args)
+        self._conanfile.run(cmd)
+
     def _build(self, products, all_products):
+
         args = self._get_common_arguments()
         args.append('--no-install')
 
@@ -55,12 +74,8 @@ class Qbs(object):
 
         args.extend(['--jobs', '%s' % self.jobs])
 
-        if self.profile:
-            args.append('profile:%s' % self.profile)
-
         for name in self._configuration:
-            config = self._configuration[name]
-            args.extend(_configuration_dict_to_commandlist(name, config))
+            args.append('config:%s' % name)
 
         cmd = 'qbs build %s' % cmd_args_to_string(args)
         self._conanfile.run(cmd)
diff --git a/conans/client/cmd/export.py b/conans/client/cmd/export.py
index 93ae8bc26..b503ac474 100644
--- a/conans/client/cmd/export.py
+++ b/conans/client/cmd/export.py
@@ -7,7 +7,7 @@ from conan.tools.scm import Git
 from conans.errors import ConanException, conanfile_exception_formatter
 from conans.model.manifest import FileTreeManifest
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import DATA_YML
+from conan.internal.paths import DATA_YML
 from conans.util.files import is_dirty, rmdir, set_dirty, mkdir, clean_dirty, chdir
 
 
diff --git a/conans/client/cmd/uploader.py b/conans/client/cmd/uploader.py
index 424e100ff..cff1385a9 100644
--- a/conans/client/cmd/uploader.py
+++ b/conans/client/cmd/uploader.py
@@ -6,7 +6,7 @@ from conan.internal.conan_app import ConanApp
 from conan.api.output import ConanOutput
 from conans.client.source import retrieve_exports_sources
 from conans.errors import ConanException, NotFoundException
-from conans.paths import (CONAN_MANIFEST, CONANFILE, EXPORT_SOURCES_TGZ_NAME,
+from conan.internal.paths import (CONAN_MANIFEST, CONANFILE, EXPORT_SOURCES_TGZ_NAME,
                           EXPORT_TGZ_NAME, PACKAGE_TGZ_NAME, CONANINFO)
 from conans.util.files import (clean_dirty, is_dirty, gather_files,
                                gzopen_without_timestamps, set_dirty_context_manager, mkdir,
diff --git a/conans/client/conanfile/package.py b/conans/client/conanfile/package.py
index 3ee44ab39..12745e6bd 100644
--- a/conans/client/conanfile/package.py
+++ b/conans/client/conanfile/package.py
@@ -4,7 +4,7 @@ from conan.api.output import ConanOutput
 from conans.errors import ConanException, conanfile_exception_formatter, conanfile_remove_attr
 from conans.model.manifest import FileTreeManifest
 from conans.model.package_ref import PkgReference
-from conans.paths import CONANINFO
+from conan.internal.paths import CONANINFO
 from conans.util.files import save, mkdir, chdir
 
 
diff --git a/conans/client/installer.py b/conans/client/installer.py
index ce4755728..20198cc03 100644
--- a/conans/client/installer.py
+++ b/conans/client/installer.py
@@ -13,7 +13,7 @@ from conans.client.source import retrieve_exports_sources, config_source
 from conans.errors import (ConanException, conanfile_exception_formatter, conanfile_remove_attr)
 from conans.model.build_info import CppInfo, MockInfoProperty
 from conans.model.package_ref import PkgReference
-from conans.paths import CONANINFO
+from conan.internal.paths import CONANINFO
 from conans.util.files import clean_dirty, is_dirty, mkdir, rmdir, save, set_dirty, chdir
 
 
diff --git a/conans/client/loader.py b/conans/client/loader.py
index 65f0c61bd..3eae33874 100644
--- a/conans/client/loader.py
+++ b/conans/client/loader.py
@@ -20,7 +20,7 @@ from conans.errors import ConanException, NotFoundException, conanfile_exception
 from conans.model.conan_file import ConanFile
 from conans.model.options import Options
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import DATA_YML
+from conan.internal.paths import DATA_YML
 from conans.util.files import load, chdir, load_user_encoded
 
 
diff --git a/conans/client/migrations.py b/conans/client/migrations.py
index 6c56d7035..74267a5b3 100644
--- a/conans/client/migrations.py
+++ b/conans/client/migrations.py
@@ -56,7 +56,9 @@ class ClientMigrator(Migrator):
         if old_version and old_version < "2.0.14-":
             _migrate_pkg_db_lru(self.cache_folder, old_version)
 
-        if old_version and old_version < "2.4":
+        # let the back migration files be stored
+        # if there was not a previous install (old_version==None)
+        if old_version is None or old_version < "2.4":
             _migrate_default_compatibility(self.cache_folder)
 
 
diff --git a/conans/client/remote_manager.py b/conans/client/remote_manager.py
index 51ce2e2d1..9aea240a6 100644
--- a/conans/client/remote_manager.py
+++ b/conans/client/remote_manager.py
@@ -16,7 +16,7 @@ from conans.model.info import load_binary_info
 from conans.model.package_ref import PkgReference
 from conans.model.recipe_ref import RecipeReference
 from conans.util.files import rmdir, human_size
-from conans.paths import EXPORT_SOURCES_TGZ_NAME, EXPORT_TGZ_NAME, PACKAGE_TGZ_NAME
+from conan.internal.paths import EXPORT_SOURCES_TGZ_NAME, EXPORT_TGZ_NAME, PACKAGE_TGZ_NAME
 from conans.util.files import mkdir, tar_extract
 
 
diff --git a/conans/client/rest/rest_client_v2.py b/conans/client/rest/rest_client_v2.py
index cc05c3038..2e46de8de 100644
--- a/conans/client/rest/rest_client_v2.py
+++ b/conans/client/rest/rest_client_v2.py
@@ -11,7 +11,7 @@ from conans.client.rest.rest_client_common import RestCommonMethods, get_excepti
 from conans.errors import ConanException, NotFoundException, PackageNotFoundException, \
     RecipeNotFoundException, AuthenticationException, ForbiddenException
 from conans.model.package_ref import PkgReference
-from conans.paths import EXPORT_SOURCES_TGZ_NAME
+from conan.internal.paths import EXPORT_SOURCES_TGZ_NAME
 from conans.util.dates import from_iso8601_to_timestamp
 from conans.util.thread import ExceptionThread
 
diff --git a/conans/model/manifest.py b/conans/model/manifest.py
index c3c10737f..06ec8daa5 100644
--- a/conans/model/manifest.py
+++ b/conans/model/manifest.py
@@ -1,7 +1,7 @@
 import os
 from collections import defaultdict
 
-from conans.paths import CONAN_MANIFEST, EXPORT_SOURCES_TGZ_NAME, EXPORT_TGZ_NAME, PACKAGE_TGZ_NAME
+from conan.internal.paths import CONAN_MANIFEST, EXPORT_SOURCES_TGZ_NAME, EXPORT_TGZ_NAME, PACKAGE_TGZ_NAME
 from conans.util.dates import timestamp_now, timestamp_to_str
 from conans.util.files import load, md5, md5sum, save, gather_files
 
diff --git a/conans/search/search.py b/conans/search/search.py
index aae814937..c0adea1e7 100644
--- a/conans/search/search.py
+++ b/conans/search/search.py
@@ -8,7 +8,7 @@ from conans.errors import ConanException
 from conans.model.info import load_binary_info
 from conans.model.package_ref import PkgReference
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import CONANINFO
+from conan.internal.paths import CONANINFO
 from conans.search.query_parse import evaluate_postfix, infix_to_postfix
 from conans.util.files import load
 
diff --git a/conans/server/conf/__init__.py b/conans/server/conf/__init__.py
index 348ee124d..a1247e10a 100644
--- a/conans/server/conf/__init__.py
+++ b/conans/server/conf/__init__.py
@@ -9,7 +9,7 @@ from datetime import timedelta
 from configparser import ConfigParser, NoSectionError
 
 from conans.errors import ConanException
-from conans.paths import conan_expand_user
+from conan.internal.paths import conan_expand_user
 from conans.server.conf.default_server_conf import default_server_conf
 from conans.server.store.disk_adapter import ServerDiskAdapter
 from conans.server.store.server_store import ServerStore
diff --git a/conans/server/launcher.py b/conans/server/launcher.py
index 7fde4d390..85247ad83 100644
--- a/conans/server/launcher.py
+++ b/conans/server/launcher.py
@@ -3,7 +3,7 @@ import os
 import sys
 
 from conans import REVISIONS
-from conans.paths import conan_expand_user
+from conan.internal.paths import conan_expand_user
 from conans.server import SERVER_CAPABILITIES
 from conans.server.conf import get_server_store
 
diff --git a/conans/server/service/v2/search.py b/conans/server/service/v2/search.py
index 0a02134de..66ec092ff 100644
--- a/conans/server/service/v2/search.py
+++ b/conans/server/service/v2/search.py
@@ -6,7 +6,7 @@ from fnmatch import translate
 from conans.errors import ForbiddenException, RecipeNotFoundException
 from conans.model.package_ref import PkgReference
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import CONANINFO
+from conan.internal.paths import CONANINFO
 from conans.search.search import _partial_match
 from conans.server.utils.files import list_folder_subdirs
 from conans.util.files import load
diff --git a/conans/server/service/v2/service_v2.py b/conans/server/service/v2/service_v2.py
index 021c2c402..be37b5d68 100644
--- a/conans/server/service/v2/service_v2.py
+++ b/conans/server/service/v2/service_v2.py
@@ -4,7 +4,7 @@ import os
 from bottle import FileUpload, static_file
 
 from conans.errors import RecipeNotFoundException, PackageNotFoundException, NotFoundException
-from conans.paths import CONAN_MANIFEST
+from conan.internal.paths import CONAN_MANIFEST
 from conans.model.package_ref import PkgReference
 from conans.server.service.mime import get_mime_type
 from conans.server.store.server_store import ServerStore
diff --git a/conans/server/store/server_store.py b/conans/server/store/server_store.py
index f103ba2a3..e79e956a3 100644
--- a/conans/server/store/server_store.py
+++ b/conans/server/store/server_store.py
@@ -2,7 +2,7 @@ import os
 from os.path import join, normpath, relpath
 
 from conans.errors import ConanException, PackageNotFoundException, RecipeNotFoundException
-from conans.paths import CONAN_MANIFEST
+from conan.internal.paths import CONAN_MANIFEST
 from conans.model.package_ref import PkgReference
 from conans.model.recipe_ref import RecipeReference
 from conans.server.revision_list import RevisionList
diff --git a/setup.py b/setup.py
index 45d16b892..3c14b1b87 100644
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@ from os import path
 
 # The tests utils are used by conan-package-tools
 here = path.abspath(path.dirname(__file__))
-excluded_test_packages = ["test"]
+excluded_test_packages = ["test*"]
 
 
 def get_requires(filename):


Test patch:
diff --git a/conans/paths/__init__.py b/conan/internal/paths.py
similarity index 100%
rename from conans/paths/__init__.py
rename to conan/internal/paths.py
diff --git a/conan/test/utils/test_files.py b/conan/test/utils/test_files.py
index c35f556ed..fa88b3bf2 100644
--- a/conan/test/utils/test_files.py
+++ b/conan/test/utils/test_files.py
@@ -11,7 +11,7 @@ from io import BytesIO
 from conan.tools.files.files import untargz
 from conans.client.subsystems import get_cased_path
 from conans.errors import ConanException
-from conans.paths import PACKAGE_TGZ_NAME
+from conan.internal.paths import PACKAGE_TGZ_NAME
 from conans.util.files import gzopen_without_timestamps
 
 
diff --git a/test/functional/command/config_install_test.py b/test/functional/command/config_install_test.py
index 073baa852..5e5d668c5 100644
--- a/test/functional/command/config_install_test.py
+++ b/test/functional/command/config_install_test.py
@@ -10,7 +10,7 @@ from mock import patch
 from conan.api.model import Remote
 from conans.client.conf.config_installer import _hide_password
 from conans.client.downloaders.file_downloader import FileDownloader
-from conans.paths import DEFAULT_CONAN_HOME
+from conan.internal.paths import DEFAULT_CONAN_HOME
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.file_server import TestFileServer
 from conan.test.utils.test_files import scan_folder, temp_folder, tgz_with_contents
diff --git a/test/functional/command/test_install_deploy.py b/test/functional/command/test_install_deploy.py
index 24bb1bd63..1967e4b48 100644
--- a/test/functional/command/test_install_deploy.py
+++ b/test/functional/command/test_install_deploy.py
@@ -435,3 +435,57 @@ def test_not_deploy_absolute_paths():
 
     env = c.load("conanbuildenv-release-x86_64.sh")
     assert f'export MYPATH="{some_abs_path}/mypath"' in env
+
+
+class TestRuntimeDeployer:
+    def test_runtime_deploy(self):
+        c = TestClient()
+        conanfile = textwrap.dedent("""
+           from conan import ConanFile
+           from conan.tools.files import copy
+           class Pkg(ConanFile):
+               def package(self):
+                   copy(self, "*.so", src=self.build_folder, dst=self.package_folder)
+                   copy(self, "*.dll", src=self.build_folder, dst=self.package_folder)
+           """)
+        c.save({"pkga/conanfile.py": conanfile,
+                "pkga/lib/pkga.so": "",
+                "pkga/bin/pkga.dll": "",
+                "pkgb/conanfile.py": conanfile,
+                "pkgb/lib/pkgb.so": ""})
+        c.run("export-pkg pkga --name=pkga --version=1.0")
+        c.run("export-pkg pkgb --name=pkgb --version=1.0")
+        c.run("install --requires=pkga/1.0 --requires=pkgb/1.0 --deployer=runtime_deploy "
+              "--deployer-folder=myruntime -vvv")
+
+        expected = sorted(["pkga.so", "pkgb.so", "pkga.dll"])
+        assert sorted(os.listdir(os.path.join(c.current_folder, "myruntime"))) == expected
+
+    def test_runtime_deploy_components(self):
+        c = TestClient()
+        conanfile = textwrap.dedent("""
+            import os
+            from conan import ConanFile
+            from conan.tools.files import copy
+            class Pkg(ConanFile):
+               def package(self):
+                   copy(self, "*.so", src=self.build_folder,
+                        dst=os.path.join(self.package_folder, "a"))
+                   copy(self, "*.dll", src=self.build_folder,
+                        dst=os.path.join(self.package_folder, "b"))
+               def package_info(self):
+                   self.cpp_info.components["a"].libdirs = ["a"]
+                   self.cpp_info.components["b"].bindirs = ["b"]
+           """)
+        c.save({"pkga/conanfile.py": conanfile,
+                "pkga/lib/pkga.so": "",
+                "pkga/bin/pkga.dll": "",
+                "pkgb/conanfile.py": conanfile,
+                "pkgb/lib/pkgb.so": ""})
+        c.run("export-pkg pkga --name=pkga --version=1.0")
+        c.run("export-pkg pkgb --name=pkgb --version=1.0")
+        c.run("install --requires=pkga/1.0 --requires=pkgb/1.0 --deployer=runtime_deploy "
+              "--deployer-folder=myruntime -vvv")
+
+        expected = sorted(["pkga.so", "pkgb.so", "pkga.dll"])
+        assert sorted(os.listdir(os.path.join(c.current_folder, "myruntime"))) == expected
diff --git a/test/functional/toolchains/qbs/test_qbs.py b/test/functional/toolchains/qbs/test_qbs.py
index 3bb0ea338..52ea80635 100644
--- a/test/functional/toolchains/qbs/test_qbs.py
+++ b/test/functional/toolchains/qbs/test_qbs.py
@@ -50,6 +50,7 @@ def test_qbs_all_products():
 
         def build(self):
             qbs = Qbs(self)
+            qbs.resolve()
             qbs.build_all()
         ''')
 
@@ -89,6 +90,7 @@ def test_qbs_specific_products():
 
         def build(self):
             qbs = Qbs(self)
+            qbs.resolve()
             qbs.build(products=["hello", "hello"])
         ''')
 
@@ -130,6 +132,7 @@ def test_qbs_multiple_configurations():
             qbs = Qbs(self)
             qbs.add_configuration("release", {"qbs.debugInformation": False})
             qbs.add_configuration("debug", {"qbs.debugInformation": True})
+            qbs.resolve()
             qbs.build()
         ''')
 
diff --git a/test/integration/build_requires/profile_build_requires_test.py b/test/integration/build_requires/profile_build_requires_test.py
index cda554a1e..1d7c9c4a3 100644
--- a/test/integration/build_requires/profile_build_requires_test.py
+++ b/test/integration/build_requires/profile_build_requires_test.py
@@ -3,7 +3,7 @@ import platform
 import textwrap
 import unittest
 
-from conans.paths import CONANFILE
+from conan.internal.paths import CONANFILE
 from conan.test.utils.tools import TestClient, GenConanfile
 
 
diff --git a/test/integration/command/export/export_test.py b/test/integration/command/export/export_test.py
index 33f6ff3d1..ea2485ac1 100644
--- a/test/integration/command/export/export_test.py
+++ b/test/integration/command/export/export_test.py
@@ -10,7 +10,7 @@ from parameterized import parameterized
 
 from conans.model.manifest import FileTreeManifest
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import CONANFILE, CONAN_MANIFEST
+from conan.internal.paths import CONANFILE, CONAN_MANIFEST
 from conan.test.utils.tools import TestClient, GenConanfile
 from conans.util.files import load, save
 
diff --git a/test/integration/command/export_pkg_test.py b/test/integration/command/export_pkg_test.py
index bb6f4cb13..6ed19891b 100644
--- a/test/integration/command/export_pkg_test.py
+++ b/test/integration/command/export_pkg_test.py
@@ -6,7 +6,7 @@ import textwrap
 import unittest
 from textwrap import dedent
 
-from conans.paths import CONANFILE
+from conan.internal.paths import CONANFILE
 from conan.test.utils.tools import TestClient, GenConanfile
 from conans.util.files import load
 
diff --git a/test/integration/command/info/test_info_folders.py b/test/integration/command/info/test_info_folders.py
index d29b3f56c..464a02e65 100644
--- a/test/integration/command/info/test_info_folders.py
+++ b/test/integration/command/info/test_info_folders.py
@@ -3,7 +3,7 @@ import textwrap
 import json
 import pytest
 
-from conans.paths import CONANFILE
+from conan.internal.paths import CONANFILE
 from conan.test.utils.tools import TestClient
 from conan.internal.cache.conan_reference_layout import EXPORT_FOLDER
 
diff --git a/test/integration/command/install/test_install_transitive.py b/test/integration/command/install/test_install_transitive.py
index 3f82cdb0a..017ce6c57 100644
--- a/test/integration/command/install/test_install_transitive.py
+++ b/test/integration/command/install/test_install_transitive.py
@@ -4,7 +4,7 @@ import pytest
 
 from conans.model.info import load_binary_info
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import CONANFILE_TXT, CONANINFO
+from conan.internal.paths import CONANFILE_TXT, CONANINFO
 from conan.test.utils.tools import TestClient,  GenConanfile
 from conans.util.files import save, load
 
diff --git a/test/integration/command/source_test.py b/test/integration/command/source_test.py
index d04bf0074..c856d4627 100644
--- a/test/integration/command/source_test.py
+++ b/test/integration/command/source_test.py
@@ -6,7 +6,7 @@ from collections import OrderedDict
 
 import pytest
 
-from conans.paths import CONANFILE
+from conan.internal.paths import CONANFILE
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.tools import TestClient, TestServer
 from conans.util.files import save
diff --git a/test/integration/command/test_package_test.py b/test/integration/command/test_package_test.py
index 6b8cc8f21..727fa9872 100644
--- a/test/integration/command/test_package_test.py
+++ b/test/integration/command/test_package_test.py
@@ -4,7 +4,7 @@ import textwrap
 import unittest
 
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import CONANFILE
+from conan.internal.paths import CONANFILE
 from conan.test.utils.tools import NO_SETTINGS_PACKAGE_ID, TestClient, GenConanfile
 from conans.util.files import load
 
diff --git a/test/integration/command/upload/syncronize_test.py b/test/integration/command/upload/syncronize_test.py
index ddeede375..27dd539e1 100644
--- a/test/integration/command/upload/syncronize_test.py
+++ b/test/integration/command/upload/syncronize_test.py
@@ -4,7 +4,7 @@ import unittest
 from conan.tools.files.files import untargz  # FIXME: DO not import from tools
 from conans.model.manifest import FileTreeManifest
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import EXPORT_TGZ_NAME
+from conan.internal.paths import EXPORT_TGZ_NAME
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.test_files import temp_folder
 from conan.test.utils.tools import TestClient, TestServer
diff --git a/test/integration/command/upload/upload_complete_test.py b/test/integration/command/upload/upload_complete_test.py
index 79b23a579..becb690db 100644
--- a/test/integration/command/upload/upload_complete_test.py
+++ b/test/integration/command/upload/upload_complete_test.py
@@ -3,7 +3,7 @@ import unittest
 
 from requests import ConnectionError
 
-from conans.paths import CONAN_MANIFEST
+from conan.internal.paths import CONAN_MANIFEST
 from conan.test.utils.tools import (TestClient, TestRequester, TestServer,
                                      GenConanfile)
 from conans.util.env import environment_update
diff --git a/test/integration/command/upload/upload_test.py b/test/integration/command/upload/upload_test.py
index d77ed839b..1e344ae45 100644
--- a/test/integration/command/upload/upload_test.py
+++ b/test/integration/command/upload/upload_test.py
@@ -13,7 +13,7 @@ from requests import Response
 from conans.errors import ConanException
 from conans.model.package_ref import PkgReference
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import EXPORT_SOURCES_TGZ_NAME, PACKAGE_TGZ_NAME
+from conan.internal.paths import EXPORT_SOURCES_TGZ_NAME, PACKAGE_TGZ_NAME
 from conan.test.utils.tools import NO_SETTINGS_PACKAGE_ID, TestClient, TestServer, \
     TurboTestClient, GenConanfile, TestRequester, TestingResponse
 from conans.util.files import gzopen_without_timestamps, is_dirty, save, set_dirty
diff --git a/test/integration/configuration/default_profile_test.py b/test/integration/configuration/default_profile_test.py
index 4b39c8e45..d8a98eab8 100644
--- a/test/integration/configuration/default_profile_test.py
+++ b/test/integration/configuration/default_profile_test.py
@@ -2,7 +2,7 @@ import os
 import textwrap
 import unittest
 
-from conans.paths import CONANFILE
+from conan.internal.paths import CONANFILE
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.test_files import temp_folder
 from conan.test.utils.tools import NO_SETTINGS_PACKAGE_ID, TestClient
diff --git a/test/integration/configuration/profile_test.py b/test/integration/configuration/profile_test.py
index d47dba871..c121cdf4c 100644
--- a/test/integration/configuration/profile_test.py
+++ b/test/integration/configuration/profile_test.py
@@ -9,7 +9,7 @@ from textwrap import dedent
 import pytest
 from parameterized import parameterized
 
-from conans.paths import CONANFILE
+from conan.internal.paths import CONANFILE
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.profiles import create_profile as _create_profile
 from conan.test.utils.test_files import temp_folder
diff --git a/test/integration/remote/auth_test.py b/test/integration/remote/auth_test.py
index d5cdd2333..e95969854 100644
--- a/test/integration/remote/auth_test.py
+++ b/test/integration/remote/auth_test.py
@@ -8,7 +8,7 @@ from requests.models import Response
 from conans.client.store.localdb import LocalDB
 from conans.errors import AuthenticationException
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import CONANFILE
+from conan.internal.paths import CONANFILE
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.test_files import temp_folder
 from conan.test.utils.tools import TestClient
diff --git a/test/integration/remote/download_retries_test.py b/test/integration/remote/download_retries_test.py
index 6b5e5cadf..c22176357 100644
--- a/test/integration/remote/download_retries_test.py
+++ b/test/integration/remote/download_retries_test.py
@@ -1,6 +1,6 @@
 import unittest
 
-from conans.paths import CONANFILE
+from conan.internal.paths import CONANFILE
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.tools import TestClient, TestServer, TestRequester
 
diff --git a/test/integration/remote/multi_remote_test.py b/test/integration/remote/multi_remote_test.py
index 91c21c53d..3ab4d074d 100644
--- a/test/integration/remote/multi_remote_test.py
+++ b/test/integration/remote/multi_remote_test.py
@@ -3,7 +3,7 @@ from collections import OrderedDict
 from time import sleep
 
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import CONANFILE
+from conan.internal.paths import CONANFILE
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.tools import TestClient, TestServer
 
diff --git a/test/integration/remote/rest_api_test.py b/test/integration/remote/rest_api_test.py
index 9d9e81ae5..736e48d28 100644
--- a/test/integration/remote/rest_api_test.py
+++ b/test/integration/remote/rest_api_test.py
@@ -15,7 +15,7 @@ from conans.client.userio import UserInput
 from conans.model.manifest import FileTreeManifest
 from conans.model.package_ref import PkgReference
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import CONANFILE, CONANINFO, CONAN_MANIFEST
+from conan.internal.paths import CONANFILE, CONANINFO, CONAN_MANIFEST
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.mocks import LocalDBMock
 from conan.test.utils.server_launcher import TestServerLauncher
diff --git a/test/integration/settings/settings_override_test.py b/test/integration/settings/settings_override_test.py
index c5c26f1c5..e1959a2bf 100644
--- a/test/integration/settings/settings_override_test.py
+++ b/test/integration/settings/settings_override_test.py
@@ -3,7 +3,7 @@ import os
 import pytest
 
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import CONANINFO
+from conan.internal.paths import CONANINFO
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.tools import TestClient
 from conans.util.files import load
diff --git a/test/integration/symlinks/symlinks_test.py b/test/integration/symlinks/symlinks_test.py
index 407f4c572..2ceb7bd8b 100644
--- a/test/integration/symlinks/symlinks_test.py
+++ b/test/integration/symlinks/symlinks_test.py
@@ -5,7 +5,7 @@ import textwrap
 import pytest
 
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import PACKAGE_TGZ_NAME
+from conan.internal.paths import PACKAGE_TGZ_NAME
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.test_files import temp_folder
 from conan.test.utils.tools import TestClient, TestServer, TurboTestClient
diff --git a/test/integration/test_migrations.py b/test/integration/test_migrations.py
index 73fa759e0..61b9761db 100644
--- a/test/integration/test_migrations.py
+++ b/test/integration/test_migrations.py
@@ -1,6 +1,7 @@
 import os
 import sqlite3
 import textwrap
+from unittest.mock import patch
 
 import pytest
 
@@ -8,6 +9,8 @@ from conan import conan_version
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.test_files import temp_folder
 from conan.test.utils.tools import TestClient
+from conans.client.migrations import ClientMigrator
+from conans.model.version import Version
 from conans.util.files import save, load
 
 
@@ -131,3 +134,24 @@ def test_back_migrations():
         assert not os.path.exists(os.path.join(t.cache_folder, f"file{number}.txt"))
         migration_file = os.path.join(t.cache_folder, "migrations", f"2.100.0_{number}-migrate.py")
         assert not os.path.exists(migration_file)
+
+
+def test_back_default_compatibility_migration():
+    t = TestClient()
+
+    version_txt_file_path = os.path.join(t.cache_folder, "version.txt")
+
+    # emulate a clean install, no previous version
+    os.remove(version_txt_file_path)
+
+    t.run("-v")  # Fire the backward migration
+    migration_file = os.path.join(t.cache_folder, "migrations", "2.4_1-migrate.py")
+    assert os.path.exists(migration_file)
+
+    # downgrade from a clean latest conan_version to 2.3.2
+    # simulate that we are in 2.3.2 and the old one is latest conan_version
+    migrator = ClientMigrator(t.cache_folder, Version("2.3.2"))
+
+    with patch('conan.api.conan_api.ClientMigrator', new=lambda *args, **kwargs: migrator):
+        t.run("-v")  # Fire the backward migration
+        assert f"WARN: Downgrading cache from Conan {conan_version} to 2.3.2" in t.out
diff --git a/test/integration/tgz_macos_dot_files_test.py b/test/integration/tgz_macos_dot_files_test.py
index 082a0ed84..8da10dae7 100644
--- a/test/integration/tgz_macos_dot_files_test.py
+++ b/test/integration/tgz_macos_dot_files_test.py
@@ -10,7 +10,7 @@ import pytest
 
 from conans.client.remote_manager import uncompress_file
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import EXPORT_SOURCES_TGZ_NAME
+from conan.internal.paths import EXPORT_SOURCES_TGZ_NAME
 from conan.test.utils.tools import TestClient, NO_SETTINGS_PACKAGE_ID
 
 
diff --git a/test/integration/toolchains/apple/test_xcodetoolchain.py b/test/integration/toolchains/apple/test_xcodetoolchain.py
index a7e4bee8d..ffae38c49 100644
--- a/test/integration/toolchains/apple/test_xcodetoolchain.py
+++ b/test/integration/toolchains/apple/test_xcodetoolchain.py
@@ -72,3 +72,14 @@ def test_toolchain_flags():
     assert "OTHER_LDFLAGS = $(inherited) flag3 flag4" in conan_global_flags
     conan_global_file = client.load("conan_config.xcconfig")
     assert '#include "conan_global_flags.xcconfig"' in conan_global_file
+
+
+def test_flags_generated_if_only_defines():
+    # https://github.com/conan-io/conan/issues/16422
+    client = TestClient()
+    client.save({"conanfile.txt": "[generators]\nXcodeToolchain\n"})
+    client.run("install . -c 'tools.build:defines=[\"MYDEFINITION\"]'")
+    conan_global_flags = client.load("conan_global_flags.xcconfig")
+    assert "GCC_PREPROCESSOR_DEFINITIONS = $(inherited) MYDEFINITION" in conan_global_flags
+    conan_global_file = client.load("conan_config.xcconfig")
+    assert '#include "conan_global_flags.xcconfig"' in conan_global_file
diff --git a/test/integration/toolchains/cmake/test_cmaketoolchain.py b/test/integration/toolchains/cmake/test_cmaketoolchain.py
index c21b35ffc..457f6a2b3 100644
--- a/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -1501,6 +1501,7 @@ def test_toolchain_keep_absolute_paths():
     presets = json.loads(c.load(user_presets["include"][0]))
     assert os.path.isabs(presets["configurePresets"][0]["toolchainFile"])
 
+
 def test_output_dirs_gnudirs_local_default():
     # https://github.com/conan-io/conan/issues/14733
     c = TestClient()
@@ -1630,3 +1631,18 @@ def test_toolchain_extra_variables():
     toolchain = client.load("conan_toolchain.cmake")
     assert 'set(CACHE_VAR_DEFAULT_DOC "hello world" CACHE PATH "CACHE_VAR_DEFAULT_DOC")' in toolchain
 
+
+def test_variables_wrong_scaping():
+    # https://github.com/conan-io/conan/issues/16432
+    c = TestClient()
+    c.save({"tool/conanfile.py": GenConanfile("tool", "0.1"),
+            "pkg/conanfile.txt": "[tool_requires]\ntool/0.1\n[generators]\nCMakeToolchain"})
+    c.run("create tool")
+    c.run("install pkg")
+    toolchain = c.load("pkg/conan_toolchain.cmake")
+    cache_folder = c.cache_folder.replace("\\", "/")
+    assert f'list(PREPEND CMAKE_PROGRAM_PATH "{cache_folder}' in toolchain
+
+    c.run("install pkg --deployer=full_deploy")
+    toolchain = c.load("pkg/conan_toolchain.cmake")
+    assert 'list(PREPEND CMAKE_PROGRAM_PATH "${CMAKE_CURRENT_LIST_DIR}/full_deploy' in toolchain
diff --git a/test/integration/ui/exit_with_code_test.py b/test/integration/ui/exit_with_code_test.py
index a75297c90..5613ccb22 100644
--- a/test/integration/ui/exit_with_code_test.py
+++ b/test/integration/ui/exit_with_code_test.py
@@ -1,6 +1,6 @@
 import unittest
 
-from conans.paths import CONANFILE
+from conan.internal.paths import CONANFILE
 from conan.test.utils.tools import TestClient
 from conans.util.files import save
 
diff --git a/test/unittests/client/remote_manager_test.py b/test/unittests/client/remote_manager_test.py
index 966b0e608..e0438bcd8 100644
--- a/test/unittests/client/remote_manager_test.py
+++ b/test/unittests/client/remote_manager_test.py
@@ -2,7 +2,7 @@ import os
 import unittest
 
 from conans.client.cmd.uploader import compress_files
-from conans.paths import PACKAGE_TGZ_NAME
+from conan.internal.paths import PACKAGE_TGZ_NAME
 from conan.test.utils.test_files import temp_folder
 from conans.util.files import save
 
diff --git a/test/unittests/model/other_settings_test.py b/test/unittests/model/other_settings_test.py
index bbd309c98..5c04a50bd 100644
--- a/test/unittests/model/other_settings_test.py
+++ b/test/unittests/model/other_settings_test.py
@@ -4,7 +4,7 @@ import unittest
 
 from conans.model.info import load_binary_info
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import CONANFILE
+from conan.internal.paths import CONANFILE
 from conan.test.utils.tools import TestClient
 from conans.util.files import load, save
 
diff --git a/test/unittests/paths/user_home_test.py b/test/unittests/paths/user_home_test.py
index 61b28eddf..50f2b800e 100644
--- a/test/unittests/paths/user_home_test.py
+++ b/test/unittests/paths/user_home_test.py
@@ -2,7 +2,7 @@ import os
 import platform
 from pathlib import Path
 
-from conans.paths import get_conan_user_home, DEFAULT_CONAN_HOME
+from conan.internal.paths import get_conan_user_home, DEFAULT_CONAN_HOME
 from conan.test.utils.test_files import temp_folder
 from conans.util.files import chdir
 
diff --git a/test/unittests/server/conf_test.py b/test/unittests/server/conf_test.py
index d2d1f91a3..54b274c1c 100644
--- a/test/unittests/server/conf_test.py
+++ b/test/unittests/server/conf_test.py
@@ -3,7 +3,7 @@ import unittest
 from datetime import timedelta
 
 from conans.errors import ConanException
-from conans.paths import conan_expand_user
+from conan.internal.paths import conan_expand_user
 from conans.server.conf import ConanServerConfigParser
 from conan.test.utils.test_files import temp_folder
 from conans.util.config_parser import ConfigParser
diff --git a/test/unittests/server/service/service_test.py b/test/unittests/server/service/service_test.py
index fbdcbe8cb..80c50943f 100644
--- a/test/unittests/server/service/service_test.py
+++ b/test/unittests/server/service/service_test.py
@@ -6,7 +6,7 @@ from conans.errors import NotFoundException
 from conans.model.manifest import FileTreeManifest
 from conans.model.package_ref import PkgReference
 from conans.model.recipe_ref import RecipeReference
-from conans.paths import CONANINFO, CONAN_MANIFEST
+from conan.internal.paths import CONANINFO, CONAN_MANIFEST
 from conans.server.service.authorize import BasicAuthorizer
 from conans.server.service.v2.search import SearchService
 from conans.server.service.v2.service_v2 import ConanServiceV2
diff --git a/test/unittests/tools/files/test_tool_copy.py b/test/unittests/tools/files/test_tool_copy.py
index 8b7380abd..c2633289c 100644
--- a/test/unittests/tools/files/test_tool_copy.py
+++ b/test/unittests/tools/files/test_tool_copy.py
@@ -284,3 +284,18 @@ class ToolCopyTest(unittest.TestCase):
         with chdir(sources):
             copy(None, "*", "..", dst=".")  # This used to crash
             assert "file.h" in os.listdir(sources)
+
+    def test_keep_path_false(self):
+        folder1 = temp_folder()
+        save(os.path.join(folder1, "file1.txt"), "file1")
+        save(os.path.join(folder1, "sub1", "file2.txt"), "file2")
+        save(os.path.join(folder1, "sub1", "file3.txt"), "file3")
+        save(os.path.join(folder1, "sub1", "sub12", "file4.txt"), "file4")
+        save(os.path.join(folder1, "sub2", "file5.txt"), "file5")
+        save(os.path.join(folder1, "sub2", "sub22", "file6.txt"), "file6")
+        save(os.path.join(folder1, "sub2", "sub22", "sub23", "file7.txt"), "file7")
+
+        folder2 = temp_folder()
+        copy(None, "*.txt", folder1, folder2, keep_path=False)
+        for file_number in range(1, 8):
+            assert load(os.path.join(folder2, f"file{file_number}.txt")) == f"file{file_number}"