Repository: conan-io/conan
Pull request #16425 (2024-06-07 07:45:56)

source credential scope
Changelog: Fix: Make ``source_credentials.json`` do not apply to Conan server repos protocol.
Docs: https://github.com/conan-io/docs/pull/XXXX

Close https://github.com/conan-io/conan/issues/16396



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/api/subapi/upload.py b/conan/api/subapi/upload.py
index f8ad2dc3e..022dd70d2 100644
--- a/conan/api/subapi/upload.py
+++ b/conan/api/subapi/upload.py
@@ -114,7 +114,7 @@ class UploadAPI:
 
         app = ConanApp(self.conan_api)
         # TODO: verify might need a config to force it to False
-        uploader = FileUploader(app.requester, verify=True, config=config)
+        uploader = FileUploader(app.requester, verify=True, config=config, source_credentials=True)
         # TODO: For Artifactory, we can list all files once and check from there instead
         #  of 1 request per file, but this is more general
         for file in files:
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/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/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/conf/config_installer.py b/conans/client/conf/config_installer.py
index 4cb240fa6..ad4ac2971 100644
--- a/conans/client/conf/config_installer.py
+++ b/conans/client/conf/config_installer.py
@@ -138,7 +138,7 @@ def _process_download(config, cache_folder, requester):
         filename = os.path.basename(path)
         zippath = os.path.join(tmp_folder, filename)
         try:
-            downloader = FileDownloader(requester=requester)
+            downloader = FileDownloader(requester=requester, source_credentials=True)
             downloader.download(url=config.uri, file_path=zippath, verify_ssl=config.verify_ssl,
                                 retry=1)
             _process_zip_file(config, zippath, cache_folder, tmp_folder, first_remove=True)
diff --git a/conans/client/downloaders/caching_file_downloader.py b/conans/client/downloaders/caching_file_downloader.py
index 24155ad11..e6921e154 100644
--- a/conans/client/downloaders/caching_file_downloader.py
+++ b/conans/client/downloaders/caching_file_downloader.py
@@ -20,7 +20,8 @@ class SourcesCachingDownloader:
     def __init__(self, conanfile):
         helpers = getattr(conanfile, "_conan_helpers")
         self._global_conf = helpers.global_conf
-        self._file_downloader = FileDownloader(helpers.requester, scope=conanfile.display_name)
+        self._file_downloader = FileDownloader(helpers.requester, scope=conanfile.display_name,
+                                               source_credentials=True)
         self._home_folder = helpers.home_folder
         self._output = conanfile.output
         self._conanfile = conanfile
diff --git a/conans/client/downloaders/file_downloader.py b/conans/client/downloaders/file_downloader.py
index 676af7a07..ca0a47a42 100644
--- a/conans/client/downloaders/file_downloader.py
+++ b/conans/client/downloaders/file_downloader.py
@@ -13,9 +13,10 @@ from conans.util.sha import check_with_algorithm_sum
 
 class FileDownloader:
 
-    def __init__(self, requester, scope=None):
+    def __init__(self, requester, scope=None, source_credentials=None):
         self._output = ConanOutput(scope=scope)
         self._requester = requester
+        self._source_credentials = source_credentials
 
     def download(self, url, file_path, retry=2, retry_wait=0, verify_ssl=True, auth=None,
                  overwrite=False, headers=None, md5=None, sha1=None, sha256=None):
@@ -74,7 +75,8 @@ class FileDownloader:
 
         try:
             response = self._requester.get(url, stream=True, verify=verify_ssl, auth=auth,
-                                           headers=headers)
+                                           headers=headers,
+                                           source_credentials=self._source_credentials)
         except Exception as exc:
             raise ConanException("Error downloading file %s: '%s'" % (url, exc))
 
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/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/conan_requester.py b/conans/client/rest/conan_requester.py
index 80d277054..4903ac181 100644
--- a/conans/client/rest/conan_requester.py
+++ b/conans/client/rest/conan_requester.py
@@ -24,7 +24,10 @@ DEFAULT_TIMEOUT = (30, 60)  # connect, read timeouts
 INFINITE_TIMEOUT = -1
 
 
-class URLCredentials:
+class _SourceURLCredentials:
+    """
+    Only for sources download (get(), download(), conan config install
+    """
     def __init__(self, cache_folder):
         self._urls = {}
         if not cache_folder:
@@ -70,7 +73,7 @@ class URLCredentials:
                 break
 
 
-class ConanRequester(object):
+class ConanRequester:
 
     def __init__(self, config, cache_folder=None):
         # TODO: Make all this lazy, to avoid fully configuring Requester, for every api call
@@ -84,7 +87,7 @@ class ConanRequester(object):
         else:
             self._http_requester = requests
 
-        self._url_creds = URLCredentials(cache_folder)
+        self._url_creds = _SourceURLCredentials(cache_folder)
         self._timeout = config.get("core.net.http:timeout", default=DEFAULT_TIMEOUT)
         self._no_proxy_match = config.get("core.net.http:no_proxy_match", check_type=list)
         self._proxies = config.get("core.net.http:proxies")
@@ -92,6 +95,10 @@ class ConanRequester(object):
         self._client_certificates = config.get("core.net.http:client_cert")
         self._clean_system_proxy = config.get("core.net.http:clean_system_proxy", default=False,
                                               check_type=bool)
