Repository: conan-io/conan
Pull request #18644 (2025-07-15 09:34:21)

Create api helpers to help with initialization order and caching of common objects
Changelog: Fix: Check min Conan version before loading hooks 
Docs: Omit

Closes https://github.com/conan-io/conan/issues/18643



Patch:
diff --git a/conan/api/conan_api.py b/conan/api/conan_api.py
index 1b2c46ee8..ac809e503 100644
--- a/conan/api/conan_api.py
+++ b/conan/api/conan_api.py
@@ -22,6 +22,8 @@ from conan.api.subapi.remove import RemoveAPI
 from conan.api.subapi.search import SearchAPI
 from conan.api.subapi.upload import UploadAPI
 from conan.errors import ConanException
+from conan.internal.cache.home_paths import HomePaths
+from conan.internal.hook_manager import HookManager
 from conan.internal.paths import get_conan_user_home
 from conan.internal.api.migrations import ClientMigrator
 from conan.internal.model.version_range import validate_conan_version
@@ -48,10 +50,12 @@ class ConanAPI:
         init_colorama(sys.stderr)
         self.cache_folder = cache_folder or get_conan_user_home()
         self.home_folder = self.cache_folder  # Lets call it home, deprecate "cache"
-        self.migrate()
-
         # This API is depended upon by the subsequent ones, it should be initialized first
         self.config = ConfigAPI(self)
+        _check_conan_version(self)
+
+        self._api_helpers = self._ApiHelpers(self)
+        self.migrate()
 
         self.remotes = RemotesAPI(self)
         self.command = CommandAPI(self)
@@ -60,27 +64,27 @@ class ConanAPI:
         # Get latest refs and list refs of recipes and packages
         self.list = ListAPI(self)
         self.profiles = ProfilesAPI(self)
-        self.install = InstallAPI(self)
-        self.graph = GraphAPI(self)
-        self.export = ExportAPI(self)
+        self.install = InstallAPI(self, self._api_helpers)
+        self.graph = GraphAPI(self, self._api_helpers)
+        self.export = ExportAPI(self, self._api_helpers)
         self.remove = RemoveAPI(self)
         self.new = NewAPI(self)
         self.upload = UploadAPI(self)
         self.download = DownloadAPI(self)
         self.cache = CacheAPI(self)
         self.lockfile = LockfileAPI(self)
-        self.local = LocalAPI(self)
+        self.local = LocalAPI(self, self._api_helpers)
         self.audit = AuditAPI(self)
         # Now, lazy loading of editables
         self.workspace = WorkspaceAPI(self)
-        self.report = ReportAPI(self)
-
-        _check_conan_version(self)
+        self.report = ReportAPI(self, self._api_helpers)
 
     def reinit(self):
         """
         Reinitialize the Conan API. This is useful when the configuration changes.
         """
+        # TODO: Think order of reinitialization for helpers
+        self._api_helpers.reinit()
         self.config.reinit()
         self.remotes.reinit()
         self.local.reinit()
@@ -94,6 +98,14 @@ class ConanAPI:
         migrator = ClientMigrator(self.cache_folder, conan_version)
         migrator.migrate()
 
+    class _ApiHelpers:
+        def __init__(self, conan_api):
+            self._conan_api = conan_api
+            self.hook_manager = HookManager(HomePaths(self._conan_api.home_folder).hooks_path)
+
+        def reinit(self):
+            self.hook_manager.reinit()
+
 
 def _check_conan_version(conan_api):
     required_range_new = conan_api.config.global_conf.get("core:required_conan_version")
diff --git a/conan/api/input.py b/conan/api/input.py
index 3fae3b4fa..258ded10d 100644
--- a/conan/api/input.py
+++ b/conan/api/input.py
@@ -33,11 +33,11 @@ class UserInput:
         :param username If username is specified it only request password"""
         self._raise_if_non_interactive()
         if not username:
-            self._out.write("Remote '%s' username: " % remote_name)
+            self._out.login_msg(f"Remote '{remote_name}' username: ")
             username = self.get_username()
 
-        self._out.write("Please enter a password for user '%s' on remote '%s': "
-                        % (username, remote_name))
+        self._out.login_msg("Please enter a password for "
+                            f"user '{username}' on remote '{remote_name}': ")
         try:
             pwd = self.get_password()
         except ConanException:
diff --git a/conan/api/output.py b/conan/api/output.py
index 535586c00..a8e56548d 100644
--- a/conan/api/output.py
+++ b/conan/api/output.py
@@ -19,7 +19,7 @@ LEVEL_DEBUG = 20  # -vv Closely related to internal implementation details
 LEVEL_TRACE = 10  # -vvv Fine-grained messages with very low-level implementation details
 
 
-class Color(object):
+class Color:
     """ Wrapper around colorama colors that are undefined in importing
     """
     RED = Fore.RED  # @UndefinedVariable
@@ -181,6 +181,11 @@ class ConanOutput:
         self.writeln(f'*{msg: ^48}*', fg=color)
         self.writeln(f"**************************************************\n", fg=color)
 
+    def login_msg(self, msg, newline=False):
+        # unconditional to the error level, this has to show always
+        self._write_message(msg, newline=newline)
+        return self
+
     def _write_message(self, msg, fg=None, bg=None, newline=True):
         if isinstance(msg, dict):
             # For traces we can receive a dict already, we try to transform then into more natural
@@ -189,20 +194,19 @@ class ConanOutput:
             msg = "=> {}".format(msg)
             # msg = json.dumps(msg, sort_keys=True, default=json_encoder)
 
-        ret = ""
         if self._scope:
             if self._color:
-                ret = "{}{}{}:{} ".format(fg or '', bg or '', self.scope, Style.RESET_ALL)
+                ret = f"{fg or ''}{bg or ''}{self._scope}: {msg}{Style.RESET_ALL}"
             else:
-                ret = "{}: ".format(self._scope)
-
-        if self._color:
-            ret += "{}{}{}{}".format(fg or '', bg or '', msg, Style.RESET_ALL)
+                ret = f"{self._scope}: {msg}"
         else:
-            ret += "{}".format(msg)
+            if self._color:
+                ret = f"{fg or ''}{bg or ''}{msg}{Style.RESET_ALL}"
+            else:
+                ret = msg
 
         if newline:
-            ret = "%s\n" % ret
+            ret += "\n"
 
         with self.lock:
             self.stream.write(ret)
@@ -260,7 +264,8 @@ class ConanOutput:
 
     def warning(self, msg, warn_tag=None):
         _treat_as_error = self._warn_tag_matches(warn_tag, self._warnings_as_errors)
-        if self._conan_output_level <= LEVEL_WARNING or (_treat_as_error and self._conan_output_level <= LEVEL_ERROR):
+        if (self._conan_output_level <= LEVEL_WARNING or
+                (_treat_as_error and self._conan_output_level <= LEVEL_ERROR)):
             if self._warn_tag_matches(warn_tag, self._silent_warn_tags):
                 return self
             warn_tag_msg = "" if warn_tag is None else f"{warn_tag}: "
diff --git a/conan/api/subapi/config.py b/conan/api/subapi/config.py
index 78bd3fec5..86d002cf6 100644
--- a/conan/api/subapi/config.py
+++ b/conan/api/subapi/config.py
@@ -30,7 +30,6 @@ class ConfigAPI:
         self.conan_api = conan_api
         self._new_config = None
         self._cli_core_confs = None
-        self.hook_manager = HookManager(HomePaths(conan_api.home_folder).hooks_path)
 
     def home(self):
         return self.conan_api.cache_folder
@@ -236,4 +235,3 @@ class ConfigAPI:
         if self._new_config is not None:
             self._new_config.clear()
             self._populate_global_conf()
-        self.hook_manager = HookManager(HomePaths(self.conan_api.home_folder).hooks_path)
diff --git a/conan/api/subapi/export.py b/conan/api/subapi/export.py
index 9e43ba57c..5cb188620 100644
--- a/conan/api/subapi/export.py
+++ b/conan/api/subapi/export.py
@@ -10,19 +10,20 @@ from conan.internal.util.files import mkdir
 
 class ExportAPI:
 
-    def __init__(self, conan_api):
+    def __init__(self, conan_api, helpers):
         self.conan_api = conan_api
+        self._helpers = helpers
 
     def export(self, path, name, version, user, channel, lockfile=None, remotes=None):
         ConanOutput().title("Exporting recipe to the cache")
         app = ConanApp(self.conan_api)
-        hook_manager = self.conan_api.config.hook_manager
+        hook_manager = self._helpers.hook_manager
         return cmd_export(app, hook_manager, self.conan_api.config.global_conf, path, name, version,
                           user, channel, graph_lock=lockfile, remotes=remotes)
 
     def export_pkg(self, deps_graph, source_folder, output_folder):
         cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
-        hook_manager = self.conan_api.config.hook_manager
+        hook_manager = self._helpers.hook_manager
 
         # The graph has to be loaded with build_mode=[ref.name], so that node is not tried
         # to be downloaded from remotes
diff --git a/conan/api/subapi/graph.py b/conan/api/subapi/graph.py
index 041b3a2c3..e62c5bafe 100644
--- a/conan/api/subapi/graph.py
+++ b/conan/api/subapi/graph.py
@@ -13,8 +13,9 @@ from conan.api.model import RecipeReference
 
 class GraphAPI:
 
-    def __init__(self, conan_api):
+    def __init__(self, conan_api, helpers):
         self.conan_api = conan_api
+        self._helpers = helpers
 
     def _load_root_consumer_conanfile(self, path, profile_host, profile_build,
                                       name=None, version=None, user=None, channel=None,
@@ -205,7 +206,8 @@ class GraphAPI:
         """
         ConanOutput().title("Computing necessary packages")
         conan_app = ConanBasicApp(self.conan_api)
-        binaries_analyzer = GraphBinariesAnalyzer(conan_app, self.conan_api.config.global_conf)
+        binaries_analyzer = GraphBinariesAnalyzer(conan_app, self.conan_api.config.global_conf,
+                                                  self._helpers.hook_manager)
         binaries_analyzer.evaluate_graph(graph, build_mode, lockfile, remotes, update,
                                          build_modes_test, tested_graph)
 
diff --git a/conan/api/subapi/install.py b/conan/api/subapi/install.py
index 9f35bf0bc..921bfd362 100644
--- a/conan/api/subapi/install.py
+++ b/conan/api/subapi/install.py
@@ -11,8 +11,9 @@ from conan.errors import ConanInvalidConfiguration
 
 class InstallAPI:
 
-    def __init__(self, conan_api):
+    def __init__(self, conan_api, helpers):
         self.conan_api = conan_api
+        self._helpers = helpers
 
     def install_binaries(self, deps_graph, remotes=None):
         """ Install binaries for dependency graph
@@ -21,7 +22,7 @@ class InstallAPI:
         """
         app = ConanBasicApp(self.conan_api)
         installer = BinaryInstaller(app, self.conan_api.config.global_conf, app.editable_packages,
-                                    self.conan_api.config.hook_manager)
+                                    self._helpers.hook_manager)
         install_graph = InstallGraph(deps_graph)
         install_graph.raise_errors()
         install_order = install_graph.install_order()
@@ -35,7 +36,7 @@ class InstallAPI:
         """
         app = ConanBasicApp(self.conan_api)
         installer = BinaryInstaller(app, self.conan_api.config.global_conf, app.editable_packages,
-                                    self.conan_api.config.hook_manager)
+                                    self._helpers.hook_manager)
         installer.install_system_requires(graph, only_info)
 
     def install_sources(self, graph, remotes):
@@ -46,7 +47,7 @@ class InstallAPI:
         """
         app = ConanBasicApp(self.conan_api)
         installer = BinaryInstaller(app, self.conan_api.config.global_conf, app.editable_packages,
-                                    self.conan_api.config.hook_manager)
+                                    self._helpers.hook_manager)
         installer.install_sources(graph, remotes)
 
     # TODO: Look for a better name
@@ -87,7 +88,7 @@ class InstallAPI:
             if gen not in final_generators:
                 final_generators.append(gen)
         conanfile.generators = final_generators
-        hook_manager = self.conan_api.config.hook_manager
+        hook_manager = self._helpers.hook_manager
         write_generators(conanfile, hook_manager, self.conan_api.home_folder,
                          envs_generation=envs_generation)
 
diff --git a/conan/api/subapi/local.py b/conan/api/subapi/local.py
index 11c6d5c1f..88e6bc607 100644
--- a/conan/api/subapi/local.py
+++ b/conan/api/subapi/local.py
@@ -14,8 +14,9 @@ from conan.internal.util.files import chdir
 
 class LocalAPI:
 
-    def __init__(self, conan_api):
+    def __init__(self, conan_api, helpers):
         self._conan_api = conan_api
+        self._helpers = helpers
         self.editable_packages = EditablePackages(conan_api.home_folder)
 
     @staticmethod
@@ -93,13 +94,13 @@ class LocalAPI:
         conanfile.folders.set_base_build(None)
         conanfile.folders.set_base_package(None)
 
-        hook_manager = self._conan_api.config.hook_manager
+        hook_manager = self._helpers.hook_manager
         run_source_method(conanfile, hook_manager)
 
     def build(self, conanfile):
         """ calls the 'build()' method of the current (user folder) conanfile.py
         """
-        hook_manager = self._conan_api.config.hook_manager
+        hook_manager = self._helpers.hook_manager
         conanfile.folders.set_base_package(conanfile.folders.base_build)
         conanfile.folders.set_base_pkg_metadata(os.path.join(conanfile.build_folder, "metadata"))
         run_build_method(conanfile, hook_manager)
diff --git a/conan/api/subapi/report.py b/conan/api/subapi/report.py
index f1a2a1c8e..c4c04fa43 100644
--- a/conan/api/subapi/report.py
+++ b/conan/api/subapi/report.py
@@ -14,8 +14,9 @@ from conan.internal.util.runners import conan_run
 
 
 class ReportAPI:
-    def __init__(self, conan_api):
+    def __init__(self, conan_api, helpers):
         self.conan_api = conan_api