+        platform_info = "; ".join([" ".join([platform.system(), platform.release()]),
+                                   "Python " + platform.python_version(),
+                                   platform.machine()])
+        self._user_agent = "Conan/%s (%s)" % (client_version, platform_info)
 
     @staticmethod
     def _get_retries(config):
@@ -123,6 +130,7 @@ class ConanRequester(object):
     def _add_kwargs(self, url, kwargs):
         # verify is the kwargs that comes from caller, RestAPI, it is defined in
         # Conan remote "verify_ssl"
+        source_credentials = kwargs.pop("source_credentials", None)
         if kwargs.get("verify", None) is not False:  # False means de-activate
             if self._cacert_path is not None:
                 kwargs["verify"] = self._cacert_path
@@ -135,16 +143,12 @@ class ConanRequester(object):
         if not kwargs.get("headers"):
             kwargs["headers"] = {}
 
-        self._url_creds.add_auth(url, kwargs)
+        if source_credentials:
+            self._url_creds.add_auth(url, kwargs)
 
         # Only set User-Agent if none was provided
         if not kwargs["headers"].get("User-Agent"):
-            platform_info = "; ".join([
-                " ".join([platform.system(), platform.release()]),
-                "Python "+platform.python_version(),
-                platform.machine()])
-            user_agent = "Conan/%s (%s)" % (client_version, platform_info)
-            kwargs["headers"]["User-Agent"] = user_agent
+            kwargs["headers"]["User-Agent"] = self._user_agent
 
         return kwargs
 
diff --git a/conans/client/rest/file_uploader.py b/conans/client/rest/file_uploader.py
index bba570a91..f8635315b 100644
--- a/conans/client/rest/file_uploader.py
+++ b/conans/client/rest/file_uploader.py
@@ -10,11 +10,12 @@ from conans.util.files import sha1sum
 
 class FileUploader(object):
 
-    def __init__(self, requester, verify, config):
+    def __init__(self, requester, verify, config, source_credentials=None):
         self._output = ConanOutput()
         self._requester = requester
         self._config = config
         self._verify_ssl = verify
+        self._source_credentials = source_credentials
 
     @staticmethod
     def _handle_400_response(response, auth):
@@ -37,7 +38,7 @@ class FileUploader(object):
         if headers:
             dedup_headers.update(headers)
         response = self._requester.put(url, data="", verify=self._verify_ssl, headers=dedup_headers,
-                                       auth=auth)
+                                       auth=auth, source_credentials=self._source_credentials)
         if response.status_code == 500:
             raise InternalErrorException(response_to_str(response))
 
@@ -47,7 +48,8 @@ class FileUploader(object):
             return response
 
     def exists(self, url, auth):
-        response = self._requester.head(url, verify=self._verify_ssl, auth=auth)
+        response = self._requester.head(url, verify=self._verify_ssl, auth=auth,
+                                        source_credentials=self._source_credentials)
         return bool(response.ok)
 
     def upload(self, url, abs_path, auth=None, dedup=False, retry=None, retry_wait=None,
@@ -84,7 +86,8 @@ class FileUploader(object):
         with open(abs_path, mode='rb') as file_handler:
             try:
                 response = self._requester.put(url, data=file_handler, verify=self._verify_ssl,
-                                               headers=headers, auth=auth)
+                                               headers=headers, auth=auth,
+                                               source_credentials=self._source_credentials)
                 self._handle_400_response(response, auth)
                 response.raise_for_status()  # Raise HTTPError for bad http response status
                 return response
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


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/conan/test/utils/tools.py b/conan/test/utils/tools.py
index 660d50149..2fdfb944d 100644
--- a/conan/test/utils/tools.py
+++ b/conan/test/utils/tools.py
@@ -244,6 +244,7 @@ class TestRequester:
             kwargs["expect_errors"] = True
             kwargs.pop("stream", None)
             kwargs.pop("verify", None)
+            kwargs.pop("source_credentials", None)
             auth = kwargs.pop("auth", None)
             if auth and isinstance(auth, tuple):
                 app.set_authorization(("Basic", auth))
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_source_download_password.py b/test/integration/test_source_download_password.py
index 980c666aa..8778ad028 100644
--- a/test/integration/test_source_download_password.py
+++ b/test/integration/test_source_download_password.py
@@ -1,8 +1,9 @@
 import json
 import os
 import textwrap
+from unittest import mock
 
-
+from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.file_server import TestFileServer
 from conan.test.utils.tools import TestClient
 from conans.util.files import save
@@ -59,3 +60,20 @@ def test_source_download_password():
     c.run("source .", assert_error=True)
     assert "ERROR: conanfile.py: Error in source() method, line 6" in c.out
     assert "Authentication" in c.out
+
+
+def test_source_credentials_only_download():
+    # https://github.com/conan-io/conan/issues/16396
+    c = TestClient(default_server_user=True)
+    url = c.servers["default"].fake_url
+
+    content = {"credentials": [{"url": url, "token": "password stpaces"}]}
+    save(os.path.join(c.cache_folder, "source_credentials.json"), json.dumps(content))
+
+    c.save({"conanfile.py": GenConanfile("pkg", "0.1")})
+    c.run("create .")
+    # add_auth should never be called for regular upload/download
+    with mock.patch("conans.client.rest.conan_requester._SourceURLCredentials.add_auth", None):
+        c.run("upload * -c -r=default")
+        c.run("remove * -c")
+        c.run("download pkg/0.1 -r=default")
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/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}"