+        self._helpers = helpers
 
     def diff(self, old_reference, new_reference, remotes, old_path=None, new_path=None, cwd=None):
         """
@@ -36,7 +37,8 @@ class ReportAPI:
                 export_ref, cache_path = _export_recipe_from_path(self.conan_api, path_to_conanfile,
                                                                   reference, remotes, cwd)
             exported_path = self.conan_api.local.get_conanfile_path(cache_path, cwd, py=True)
-            _configure_source(self.conan_api, exported_path, export_ref, remotes)
+            _configure_source(self.conan_api, self._helpers.hook_manager, exported_path, export_ref,
+                              remotes)
             return export_ref, cache_path
 
 
@@ -77,7 +79,7 @@ class ReportAPI:
             "dst_prefix": dst_prefix,
         }
 
-def _configure_source(conan_api, conanfile_path, ref, remotes):
+def _configure_source(conan_api, hook_manager, conanfile_path, ref, remotes):
     app = ConanApp(conan_api)
     conanfile = app.loader.load_consumer(conanfile_path, name=ref.name, version=str(ref.version),
                                          user=ref.user, channel=ref.channel, graph_lock=None,
@@ -97,7 +99,7 @@ def _configure_source(conan_api, conanfile_path, ref, remotes):
     conanfile.folders.set_base_source(source_folder)
     conanfile.folders.set_base_export_sources(export_source_folder)
     conanfile.folders.set_base_recipe_metadata(recipe_layout.metadata())
-    config_source(export_source_folder, conanfile, conan_api.config.hook_manager)
+    config_source(export_source_folder, conanfile, hook_manager)
 
 def _get_ref_from_cache_or_remote(conan_api, reference, enabled_remotes):
     ref = RecipeReference.loads(reference)
diff --git a/conan/cli/commands/new.py b/conan/cli/commands/new.py
index d316bf5a3..e6aab3c35 100644
--- a/conan/cli/commands/new.py
+++ b/conan/cli/commands/new.py
@@ -12,7 +12,7 @@ def new(conan_api, parser, *args):
                         "either a predefined built-in or a user-provided one. "
                         "Available built-in templates: basic, cmake_lib, cmake_exe, header_lib, "
                         "meson_lib, meson_exe, msbuild_lib, msbuild_exe, bazel_lib, bazel_exe, "
-                        "autotools_lib, autotools_exe, local_recipes_index, workspace. "
+                        "autotools_lib, autotools_exe, premake_lib, premake_exe, local_recipes_index, workspace. "
                         "E.g. 'conan new cmake_lib -d name=hello -d version=0.1'. "
                         "You can define your own templates too by inputting an absolute path "
                         "as your template, or a path relative to your conan home folder."
diff --git a/conan/internal/api/detect/detect_api.py b/conan/internal/api/detect/detect_api.py
index 71d8e6e8a..aefa2967a 100644
--- a/conan/internal/api/detect/detect_api.py
+++ b/conan/internal/api/detect/detect_api.py
@@ -488,11 +488,10 @@ def _cc_compiler(compiler_exe="cc"):
         compiler = "clang" if "clang" in out else "gcc"
         # clang and gcc have version after a space, first try to find that to skip extra numbers
         # that might appear in the first line of the output before the version
-        installed_version = re.search(r" ([0-9]+(\.[0-9]+)+)", out)
-        # Try only major but with spaces next
-        installed_version = installed_version or re.search(r" ([0-9]+(\.[0-9]+)?)", out)
+        # There might also be a leading parenthesis that contains build information, so we try to skip it
+        installed_version = re.search(r"(?:\(.*\))? ([0-9]+(\.[0-9]+)*)", out)
         # Fallback to the first number we find optionally followed by other version fields
-        installed_version = installed_version or re.search(r"([0-9]+(\.[0-9]+)?)", out)
+        installed_version = installed_version or re.search(r"([0-9]+(\.[0-9]+)*)", out)
         if installed_version and installed_version.group(1):
             installed_version = installed_version.group(1)
             ConanOutput(scope="detect_api").info("Found cc=%s-%s" % (compiler, installed_version))
@@ -510,7 +509,6 @@ def detect_gcc_compiler(compiler_exe="gcc"):
             if "clang" in out:
                 return None, None, None
 
-        # TODO: Add -dumpfullversion to always return major.minor
         ret, out = detect_runner(f'"{compiler_exe}" -dumpversion')
         if ret != 0:
             return None, None, None
diff --git a/conan/internal/graph/graph_binaries.py b/conan/internal/graph/graph_binaries.py
index 386f69476..4b2042ff0 100644
--- a/conan/internal/graph/graph_binaries.py
+++ b/conan/internal/graph/graph_binaries.py
@@ -24,12 +24,12 @@ from conan.internal.util.files import load
 
 class GraphBinariesAnalyzer:
 
-    def __init__(self, conan_app, global_conf):
+    def __init__(self, conan_app, global_conf, hook_manager):
         self._cache = conan_app.cache
         self._home_folder = conan_app.cache_folder
         self._global_conf = global_conf
         self._remote_manager = conan_app.remote_manager
-        self._hook_manager = conan_app.conan_api.config.hook_manager
+        self._hook_manager = hook_manager
         # These are the nodes with pref (not including PREV) that have been evaluated
         self._evaluated = {}  # {pref: [nodes]}
         compat_folder = HomePaths(conan_app.cache_folder).compatibility_plugin_path
diff --git a/conan/internal/hook_manager.py b/conan/internal/hook_manager.py
index 7ef83bfc8..f790c5d28 100644
--- a/conan/internal/hook_manager.py
+++ b/conan/internal/hook_manager.py
@@ -61,3 +61,7 @@ class HookManager:
                     self.hooks.setdefault(method, []).append((hook_name, hook_method))
         except Exception as e:
             raise ConanException("Error loading hook '%s': %s" % (hook_path, str(e)))
+
+    def reinit(self):
+        self.hooks.clear()
+        self._load_hooks()
diff --git a/conan/internal/loader.py b/conan/internal/loader.py
index 75b10db35..f3634ee9a 100644
--- a/conan/internal/loader.py
+++ b/conan/internal/loader.py
@@ -133,6 +133,9 @@ class ConanFileLoader:
                                      % (channel, conanfile.channel))
             conanfile.channel = channel
 
+        if conanfile.channel and not conanfile.user:
+            raise ConanException(f"{conanfile_path}: Can't specify channel '{conanfile.channel}' without user")
+
         if hasattr(conanfile, "set_name"):
             with conanfile_exception_formatter("conanfile.py", "set_name"):
                 conanfile.set_name()
@@ -167,8 +170,6 @@ class ConanFileLoader:
                                     remotes, update, check_update,
                                     tested_python_requires=tested_python_requires)
 
-        if conanfile.channel and not conanfile.user:
-            raise ConanException(f"{conanfile_path}: Can't specify channel without user")
         ref = RecipeReference(conanfile.name, conanfile.version, conanfile.user, conanfile.channel)
         if str(ref):
             conanfile.display_name = "%s (%s)" % (os.path.basename(conanfile_path), str(ref))
diff --git a/conan/internal/model/conan_file.py b/conan/internal/model/conan_file.py
index 5b2c595bf..a68c32775 100644
--- a/conan/internal/model/conan_file.py
+++ b/conan/internal/model/conan_file.py
@@ -75,6 +75,7 @@ class ConanFile:
     runenv_info = None
     conf_info = None
     generator_info = None
+    conan_data = None
 
     def __init__(self, display_name=""):
         self.display_name = display_name
@@ -176,6 +177,8 @@ class ConanFile:
         if self.info is not None:
             result["info"] = self.info.serialize()
         result["vendor"] = self.vendor
+        if self.conan_data:
+            result["conandata"] = self.conan_data
         return result
 
     @property
diff --git a/conan/internal/rest/download_cache.py b/conan/internal/rest/download_cache.py
index 9ee3a3122..9d9c916fb 100644
--- a/conan/internal/rest/download_cache.py
+++ b/conan/internal/rest/download_cache.py
@@ -123,9 +123,17 @@ class DownloadCache:
         try:
             summary_key = str(conanfile.ref)
         except AttributeError:
-            # The recipe path would be different between machines
-            # So best we can do is to set this as unknown
-            summary_key = "unknown"
+            # If there's no node associated with the conanfile,
+            # try to construct a reference from the conanfile itself.
+            # We accept it if we have a name and a version at least.
+            if conanfile.name and conanfile.version:
+                user = f"@{conanfile.user}" if conanfile.user else ""
+                channel = f"/{conanfile.channel}" if conanfile.channel else ""
+                summary_key = f"{conanfile.name}/{conanfile.version}{user}{channel}"
+            else:
+                # The recipe path would be different between machines
+                # So best we can do is to set this as unknown
+                summary_key = "unknown"
 
         if not isinstance(urls, (list, tuple)):
             urls = [urls]
diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py
index 30b71b0ea..cf11208af 100644
--- a/conan/tools/cmake/cmakedeps2/target_configuration.py
+++ b/conan/tools/cmake/cmakedeps2/target_configuration.py
@@ -122,6 +122,23 @@ class TargetConfigurationTemplate2:
 
         prefixes = self._cmakedeps.get_property("cmake_additional_variables_prefixes",
                                                 self._conanfile, check_type=list) or []
+        seen_aliases = set()
+        root_target_name = self._cmakedeps.get_property("cmake_target_name", self._conanfile)
+        root_target_name = root_target_name or f"{pkg_name}::{pkg_name}"
+        for lib in libs.values():
+            for alias in lib.get("cmake_target_aliases", []):
+                if alias == root_target_name:
+                    raise ConanException(f"Can't define an alias '{alias}' for the "
+                                         f"root target '{root_target_name}' in {self._conanfile}. "
+                                         f"Changing the default target should be done with the "
+                                         f"'cmake_target_name' property.")
+                if alias in seen_aliases:
+                    raise ConanException(f"Alias '{alias}' already defined in {self._conanfile}. ")
+                seen_aliases.add(alias)
+                if alias in libs:
+                    raise ConanException(f"Alias '{alias}' already defined as a target in "
+                                         f"{self._conanfile}. ")
+
         f = self._cmakedeps.get_cmake_filename(self._conanfile)
         prefixes = [f] + prefixes
         include_dirs = definitions = libraries = None
@@ -164,12 +181,16 @@ class TargetConfigurationTemplate2:
                 target = self._get_cmake_lib(component, cpp_info.components, pkg_folder,
                                              pkg_folder_var)
                 if target is not None:
+                    cmake_target_aliases = self._get_aliases(name)
+                    target["cmake_target_aliases"] = cmake_target_aliases
                     libs[target_name] = target
         else:
             target_name = self._cmakedeps.get_property("cmake_target_name", self._conanfile)
             target_name = target_name or f"{pkg_name}::{pkg_name}"
             target = self._get_cmake_lib(cpp_info, None, pkg_folder, pkg_folder_var)
             if target is not None:
+                cmake_target_aliases = self._get_aliases()
+                target["cmake_target_aliases"] = cmake_target_aliases
                 libs[target_name] = target
         return libs
 
@@ -233,11 +254,16 @@ class TargetConfigurationTemplate2:
             target["link_languages"] = link_languages
         return target
 
+    def _get_aliases(self, comp_name=None):
+        aliases = self._cmakedeps.get_property("cmake_target_aliases", self._conanfile,
+                                               comp_name, check_type=list) or []
+        return aliases
+
     def _add_root_lib_target(self, libs, pkg_name, cpp_info):
         """
-        Addd a new pkgname::pkgname INTERFACE target that depends on default_components or
+        Add a new pkgname::pkgname INTERFACE target that depends on default_components or
         on all other library targets (not exes)
-        It will not be added if there exists already a pkgname::pkgname target.
+        It will not be added if there exists already a pkgname::pkgname target (Or an alias exists).
         """
         root_target_name = self._cmakedeps.get_property("cmake_target_name", self._conanfile)
         root_target_name = root_target_name or f"{pkg_name}::{pkg_name}"
@@ -253,8 +279,11 @@ class TargetConfigurationTemplate2:
                     all_requires[comp_name] = True  # It is an interface, full link
             else:
                 all_requires = {k: True for k in libs.keys()}
+            # This target might have an alias, so we need to check it
+            cmake_target_aliases = self._get_aliases()
             libs[root_target_name] = {"type": "INTERFACE",
-                                      "requires": all_requires}
+                                      "requires": all_requires,
+                                      "cmake_target_aliases": cmake_target_aliases}
 
     def _get_exes(self, cpp_info, pkg_name, pkg_folder, pkg_folder_var):
         exes = {}
@@ -333,6 +362,12 @@ class TargetConfigurationTemplate2:
             message(STATUS "Conan: Target declared imported {{lib_info["type"]}} library '{{lib}}'")
             add_library({{lib}} {{lib_info["type"]}} IMPORTED)
         endif()
+        {% for alias in lib_info.get("cmake_target_aliases", []) %}
+        if(NOT TARGET {{alias}})
+            message(STATUS "Conan: Target declared alias '{{alias}}' for '{{lib}}'")
+            add_library({{alias}} ALIAS {{lib}})
+        endif()
+        {% endfor %}
         {% if lib_info.get("includedirs") %}
         set_property(TARGET {{lib}} APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES
                      {{config_wrapper(config, lib_info["includedirs"])}})
diff --git a/conan/tools/premake/premakedeps.py b/conan/tools/premake/premakedeps.py
index 80b291ac2..c98a6377e 100644
--- a/conan/tools/premake/premakedeps.py
+++ b/conan/tools/premake/premakedeps.py
@@ -8,16 +8,27 @@ from conan.tools.premake.constants import CONAN_TO_PREMAKE_ARCH
 
 # Filename format strings
 PREMAKE_VAR_FILE = "conan_{pkgname}_vars_{config}.premake5.lua"
-PREMAKE_CONF_FILE = "conan_{pkgname}{config}.premake5.lua"
 PREMAKE_PKG_FILE = "conan_{pkgname}.premake5.lua"
 PREMAKE_ROOT_FILE = "conandeps.premake5.lua"
 
+PREMAKE_CONFIG_FILE = "conanconfig_{config}.premake5.lua"
+PREMAKE_CONFIG_ROOT_FILE = "conanconfig.premake5.lua"
+
 # File template format strings
+PREMAKE_TEMPLATE_CONFIG = """
+include "conanutils.premake5.lua"
+
+t_conan_deps_order = {{}}
+t_conan_deps_order["{config}"] = {{{order}}}
+
+if conan_deps_order == nil then conan_deps_order = {{}} end
+conan_premake_tmerge(conan_deps_order, t_conan_deps_order)
+"""
 PREMAKE_TEMPLATE_UTILS = """
 function conan_premake_tmerge(dst, src)
     for k, v in pairs(src) do
         if type(v) == "table" then
-            if type(conandeps[k] or 0) == "table" then
+            if type(dst[k] or 0) == "table" then
                 conan_premake_tmerge(dst[k] or {}, src[k] or {})
             else
                 dst[k] = v
@@ -66,8 +77,9 @@ function {function_name}(conf, pkg)
     if conf == nil then
 {filter_call}
     elseif pkg == nil then
-        for k,v in pairs(conandeps[conf]) do
-            {function_name}(conf, k)
+        local order = conan_deps_order[conf]
+        for index, lib in ipairs(order) do
+            {function_name}(conf, lib)
         end
     else
 {lua_content}
@@ -183,10 +195,13 @@ class PremakeDeps:
         # Global utility file
         self._output_lua_file("conanutils.premake5.lua", [PREMAKE_TEMPLATE_UTILS])
 
-        # Extract all dependencies
-        host_req = self._conanfile.dependencies.host
-        test_req = self._conanfile.dependencies.test
-        build_req = self._conanfile.dependencies.build
+        # Extract all dependencies in topological order: some linkers like ld or gold prunes the
+        # functions which are not being used in the lookup table. If the less dependant libraries are
+        # passed first, the linker will not be able to resolve the symbols in the dependent libraries
+        # as they will have been removed
+        host_req = self._conanfile.dependencies.host.topological_sort
+        test_req = self._conanfile.dependencies.test.topological_sort
+        build_req = self._conanfile.dependencies.build.topological_sort
 
         # Merge into one list
         full_req = list(host_req.items()) + list(test_req.items()) + list(build_req.items())
@@ -212,13 +227,13 @@ class PremakeDeps:
             # Create list of all available profiles by searching on disk
             file_pattern = PREMAKE_VAR_FILE.format(pkgname=dep_name, config="*")
             file_regex = PREMAKE_VAR_FILE.format(pkgname=re.escape(dep_name), config="(([^_]*)_(.*))")
-            available_files = glob.glob(file_pattern)
+            available_config_files = glob.glob(file_pattern)
             # Add filename of current generations var file if not already present
-            if var_filename not in available_files:
-                available_files.append(var_filename)
+            if var_filename not in available_config_files:
+                available_config_files.append(var_filename)
             profiles = [
                 (regex_res[0], regex_res.group(1), regex_res.group(2), regex_res.group(3)) for regex_res in [
-                    re.search(file_regex, file_name) for file_name in available_files
+                    re.search(file_regex, file_name) for file_name in available_config_files
                 ]
             ]
             config_sets = [profile[1] for profile in profiles]
@@ -235,6 +250,8 @@ class PremakeDeps:
         self._output_lua_file(PREMAKE_ROOT_FILE, [
             # Includes
             *[f'include "{pkg_file}"' for pkg_file in pkg_files],
+            # Global order for each configuration
+            'include "conanconfig.premake5.lua"',
             # Functions
             PREMAKE_TEMPLATE_ROOT_FUNCTION.format(
                 function_name="conan_setup_build",
@@ -257,4 +274,19 @@ class PremakeDeps:
             PREMAKE_TEMPLATE_ROOT_GLOBAL
         ])
 
+        # Output configuration file for the current build configuration
+        self._output_lua_file(PREMAKE_CONFIG_FILE.format(config=conf_name), [
+            PREMAKE_TEMPLATE_CONFIG.format(
+                config=conf_name, order=", ".join(f'"{name}"' for name in reversed(dep_names))
+            )
+        ])
+
+        # Output root configuration file
+        available_config_files = glob.glob(PREMAKE_CONFIG_FILE.format(config="*"))
+        available_configs = [file_name.split("_", 1)[1].split(".")[0] for file_name in available_config_files]
+        available_configs.append(conf_name)
+        self._output_lua_file(PREMAKE_CONFIG_ROOT_FILE, [
+            *['include "{}"'.format(PREMAKE_CONFIG_FILE.format(config=config)) for config in available_configs],
+        ])
+
         return self.output_files
diff --git a/conan/tools/sbom/cyclonedx.py b/conan/tools/sbom/cyclonedx.py
index 97d18fe71..521cdf442 100644
--- a/conan/tools/sbom/cyclonedx.py
+++ b/conan/tools/sbom/cyclonedx.py
@@ -40,14 +40,13 @@ def cyclonedx_1_4(conanfile, name=None, add_build=False, add_tests=False, **kwar
     dependencies = []
     if has_special_root_node:
         deps = {"ref": special_id,
-                "dependsOn": [f"pkg:conan/{d.dst.name}@{d.dst.ref.version}?rref={d.dst.ref.revision}"
-                              for d in graph.root.edges]}
+                "dependsOn": [_calculate_bomref(d.dst) for d in graph.root.edges]}
         dependencies.append(deps)
     for c in nodes:
-        deps = {"ref": f"pkg:conan/{c.name}@{c.ref.version}?rref={c.ref.revision}"}
+        deps = {"ref": _calculate_bomref(c)}
         dep = [d for d in c.edges if (d.dst.context == "host" or add_build) and (not d.dst.test or add_tests)]
 
-        depends_on = [f"pkg:conan/{d.dst.name}@{d.dst.ref.version}?rref={d.dst.ref.revision}" for d in dep]
+        depends_on = [_calculate_bomref(d.dst) for d in dep]
         if depends_on:
             deps["dependsOn"] = depends_on
         dependencies.append(deps)
@@ -55,7 +54,7 @@ def cyclonedx_1_4(conanfile, name=None, add_build=False, add_tests=False, **kwar
     sbom_cyclonedx_1_4 = {
         **({"components": [{
             "author": node.conanfile.author or "Unknown",
-            "bom-ref": f"pkg:conan/{node.name}@{node.ref.version}?rref={node.ref.revision}",
+            "bom-ref": _calculate_bomref(node),
             "description": node.conanfile.description,
             **({"externalReferences": [{
                 "type": "website",
@@ -71,7 +70,7 @@ def cyclonedx_1_4(conanfile, name=None, add_build=False, add_tests=False, **kwar
         "metadata": {
             "component": {
                 "author": conanfile.author or "Unknown",
-                "bom-ref": special_id if has_special_root_node else f"pkg:conan/{conanfile.name}@{conanfile.ref.version}?rref={conanfile.ref.revision}",
+                "bom-ref": special_id if has_special_root_node else _calculate_bomref(conanfile),
                 "name": name if name else name_default,
                 "type": "application" if conanfile.package_type == "application" else "library",
             },
@@ -131,14 +130,14 @@ def cyclonedx_1_6(conanfile, name=None, add_build=False, add_tests=False, **kwar
     dependencies = []
     if has_special_root_node:
         deps = {"ref": special_id,
-                "dependsOn": [f"pkg:conan/{d.dst.name}@{d.dst.ref.version}?rref={d.dst.ref.revision}"
+                "dependsOn": [_calculate_bomref(d.dst)
                               for d in graph.root.edges]}
         dependencies.append(deps)
     for c in nodes:
-        deps = {"ref": f"pkg:conan/{c.name}@{c.ref.version}?rref={c.ref.revision}"}
+        deps = {"ref": _calculate_bomref(c)}
         dep = [d for d in c.edges if (d.dst.context == "host" or add_build) and (not d.dst.test or add_tests)]
 
-        depends_on = [f"pkg:conan/{d.dst.name}@{d.dst.ref.version}?rref={d.dst.ref.revision}" for d in dep]
+        depends_on = [_calculate_bomref(d.dst) for d in dep]
         if depends_on:
             deps["dependsOn"] = depends_on
         dependencies.append(deps)
@@ -146,7 +145,7 @@ def cyclonedx_1_6(conanfile, name=None, add_build=False, add_tests=False, **kwar
     sbom_cyclonedx_1_6 = {
         **({"components": [{
             **({"authors": [{"name": node.conanfile.author}]} if node.conanfile.author else {}),
-            "bom-ref": f"pkg:conan/{node.name}@{node.ref.version}?rref={node.ref.revision}",
+            "bom-ref": _calculate_bomref(node),
             "description": node.conanfile.description,
             **({"externalReferences": [{
                 "type": "website",
@@ -162,7 +161,7 @@ def cyclonedx_1_6(conanfile, name=None, add_build=False, add_tests=False, **kwar
         "metadata": {
             "component": {
                 **({"authors": [{"name": conanfile.author}]} if conanfile.author else {}),
-                "bom-ref": special_id if has_special_root_node else f"pkg:conan/{conanfile.name}@{conanfile.ref.version}?rref={conanfile.ref.revision}",
+                "bom-ref": special_id if has_special_root_node else _calculate_bomref(conanfile),
                 "name": name if name else name_default,
                 "type": "application" if conanfile.package_type == "application" else "library"
             },
@@ -198,4 +197,10 @@ def _calculate_licenses(component):
         for l in licenses
     ]
 
+def _calculate_bomref(component):
+    user = f"&user={component.ref.user}" if component.ref.user else ""
+    channel = f"&channel={component.ref.channel}" if component.ref.channel else ""
+    return f"pkg:conan/{component.name}@{component.ref.version}?rref={component.ref.revision}{user}{channel}"
+
+
 


Test patch:
diff --git a/test/functional/sbom/test_cyclonedx.py b/test/functional/sbom/test_cyclonedx.py
index a6ca198da..c661ca3ae 100644
--- a/test/functional/sbom/test_cyclonedx.py
+++ b/test/functional/sbom/test_cyclonedx.py
@@ -360,3 +360,28 @@ class TestCyclonedx:
             assert not content_json["components"][0].get("author")
             assert content_json["components"][0]["authors"][0]["name"] == 'conan-dev'
             assert content_json["components"][0]["type"] == 'application'
+
+    @pytest.mark.parametrize("user, channel, user_dep, channel_dep", [("user", None, "user_dep", None), ("user", "channel", "user_dep", "channel_dep")])
+    def test_sbom_user_path(self, hook_setup_post_package_tl, user, channel, user_dep, channel_dep):
+        tc = hook_setup_post_package_tl
+        channel_ref = f"/{channel_dep}" if channel_dep else ""
+        tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0"),
+                 "conanfile.py": GenConanfile("main", "1.0").with_requires(f"dep/1.0@{user_dep}{channel_ref}")})
+        command = "create dep"
+        if user: command += f" --user={user_dep}"
+        if channel: command += f" --channel={channel_dep}"
+
+        tc.run(command)
+
+        command = "create ."
+        if user: command += f" --user={user}"
+        if channel: command += f" --channel={channel}"
+        tc.run(command)
+
+        create_layout = tc.created_layout()
+        cyclone_path = os.path.join(create_layout.metadata(), "sbom.cdx.json")
+        content = tc.load(cyclone_path)
+        content_json = json.loads(content)
+
+        assert content_json["components"][0]["bom-ref"].split("&user=")[1] == f"{user}&channel={channel}" if channel else user
+        assert content_json["dependencies"][0]["dependsOn"][0].split("&user=")[1] == f"{user_dep}&channel={channel_dep}" if channel_dep else user_dep
diff --git a/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_aliases.py b/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_aliases.py
new file mode 100644
index 000000000..b09dd2207
--- /dev/null
+++ b/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_aliases.py
@@ -0,0 +1,261 @@
+import textwrap
+import pytest
+
+from conan.test.utils.tools import TestClient
+
+new_value = "will_break_next"
+
+consumer = textwrap.dedent("""
+from conan import ConanFile
+from conan.tools.cmake import CMake
+
+class Consumer(ConanFile):
+    name = "consumer"
+    version = "1.0"
+    settings = "os", "compiler", "build_type", "arch"
+    generators = "CMakeDeps", "CMakeToolchain"
+    exports_sources = ["CMakeLists.txt"]
+    requires = "hello/1.0"
+
+    def build(self):
+        cmake = CMake(self)
+        cmake.configure()
+        cmake.build()
+""")
+
+
+@pytest.mark.tool("cmake", "3.27")
+def test_global_alias():
+    conanfile = textwrap.dedent("""
+    from conan import ConanFile
+
+    class Hello(ConanFile):
+        name = "hello"
+        version = "1.0"
+        settings = "os", "compiler", "build_type", "arch"
+
+        def package_info(self):
+            # the default global target is "hello::hello"
+            self.cpp_info.set_property("cmake_target_aliases", ["hello"])
+    """)
+
+    cmakelists = textwrap.dedent("""
+    cmake_minimum_required(VERSION 3.15)
+    project(test NONE)
+
+    find_package(hello REQUIRED)
+    get_target_property(_aliased_target hello ALIASED_TARGET)
+    message("hello aliased target: ${_aliased_target}")
+    """)
+
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}")
+
+    client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}")
+
+    assert "hello aliased target: hello::hello" in client.out
+
+
+@pytest.mark.tool("cmake", "3.27")
+def test_component_alias():
+    conanfile = textwrap.dedent("""
+    from conan import ConanFile
+
+    class Hello(ConanFile):
+        name = "hello"
+        version = "1.0"
+        settings = "os", "compiler", "build_type", "arch"
+
+        def package_info(self):
+            self.cpp_info.components["buy"].set_property("cmake_target_aliases",
+                ["hola::adios"])
+    """)
+
+    cmakelists = textwrap.dedent("""
+    cmake_minimum_required(VERSION 3.15)
+    project(test NONE)
+
+    find_package(hello REQUIRED)
+    get_target_property(_aliased_target hola::adios ALIASED_TARGET)
+    message("hola::adios aliased target: ${_aliased_target}")
+    """)
+
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}")
+
+    client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}")
+
+    assert "hola::adios aliased target: hello::buy" in client.out
+
+
+@pytest.mark.tool("cmake", "3.27")
+def test_global_and_component_alias():
+    conanfile = textwrap.dedent("""
+    from conan import ConanFile
+
+    class Hello(ConanFile):
+        name = "hello"
+        version = "1.0"
+        settings = "os", "compiler", "build_type", "arch"
+
+        def package_info(self):
+            self.cpp_info.set_property("cmake_target_aliases", ["hola::hola"])
+            self.cpp_info.components["buy"].set_property("cmake_target_aliases",
+                ["hola::adios"])
+    """)
+
+    cmakelists = textwrap.dedent("""
+    cmake_minimum_required(VERSION 3.15)
+    project(test NONE)
+
+    find_package(hello REQUIRED)
+    get_target_property(_aliased_target hola::hola ALIASED_TARGET)
+    message("hola::hola aliased target: ${_aliased_target}")
+    """)
+
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}")
+
+    client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}")
+
+    assert "hola::hola aliased target: hello::hello" in client.out
+
+
+@pytest.mark.tool("cmake", "3.27")
+def test_custom_name():
+    conanfile = textwrap.dedent("""
+    from conan import ConanFile
+
+    class Hello(ConanFile):
+        name = "hello"
+        version = "1.0"
+        settings = "os", "compiler", "build_type", "arch"
+
+        def package_info(self):
+            self.cpp_info.set_property("cmake_target_name", "ola::comprar")
+            self.cpp_info.set_property("cmake_target_aliases", ["hello"])
+    """)
+
+    cmakelists = textwrap.dedent("""
+    cmake_minimum_required(VERSION 3.15)
+    project(test NONE)
+
+    find_package(hello REQUIRED)
+    get_target_property(_aliased_target hello ALIASED_TARGET)
+    message("hello aliased target: ${_aliased_target}")
+    """)
+
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}")
+
+    client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}")
+
+    assert "hello aliased target: ola::comprar" in client.out
+
+
+@pytest.mark.tool("cmake", "3.27")
+def test_collide_component_alias():
+    conanfile = textwrap.dedent("""
+    from conan import ConanFile
+
+    class Hello(ConanFile):
+        name = "hello"
+        version = "1.0"
+        settings = "os", "compiler", "build_type", "arch"
+
+        def package_info(self):
+            self.cpp_info.components["buy"].set_property("cmake_target_aliases", ["hello::buy"])
+    """)
+
+    cmakelists = textwrap.dedent("""
+    cmake_minimum_required(VERSION 3.15)
+    project(test NONE)
+
+    find_package(hello REQUIRED)
+    """)
+
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}")
+
+    client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}", assert_error=True)
+
+    assert "Alias 'hello::buy' already defined as a target in hello/1.0" in client.out
+
+
+@pytest.mark.tool("cmake", "3.27")
+def test_collide_component_alias_to_alias():
+    conanfile = textwrap.dedent("""
+    from conan import ConanFile
+
+    class Hello(ConanFile):
+        name = "hello"
+        version = "1.0"
+        settings = "os", "compiler", "build_type", "arch"
+
+        def package_info(self):
+            self.cpp_info.components["ola"].set_property("cmake_target_aliases", ["hello::foo"])
+            self.cpp_info.components["buy"].set_property("cmake_target_aliases", ["hello::foo"])
+    """)
+
+    cmakelists = textwrap.dedent("""
+    cmake_minimum_required(VERSION 3.15)
+    project(test NONE)
+
+    find_package(hello REQUIRED)
+    """)
+
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}")
+
+    client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}", assert_error=True)
+
+    assert "Alias 'hello::foo' already defined in hello/1.0" in client.out
+
+
+@pytest.mark.tool("cmake", "3.27")
+@pytest.mark.parametrize("root_target", ["hello::custom", None])
+def test_skip_global_if_aliased(root_target):
+    target_line = f'self.cpp_info.set_property("cmake_target_name", "{root_target}")' if root_target else ""
+    target_name = root_target or "hello::hello"
+    conanfile = textwrap.dedent(f"""
+    from conan import ConanFile
+
+    class Hello(ConanFile):
+        name = "hello"
+        version = "1.0"
+        settings = "os", "compiler", "build_type", "arch"
+
+        def package_info(self):
+            {target_line}
+            self.cpp_info.components["foo"].set_property("cmake_target_aliases", ["{target_name}"])
+    """)
+
+    cmakelists = textwrap.dedent("""
+    cmake_minimum_required(VERSION 3.15)
+    project(test NONE)
+
+    find_package(hello REQUIRED)
+    get_target_property(_aliased_target hello::hello ALIASED_TARGET)
+    message("hello::hello aliased target: ${_aliased_target}")
+    """)
+
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}")
+
+    client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists})
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}", assert_error=True)
+
+    assert f"Can't define an alias '{target_name}' for the root target '{target_name}' in hello/1.0" in client.out
diff --git a/test/integration/cache/backup_sources_test.py b/test/integration/cache/backup_sources_test.py
index 344ce63d2..94e5184b9 100644
--- a/test/integration/cache/backup_sources_test.py
+++ b/test/integration/cache/backup_sources_test.py
@@ -62,17 +62,16 @@ class TestDownloadCacheBackupSources:
 
             assert 2 == len(os.listdir(os.path.join(tmp_folder, "s")))
             content = json.loads(load(os.path.join(tmp_folder, "s", sha256 + ".json")))
-            assert "http://localhost.mirror:5000/myfile.txt" in content["references"]["unknown"]
-            assert "http://localhost:5000/myfile.txt" in content["references"]["unknown"]
-            assert len(content["references"]["unknown"]) == 2
+            assert "http://localhost.mirror:5000/myfile.txt" in content["references"]["pkg/1.0"]
+            assert len(content["references"]["pkg/1.0"]) == 1
 
             # Ensure the cache is working and we didn't break anything by modifying the summary
             client.run("source .")
             assert "Downloading file" not in client.out
 
-            client.run("create .")
+            client.run("create . --user=barbarian")
             content = json.loads(load(os.path.join(tmp_folder, "s", sha256 + ".json")))
-            assert content["references"]["pkg/1.0"] == \
+            assert content["references"]["pkg/1.0@barbarian"] == \
                    ["http://localhost.mirror:5000/myfile.txt"]
 
             client.run("create . --user=barbarian --channel=stable")
@@ -214,9 +213,9 @@ class TestDownloadCacheBackupSources:
                             from conan.tools.files import download
                             class Pkg2(ConanFile):
                                 name = "pkg"
-                                version = "1.0"
                                 def source(self):
-                                    download(self, "{self.file_server.fake_url}/internet/myfile.txt", "myfile.txt",
+                                    download(self, "{self.file_server.fake_url}/internet/myfile.txt",
+                                             "myfile.txt",
                                              sha256="{sha256}")
                             """)
 
@@ -234,7 +233,7 @@ class TestDownloadCacheBackupSources:
         s_folder = os.path.join(self.download_cache_folder, "s")
         assert len(os.listdir(s_folder)) == 2
 
-        self.client.run("export .")
+        self.client.run("export . --version=1.0")
         self.client.run("upload * -c -r=default")
         assert "No backup sources files to upload" in self.client.out
 
@@ -746,7 +745,8 @@ class TestDownloadCacheBackupSources:
         # Ensure we are testing for an already uploaded recipe
         assert f"Recipe 'pkg/1.0#{exported_rev}' already in server, skipping upload" in self.client.out
 
-    def test_source_then_upload_workflow(self):
+    @pytest.mark.parametrize("unknown", [True, False])
+    def test_source_then_upload_workflow(self, unknown):
         mkdir(os.path.join(self.download_cache_folder, "s"))
 
         http_server_base_folder_internet = os.path.join(self.file_server.store, "internet")
@@ -770,7 +770,12 @@ class TestDownloadCacheBackupSources:
                             f"core.sources:upload_url={self.file_server.fake_url}/backup/"})
 
         self.client.save({"conanfile.py": conanfile})
-        self.client.run("source .")
+        ref_args = "" if unknown else "--name foo --version 1.0"
+        reference_key = "unknown" if unknown else "foo/1.0"
+        self.client.run(f"source . {ref_args}")
+        content = json.loads(load(os.path.join(self.download_cache_folder, "s", sha256 + ".json")))
+        assert len(content["references"][reference_key]) == 1
+
         self.client.run("cache backup-upload")
         # This used to crash because we were trying to list a missing dir if only exports were made
         assert "[Errno 2] No such file or directory" not in self.client.out
diff --git a/test/integration/command/export/export_test.py b/test/integration/command/export/export_test.py
index 8bd40f9e9..01b976c85 100644
--- a/test/integration/command/export/export_test.py
+++ b/test/integration/command/export/export_test.py
@@ -33,6 +33,22 @@ class TestExportSettings:
         client.save({"conanfile.py": GenConanfile("lib", "1.0")})
         client.run("export . --user=lasote")
         assert "lib/1.0@lasote: Exporting package recipe" in client.out
+        client.run("export . --channel=channel", assert_error=True)
+        assert "Can't specify channel 'channel' without user" in client.out
+
+        client.save({"conanfile.py": GenConanfile("lib", "1.0").with_class_attribute('channel = "channel"')})
+        client.run("export .", assert_error=True)
+        assert "Can't specify channel 'channel' without user" in client.out
+
+        client.run("export . --user=user")
+        assert "Exported: lib/1.0@user/channel" in client.out
+
+        client.save({"conanfile.py": GenConanfile("lib", "1.0").with_class_attribute('user = "user"')})
+        client.run("export .")
+        assert "lib/1.0@user: Exported" in client.out
+        client.run("export . --channel=channel")
+        assert "lib/1.0@user/channel: Exported" in client.out
+
 
     def test_export_read_only(self):
         client = TestClient(light=True)
diff --git a/test/integration/command/install/install_test.py b/test/integration/command/install/install_test.py
index 81e1c51d2..e0e252764 100644
--- a/test/integration/command/install/install_test.py
+++ b/test/integration/command/install/install_test.py
@@ -32,7 +32,7 @@ def test_install_reference_error(client):
     assert "ERROR: Can't use --name, --version, --user or --channel arguments with --requires" in client.out
     client.save({"conanfile.py": GenConanfile("pkg", "1.0")})
     client.run("install . --channel=testing", assert_error=True)
-    assert "Can't specify channel without user" in client.out
+    assert "Can't specify channel 'testing' without user" in client.out
 
 
 def test_install_args_error():
diff --git a/test/integration/conanfile/conan_data_test.py b/test/integration/conanfile/conan_data_test.py
index e05d4d977..d1abb4ffd 100644
--- a/test/integration/conanfile/conan_data_test.py
+++ b/test/integration/conanfile/conan_data_test.py
@@ -1,3 +1,4 @@
+import json
 import os
 import shutil
 import sys
@@ -193,6 +194,22 @@ class Lib(ConanFile):
         client.run("export-pkg . --name=name --version=version")
         self.assertIn("My URL: this url", client.out)
 
+    def test_conan_data_serialize(self):
+        c = TestClient(light=True)
+        conandata = textwrap.dedent("""
+            src:
+                url: "this url"
+        """)
+        c.save({"conanfile.py": GenConanfile("pkg", "0.1"),
+                "conandata.yml": conandata})
+        c.run("graph info . --format=json")
+        graph = json.loads(c.stdout)
+        assert graph["graph"]["nodes"]["0"]["conandata"]["src"] == {"url": "this url"}
+
+        c.run("inspect . --format=json")
+        result = json.loads(c.stdout)
+        assert result["conandata"]["src"] == {"url": "this url"}
+
 
 class TestConanDataUpdate:
     """
diff --git a/test/integration/configuration/required_version_test.py b/test/integration/configuration/required_version_test.py
index de712afde..fc0092054 100644
--- a/test/integration/configuration/required_version_test.py
+++ b/test/integration/configuration/required_version_test.py
@@ -1,5 +1,4 @@
 import os
-import unittest
 import mock
 
 from conan import __version__
@@ -8,44 +7,49 @@ from conan.test.utils.tools import TestClient
 from conan.internal.util.files import save
 
 
-class RequiredVersionTest(unittest.TestCase):
+class TestRequiredVersion:
 
     @mock.patch("conan.__version__", "1.26.0")
     def test_wrong_version(self):
         required_version = "1.23.0"
-        client = TestClient()
+        client = TestClient(light=True)
         client.save_home({"global.conf": f"core:required_conan_version={required_version}"})
         client.run("help", assert_error=True)
-        self.assertIn("Current Conan version (1.26.0) does not satisfy the defined "
-                      "one ({})".format(required_version), client.out)
+        assert f"Current Conan version (1.26.0) does not satisfy the defined one ({required_version})" in client.out
 
     @mock.patch("conan.__version__", "1.22.0")
     def test_exact_version(self):
         required_version = "1.22.0"
-        client = TestClient()
+        client = TestClient(light=True)
         client.save_home({"global.conf": f"core:required_conan_version={required_version}"})
         client.run("--help")
 
     @mock.patch("conan.__version__", "2.1.0")
     def test_lesser_version(self):
         required_version = "<3.0"
-        client = TestClient()
+        client = TestClient(light=True)
         client.save_home({"global.conf": f"core:required_conan_version={required_version}"})
         client.run("--help")
 
     @mock.patch("conan.__version__", "1.0.0")
     def test_greater_version(self):
         required_version = ">0.1.0"
-        client = TestClient()
+        client = TestClient(light=True)
         client.save_home({"global.conf": f"core:required_conan_version={required_version}"})
         client.run("--help")
 
+    @mock.patch("conan.__version__", "1.0.0")
+    def test_greater_version_cc_override(self):
+        client = TestClient(light=True)
+        client.save_home({"global.conf": f"core:required_conan_version=>0.1.0"})
+        client.run("config home -cc core:required_conan_version=>2.0", assert_error=True)
+        assert f"Current Conan version (1.0.0) does not satisfy the defined one (>2.0)" in client.out
+
     def test_bad_format(self):
         required_version = "1.0.0.0-foobar"
         cache_folder = temp_folder()
         save(os.path.join(cache_folder, "global.conf"),
              f"core:required_conan_version={required_version}")
-        c = TestClient(cache_folder)
+        c = TestClient(cache_folder, light=True)
         c.run("version", assert_error=True)
-        self.assertIn("Current Conan version ({}) does not satisfy the defined one ({})"
-                      .format(__version__, required_version), c.out)
+        assert f"Current Conan version ({__version__}) does not satisfy the defined one ({required_version})" in c.out
diff --git a/test/integration/remote/auth_test.py b/test/integration/remote/auth_test.py
index 25ae03cb3..42d0b3eb7 100644
--- a/test/integration/remote/auth_test.py
+++ b/test/integration/remote/auth_test.py
@@ -1,35 +1,25 @@
 import copy
 import os
 import textwrap
-import unittest
-import pytest
 
+import pytest
 from requests.models import Response
 
-from conan.internal.api.remotes.localdb import LocalDB
 from conan.api.model import RecipeReference
-from conan.internal.paths import CONANFILE
+from conan.internal.api.remotes.localdb import LocalDB
+from conan.internal.util.files import save
 from conan.test.assets.genconanfile import GenConanfile
+from conan.test.utils.env import environment_update
 from conan.test.utils.test_files import temp_folder
 from conan.test.utils.tools import TestClient
 from conan.test.utils.tools import TestRequester
 from conan.test.utils.tools import TestServer
-from conan.test.utils.env import environment_update
-from conan.internal.util.files import save
-
-conan_content = """
-from conan import ConanFile
-
-class OpenSSLConan(ConanFile):
-    name = "openssl"
-    version = "2.0.1"
-    files = '*'
-"""
 
 
-class AuthorizeTest(unittest.TestCase):
+class TestAuthorize:
 
-    def setUp(self):
+    @pytest.fixture(autouse=True)
+    def setup(self):
         self.servers = {}
         self.ref = RecipeReference.loads("openssl/2.0.1@lasote/testing")
         # Create a default remote. R/W is not authorized for ref,
@@ -46,25 +36,25 @@ class AuthorizeTest(unittest.TestCase):
         self.conan = TestClient(servers=self.servers, inputs=["bad", "1",
                                                               "bad2", "2",
                                                               "nacho@gmail.com", "nachopass"])
-        save(os.path.join(self.conan.current_folder, CONANFILE), conan_content)
+        self.conan.save({"conanfile.py": GenConanfile("openssl", "2.0.1")})
         self.conan.run("export . --user=lasote --channel=testing")
-        errors = self.conan.run("upload %s -r default --only-recipe" % str(self.ref))
+        errors = self.conan.run("upload %s -r default --only-recipe -v=quiet" % str(self.ref))
         # Check that return was  ok
-        self.assertFalse(errors)
+        assert not errors
         # Check that upload was granted
         rev = self.test_server.server_store.get_last_revision(self.ref).revision
         ref = copy.copy(self.ref)
         ref.revision = rev
-        self.assertTrue(os.path.exists(self.test_server.server_store.export(ref)))
-        self.assertIn("Please enter a password for user 'bad'", self.conan.out)
-        self.assertIn("Please enter a password for user 'bad2'", self.conan.out)
-        self.assertIn("Please enter a password for user 'nacho@gmail.com'", self.conan.out)
+        assert os.path.exists(self.test_server.server_store.export(ref))
+        assert "Please enter a password for user 'bad'" in self.conan.out
+        assert "Please enter a password for user 'bad2'" in self.conan.out
+        assert "Please enter a password for user 'nacho@gmail.com'" in self.conan.out
 
     def test_auth_with_env(self):
 
         def _upload_with_credentials(credentials, assert_error=False):
             cli = TestClient(servers=self.servers)
-            save(os.path.join(cli.current_folder, CONANFILE), conan_content)
+            cli.save({"conanfile.py": GenConanfile("openssl", "2.0.1")})
             cli.run("export . --user=lasote --channel=testing")
             with environment_update(credentials):
                 cli.run("upload %s -r default --only-recipe" % str(self.ref),
@@ -74,42 +64,42 @@ class AuthorizeTest(unittest.TestCase):
         # Try with remote name in credentials
         client = _upload_with_credentials({"CONAN_PASSWORD_DEFAULT": "pepepass",
                                            "CONAN_LOGIN_USERNAME_DEFAULT": "pepe"})
-        self.assertIn("Got username 'pepe' from environment", client.out)
-        self.assertIn("Got password '******' from environment", client.out)
+        assert "Got username 'pepe' from environment" in client.out
+        assert "Got password '******' from environment" in client.out
 
         # Try with generic password and login
         client = _upload_with_credentials({"CONAN_PASSWORD": "pepepass",
                                            "CONAN_LOGIN_USERNAME_DEFAULT": "pepe"})
-        self.assertIn("Got username 'pepe' from environment", client.out)
-        self.assertIn("Got password '******' from environment", client.out)
+        assert "Got username 'pepe' from environment" in client.out
+        assert "Got password '******' from environment" in client.out
 
         # Try with generic password and generic login
         client = _upload_with_credentials({"CONAN_PASSWORD": "pepepass",
                                            "CONAN_LOGIN_USERNAME": "pepe"})
-        self.assertIn("Got username 'pepe' from environment", client.out)
-        self.assertIn("Got password '******' from environment", client.out)
+        assert "Got username 'pepe' from environment" in client.out
+        assert "Got password '******' from environment" in client.out
 
         # Bad pass raise
         client = _upload_with_credentials({"CONAN_PASSWORD": "bad",
                                            "CONAN_LOGIN_USERNAME": "pepe"}, assert_error=True)
-        self.assertIn("Authentication error in remote 'default'", client.out)
+        assert "Authentication error in remote 'default'" in client.out
 
     def test_max_retries(self):
         """Bad login 3 times"""
         client = TestClient(servers=self.servers, inputs=["baduser", "badpass",
                                                           "baduser", "badpass2",
                                                           "baduser3", "badpass3"])
-        save(os.path.join(client.current_folder, CONANFILE), conan_content)
+        client.save({"conanfile.py": GenConanfile("openssl", "2.0.1")})
         client.run("export . --user=lasote --channel=testing")
         errors = client.run("upload %s -r default --only-recipe" % str(self.ref), assert_error=True)
         # Check that return was not ok
-        self.assertTrue(errors)
+        assert errors
         # Check that upload was not granted
         rev = self.servers["default"].server_store.get_last_revision(self.ref)
-        self.assertIsNone(rev)
+        assert rev is None
 
         # Check that login failed all times
-        self.assertIn("Too many failed login attempts, bye!", client.out)
+        assert "Too many failed login attempts, bye!" in client.out
 
     def test_no_client_username_checks(self):
         """Checks whether client username checks are disabled."""
@@ -119,7 +109,7 @@ class AuthorizeTest(unittest.TestCase):
                                                           "badpass",
                                                           "nacho@gmail.com", "nachopass"])
 
-        save(os.path.join(client.current_folder, CONANFILE), conan_content)
+        client.save({"conanfile.py": GenConanfile("openssl", "2.0.1")})
         client.run("export . --user=lasote --channel=testing")
         client.run("upload %s -r default --only-recipe" % str(self.ref))
 
@@ -127,9 +117,9 @@ class AuthorizeTest(unittest.TestCase):
         rev = self.test_server.server_store.get_last_revision(self.ref).revision
         ref = copy.copy(self.ref)
         ref.revision = rev
-        self.assertTrue(os.path.exists(self.test_server.server_store.export(ref)))
-        self.assertIn("Please enter a password for user 'some_random.special!characters' on remote 'default'",
-                      client.out)
+        assert os.path.exists(self.test_server.server_store.export(ref))
+        assert ("Please enter a password for user 'some_random.special!characters' "
+                "on remote 'default'") in client.out
 
     def test_authorize_disabled_remote(self):
         tc = TestClient(servers=self.servers)
@@ -139,7 +129,8 @@ class AuthorizeTest(unittest.TestCase):
         # This used to fail when the authentication was not possible for disabled remotes
         tc.run("remote disable default")
         tc.run("remote login default pepe -p pepepass")
-        self.assertIn("Changed user of remote 'default' from 'None' (anonymous) to 'pepe' (authenticated)", tc.out)
+        assert ("Changed user of remote 'default' "
+                "from 'None' (anonymous) to 'pepe' (authenticated)") in tc.out
 
 
 class TestAuthenticationTest:
diff --git a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
index ef72cc991..0f329d469 100644
--- a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
+++ b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
@@ -482,3 +482,42 @@ def test_requires_to_application_component():
     assert "automake::automake" not in targets
     assert "# Requirement automake::mylibapp => Full link: True" in targets
     assert "$<$<CONFIG:RELEASE>:automake::mylibapp>" in targets
+
+
+def test_alias_cmakedeps_set_property():
+    tc = TestClient()
+    tc.save({"dep/conanfile.py": textwrap.dedent("""
+
+        from conan import ConanFile
+        class Dep(ConanFile):
+            name = "dep"
+            version = "1.0"
+            settings = "os", "compiler", "build_type", "arch"
+            def package_info(self):
+                self.cpp_info.components["mycomp"].set_property("cmake_target_name", "dep::mycomponent")
+        """),
+             "conanfile.py": textwrap.dedent("""
+             from conan import ConanFile
+             from conan.tools.cmake import CMakeDeps, CMake
+             class Pkg(ConanFile):
+                name = "pkg"
+                version = "1.0"
+                settings = "os", "compiler", "build_type", "arch"
+
+                requires = "dep/1.0"
+
+                def generate(self):
+                    deps = CMakeDeps(self)
+                    deps.set_property("dep", "cmake_target_aliases", ["alias", "dep::other_name"])
+                    deps.set_property("dep::mycomp", "cmake_target_aliases", ["component_alias", "dep::my_aliased_component"])
+                    deps.generate()
+             """)})
+    tc.run("create dep")
+    tc.run(f"install . -c tools.cmake.cmakedeps:new={new_value}")
+    targets_data = tc.load('dep-Targets-release.cmake')
+    assert "add_library(dep::dep" in targets_data
+    assert "add_library(alias" in targets_data
+    assert "add_library(dep::other_name" in targets_data
+
+    assert "add_library(component_alias" in targets_data
+    assert "add_library(dep::my_aliased_component" in targets_data
diff --git a/test/integration/toolchains/premake/test_premakedeps.py b/test/integration/toolchains/premake/test_premakedeps.py
index 9e42410b8..e4a8c9b35 100644
--- a/test/integration/toolchains/premake/test_premakedeps.py
+++ b/test/integration/toolchains/premake/test_premakedeps.py
@@ -1,5 +1,6 @@
 import textwrap
 
+from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.tools import TestClient
 
 
@@ -63,3 +64,73 @@ def test_premakedeps():
     assert_vars_file(client, 'debug')
     assert_vars_file(client, 'release')
 
+
+def test_premakedeps_link_order():
+    client = TestClient()
+    profile = textwrap.dedent(
+        """
+        [settings]
+        os=Linux
+        arch=x86_64
+        compiler=gcc
+        compiler.version=9
+        compiler.cppstd=17
+        compiler.libcxx=libstdc++
+        build_type=Release
+        """)
+    client.save(
+        {
+            "liba/conanfile.py": GenConanfile("liba")
+            .with_settings("os", "compiler", "build_type", "arch")
+            .with_version("1.0")
+            .with_generator("PremakeDeps")
+            .with_generator("PremakeToolchain")
+            .with_package_info(cpp_info={"libs": ["liba"]}),
+
+            "libb/conanfile.py": GenConanfile("libb")
+            .with_settings("os", "compiler", "build_type", "arch")
+            .with_version("1.0")
+            .with_requires("liba/1.0")
+            .with_generator("PremakeDeps")
+            .with_package_info(
+                cpp_info={
+                    "components": {
+                        "libb1": {"requires": ["liba::liba"]},
+                        "libb2": {"requires": ["libb1"]},
+                    }
+                }
+            ),
+
+            "libc/conanfile.py": GenConanfile("libc")
+            .with_settings("os", "compiler", "build_type", "arch")
+            .with_version("1.0")
+            .with_requires("libb/1.0")
+            .with_generator("PremakeDeps")
+            .with_package_info(cpp_info={"libs": ["libc"]}),
+
+            "consumer/conanfile.py": GenConanfile("consumer")
+            .with_settings("os", "compiler", "build_type", "arch")
+            .with_version("1.0")
+            .with_requires("libc/1.0")
+            .with_generator("PremakeDeps"),
+
+            "profile": profile
+        }
+    )
+    client.run("create liba -pr profile")
+    client.run("create libb -pr profile")
+    client.run("create libc -pr profile")
+    client.run("install consumer -pr profile")
+    contents = client.load("consumer/conanconfig.premake5.lua")
+    assert 'include "conanconfig_release_x86_64.premake5.lua"' in contents
+    contents = client.load("consumer/conanconfig_release_x86_64.premake5.lua")
+    # Check correct order of dependencies: more dependent libs should be linked first
+    assert 't_conan_deps_order["release_x86_64"] = {"libc", "libb", "liba"}' in contents
+
+    # Check previous configurations are preserved when installing new configuration
+    client.run("install consumer -pr profile -s build_type=Debug --build=missing")
+    contents = client.load("consumer/conanconfig.premake5.lua")
+    assert 'include "conanconfig_release_x86_64.premake5.lua"' in contents
+    assert 'include "conanconfig_debug_x86_64.premake5.lua"' in contents
+    contents = client.load("consumer/conanconfig_debug_x86_64.premake5.lua")
+    assert 't_conan_deps_order["debug_x86_64"] = {"libc", "libb", "liba"}' in contents
diff --git a/test/unittests/util/detect_test.py b/test/unittests/util/detect_test.py
index 476b89b16..be71c00de 100644
--- a/test/unittests/util/detect_test.py
+++ b/test/unittests/util/detect_test.py
@@ -64,14 +64,18 @@ class TestDetect:
     ["g++ (Conan-Build-gcc--binutils-2.42) 14.1.0", "14.1.0"],
     ["gcc.exe (x86_64-posix-seh-rev0, Built by MinGW-Builds project) 15.1.0", "15.1.0"],
     ["gcc.exe (x86_64-posix-seh-rev0, Built by MinGW-Builds project) 15.15.0", "15.15.0"],
+    ["gcc (crosstool-NG 1.27.0) 13.3.0", "13.3.0"],
+    ["gcc (crosstool-NG 1.27.0) 13.3", "13.3"],
     ["gcc (GCC) 4.8.1", "4.8.1"],
     ["clang version 18.1.0rc (https://github.com/llvm/llvm-project.git 461274b81d8641eab64d494accddc81d7db8a09e)", "18.1.0"],
     ["cc.exe (Rev3, Built by MSYS2 project) 14.0", "14.0"],
     ["clang version 18 (https://github.com/llvm/llvm-project.git 461274b81d8641eab64d494accddc81d7db8a09e)", "18"],
     ["cc.exe (Rev3, Built by MSYS2 project) 14", "14"],
+    ["cc.exe 14.2", "14.2"],
+    ["cc1 14.2", "14.2"],
 ])
 @patch("conan.internal.api.detect.detect_api.detect_runner")
-def test_detect_cc_versionings(detect_runner_mock, version_return, expected_version):
+def test_detect_cc_versioning(detect_runner_mock, version_return, expected_version):
     detect_runner_mock.return_value = 0, version_return
     compiler, installed_version, compiler_exe = _cc_compiler()
     # Using private _value attribute to compare the str that reaches it, so no space issues arise