Repository: conan-io/conan
Pull request #18247 (2025-05-06 16:19:35)

[new command] `conan report diff` to inspect diffs between versions and revisions 
Changelog: Feature: New `conan report diff` command to inspect diffs between versions and revisions
Docs: https://github.com/conan-io/docs/pull/4127

That still need to be done:
* [x] Testing
* [x] Remove the open directives, make this more in line with other Conan commands and let the user open the resulting html file by themselves
* [x] Maybe make a default txt formatter that just spits the diff, and have the html formatter output the necessary files? But the formatters can't create more than 1 file 🤔 
* [x] Have a way to filter what gets diffed - maybe someone can we just care about source code, or only about build-system related changes



Patch:
diff --git a/conan/api/conan_api.py b/conan/api/conan_api.py
index 909148e14..1b2c46ee8 100644
--- a/conan/api/conan_api.py
+++ b/conan/api/conan_api.py
@@ -7,6 +7,7 @@ from conan.api.subapi.cache import CacheAPI
 from conan.api.subapi.command import CommandAPI
 from conan.api.subapi.local import LocalAPI
 from conan.api.subapi.lockfile import LockfileAPI
+from conan.api.subapi.report import ReportAPI
 from conan.api.subapi.workspace import WorkspaceAPI
 from conan.api.subapi.config import ConfigAPI
 from conan.api.subapi.download import DownloadAPI
@@ -72,6 +73,7 @@ class ConanAPI:
         self.audit = AuditAPI(self)
         # Now, lazy loading of editables
         self.workspace = WorkspaceAPI(self)
+        self.report = ReportAPI(self)
 
         _check_conan_version(self)
 
diff --git a/conan/api/subapi/config.py b/conan/api/subapi/config.py
index f7342e475..78bd3fec5 100644
--- a/conan/api/subapi/config.py
+++ b/conan/api/subapi/config.py
@@ -66,11 +66,11 @@ class ConfigAPI:
 
         # Basic checks of the package: correct package_type and no-dependencies
         deps_graph.report_graph_error()
-        pkg = deps_graph.root.dependencies[0].dst
+        pkg = deps_graph.root.edges[0].dst
         ConanOutput().info(f"Configuration from package: {pkg}")
         if pkg.conanfile.package_type is not PackageType.CONF:
             raise ConanException(f'{pkg.conanfile} is not of package_type="configuration"')
-        if pkg.dependencies:
+        if pkg.edges:
             raise ConanException(f"Configuration package {pkg.ref} cannot have dependencies")
 
         # The computation of the "package_id" and the download of the package is done as usual
diff --git a/conan/api/subapi/report.py b/conan/api/subapi/report.py
new file mode 100644
index 000000000..f1a2a1c8e
--- /dev/null
+++ b/conan/api/subapi/report.py
@@ -0,0 +1,144 @@
+import base64
+import os
+from io import StringIO
+
+from conan.api.output import ConanOutput
+from conan.errors import ConanException
+from conan.api.model import RecipeReference
+from conan.internal.conan_app import ConanApp
+from conan.internal.errors import conanfile_exception_formatter
+from conan.internal.graph.graph import CONTEXT_HOST
+from conan.internal.graph.profile_node_definer import initialize_conanfile_profile
+from conan.internal.source import config_source
+from conan.internal.util.runners import conan_run
+
+
+class ReportAPI:
+    def __init__(self, conan_api):
+        self.conan_api = conan_api
+
+    def diff(self, old_reference, new_reference, remotes, old_path=None, new_path=None, cwd=None):
+        """
+        Compare two recipes and return the differences.
+        :param old_reference: The reference of the old recipe.
+        :param new_reference: The reference of the new recipe.
+        :param remotes: List of remotes to search for the recipes.
+        :param old_path: Optional path to the old recipe's conanfile.py.
+        :param new_path: Optional path to the new recipe's conanfile.py.
+        :param cwd: Current working directory, used to resolve paths.
+        :return: A dictionary with the differences between the two recipes.
+        """
+
+        def _source(path_to_conanfile, reference):
+            if path_to_conanfile is None:
+                export_ref, cache_path = _get_ref_from_cache_or_remote(self.conan_api, reference, remotes)
+            else:
+                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)
+            return export_ref, cache_path
+
+
+        old_export_ref, old_cache_path = _source(old_path, old_reference)
+        new_export_ref, new_cache_path = _source(new_path, new_reference)
+
+        old_diff_path = os.path.abspath(os.path.join(old_cache_path, os.path.pardir)).replace("\\",
+                                                                                              "/")
+        new_diff_path = os.path.abspath(os.path.join(new_cache_path, os.path.pardir)).replace("\\",
+                                                                                              "/")
+
+        src_prefix = base64.b64encode(str(new_export_ref.repr_notime()).encode()).decode() + "/"
+        dst_prefix = base64.b64encode(str(old_export_ref.repr_notime()).encode()).decode() + "/"
+
+        command = (f'git diff --no-index --src-prefix "{src_prefix}" --dst-prefix "{dst_prefix}" '
+                   f'"{old_diff_path}" "{new_diff_path}"')
+
+        ConanOutput().info(
+            f"Generating diff from {old_export_ref.repr_notime()} to {new_export_ref.repr_notime()} (this might take a while)")
+        ConanOutput().info(command)
+
+        stdout, stderr = StringIO(), StringIO()
+        conan_run(command, stdout=stdout, stderr=stderr)
+        diff = stdout.getvalue()
+
+        if old_path:
+            self.conan_api.remove.recipe(old_export_ref)
+        if new_path:
+            self.conan_api.remove.recipe(new_export_ref)
+
+        return {
+            "diff": diff,
+            "old_export_ref": old_export_ref,
+            "new_export_ref": new_export_ref,
+            "old_cache_path": old_diff_path,
+            "new_cache_path": new_diff_path,
+            "src_prefix": src_prefix,
+            "dst_prefix": dst_prefix,
+        }
+
+def _configure_source(conan_api, 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,
+                                         remotes=remotes)
+    # This profile is empty, but with the conf from global.conf
+    profile = conan_api.profiles.get_profile([])
+    initialize_conanfile_profile(conanfile, profile, profile, CONTEXT_HOST, False)
+    # This is important, otherwise the ``conan source`` doesn't define layout and fails
+    if hasattr(conanfile, "layout"):
+        with conanfile_exception_formatter(conanfile, "layout"):
+            conanfile.layout()
+
+    recipe_layout = app.cache.recipe_layout(ref)
+    export_source_folder = recipe_layout.export_sources()
+    source_folder = recipe_layout.source()
+
+    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)
+
+def _get_ref_from_cache_or_remote(conan_api, reference, enabled_remotes):
+    ref = RecipeReference.loads(reference)
+    full_ref, matching_remote = None, False
+    # The first remote is None, which means local cache for the list subapi methods
+    for remote in [None] + enabled_remotes:
+        if ref.revision:
+            no_rrev_ref = RecipeReference.loads(reference)
+            no_rrev_ref.revision = None
+            try:
+                remote_revisions = conan_api.list.recipe_revisions(no_rrev_ref, remote)
+                if ref in remote_revisions:
+                    full_ref = ref
+                    matching_remote = remote
+                    break
+            except:
+                continue
+        else:
+            try:
+                latest_recipe_revision = conan_api.list.latest_recipe_revision(ref, remote)
+            except:
+                continue
+            if full_ref is None or (latest_recipe_revision.timestamp > full_ref.timestamp):
+                full_ref = latest_recipe_revision
+                matching_remote = remote
+    if full_ref is None or matching_remote is False:
+        raise ConanException(f"No matching reference for {reference} in remotes.\n"
+                             "If you want to check against a local recipe, add an additional --{old,new}-path arg.\n")
+    if matching_remote is not None:
+        conan_api.download.recipe(full_ref, matching_remote)
+    cache_path = conan_api.cache.export_path(full_ref)
+    return full_ref, cache_path
+
+
+def _export_recipe_from_path(conan_api, path_to_conanfile, reference, enabled_remotes, cwd=None):
+    path = conan_api.local.get_conanfile_path(path_to_conanfile, cwd, py=True)
+    ref = RecipeReference.loads(reference)
+    export_ref, conanfile = conan_api.export.export(path=path,
+                                                    name=ref.name, version=str(ref.version),
+                                                    user=ref.user, channel=ref.channel,
+                                                    lockfile=None,
+                                                    remotes=enabled_remotes)
+    cache_path = conan_api.cache.export_path(export_ref)
+    return export_ref, cache_path
diff --git a/conan/cli/commands/audit.py b/conan/cli/commands/audit.py
index ddddad029..9bbe1fff5 100644
--- a/conan/cli/commands/audit.py
+++ b/conan/cli/commands/audit.py
@@ -3,7 +3,7 @@ import os
 
 from conan.api.conan_api import ConanAPI
 from conan.api.input import UserInput
-from conan.api.model import MultiPackagesList
+from conan.api.model import MultiPackagesList, RecipeReference
 from conan.api.output import cli_out_write, ConanOutput
 from conan.api.subapi.audit import CONAN_CENTER_AUDIT_PROVIDER_NAME
 from conan.cli import make_abs_path
@@ -14,6 +14,7 @@ from conan.cli.formatters.audit.vulnerabilities import text_vuln_formatter, json
 from conan.cli.printers import print_profiles
 from conan.cli.printers.graph import print_graph_basic
 from conan.errors import ConanException
+from conan.internal.util.files import load
 
 
 def _add_provider_arg(subparser):
@@ -108,18 +109,15 @@ def audit_list(conan_api: ConanAPI, parser, subparser, *args):
     """
     List the vulnerabilities of the given reference.
     """
-    subparser.add_argument("reference", help="Reference to list vulnerabilities for", nargs="?")
-    subparser.add_argument("-l", "--list", help="pkglist file to list vulnerabilities for")
+    input_group = subparser.add_mutually_exclusive_group(required=True)
+    input_group.add_argument("reference", help="Reference to list vulnerabilities for", nargs="?")
+    input_group.add_argument("-l", "--list", help="pkglist file to list vulnerabilities for")
+    input_group.add_argument("-s", "--sbom", help="sbom file to list vulnerabilities for")
+    input_group.add_argument("-lock", "--lockfile", help="lockfile file to list vulnerabilities for")
     subparser.add_argument("-r", "--remote", help="Remote to use for listing")
     _add_provider_arg(subparser)
     args = parser.parse_args(*args)
 
-    if not args.reference and not args.list:
-        raise ConanException("Please specify a reference or a pkglist file")
-
-    if args.reference and args.list:
-        raise ConanException("Please specify a reference or a pkglist file, not both")
-
     provider = conan_api.audit.get_provider(args.provider or CONAN_CENTER_AUDIT_PROVIDER_NAME)
 
     if args.list:
@@ -130,7 +128,19 @@ def audit_list(conan_api: ConanAPI, parser, subparser, *args):
         refs_to_list = package_list.serialize()
         references = list(refs_to_list.keys())
         if not references:  # the package list might contain only refs, no revs
-            ConanOutput().warning("Nothing to list, package list do not contain recipe revisions")
+            ConanOutput().warning("Nothing to list, package list does not contain recipe revisions")
+    elif args.sbom:
+        sbom_file = make_abs_path(args.sbom)
+        sbom = json.loads(load(sbom_file))
+        if sbom.get("bomFormat") != "CycloneDX":
+            raise ConanException(f"Unsupported SBOM format, only CycloneDX is supported.")
+        purls = [component["purl"] for component in sbom["components"]]
+        references = [purl.split("pkg:conan/")[1].replace("@", "/") for purl in purls]
+    elif args.lockfile:
+        lock_file = make_abs_path(args.lockfile)
+        lockfile = conan_api.lockfile.get_lockfile(lock_file).serialize()
+        references = [str(RecipeReference.loads(ref[0] if isinstance(ref, tuple) else ref))
+                      for ref in lockfile["requires"]]
     else:
         references = [args.reference]
     return conan_api.audit.list(references, provider)
diff --git a/conan/cli/commands/cache.py b/conan/cli/commands/cache.py
index 1f1240caf..52abdb58b 100644
--- a/conan/cli/commands/cache.py
+++ b/conan/cli/commands/cache.py
@@ -187,5 +187,6 @@ def cache_backup_upload(conan_api: ConanAPI, parser, subparser, *args):
     """
     Upload all the source backups present in the cache
     """
+    args = parser.parse_args(*args)
     files = conan_api.cache.get_backup_sources()
     conan_api.upload.upload_backup_sources(files)
diff --git a/conan/cli/commands/create.py b/conan/cli/commands/create.py
index ec566ecac..f4ea4ff51 100644
--- a/conan/cli/commands/create.py
+++ b/conan/cli/commands/create.py
@@ -107,8 +107,8 @@ def create(conan_api, parser, *args):
         if args.test_folder is None else args.test_folder
     test_conanfile_path = _get_test_conanfile_path(test_package_folder, path)
     # If the user provide --test-missing and the binary was not built from source, skip test_package
-    if args.test_missing and deps_graph.root.dependencies\
-            and deps_graph.root.dependencies[0].dst.binary != "Build":
+    if args.test_missing and deps_graph.root.edges\
+            and deps_graph.root.edges[0].dst.binary != "Build":
         test_conanfile_path = None  # disable it
 
     if test_conanfile_path:
diff --git a/conan/cli/commands/editable.py b/conan/cli/commands/editable.py
index 1653845be..23679fc65 100644
--- a/conan/cli/commands/editable.py
+++ b/conan/cli/commands/editable.py
@@ -77,5 +77,6 @@ def editable_list(conan_api, parser, subparser, *args):
     """
     List all the packages in editable mode.
     """
+    parser.parse_args(*args)
     editables = conan_api.local.editable_list()
     return editables
diff --git a/conan/cli/commands/export_pkg.py b/conan/cli/commands/export_pkg.py
index e61c5ce72..aa9d55917 100644
--- a/conan/cli/commands/export_pkg.py
+++ b/conan/cli/commands/export_pkg.py
@@ -59,7 +59,7 @@ def export_pkg(conan_api, parser, *args):
 
     # TODO: Maybe we want to be able to export-pkg it as --build-require
     deps_graph = conan_api.graph.load_graph_consumer(path,
-                                                     ref.name, ref.version, ref.user, ref.channel,
+                                                     ref.name, str(ref.version), ref.user, ref.channel,
                                                      profile_host=profile_host,
                                                      profile_build=profile_build,
                                                      lockfile=lockfile, remotes=remotes, update=None,
diff --git a/conan/cli/commands/profile.py b/conan/cli/commands/profile.py
index 68c32b943..236ef3f4f 100644
--- a/conan/cli/commands/profile.py
+++ b/conan/cli/commands/profile.py
@@ -103,6 +103,7 @@ def profile_list(conan_api, parser, subparser, *args):  # noqa
     """
     List all profiles in the cache.
     """
+    parser.parse_args(*args)
     result = conan_api.profiles.list()
     return result
 
diff --git a/conan/cli/commands/remote.py b/conan/cli/commands/remote.py
index 3cb5ab50b..671ecf75b 100644
--- a/conan/cli/commands/remote.py
+++ b/conan/cli/commands/remote.py
@@ -170,6 +170,7 @@ def remote_list_users(conan_api, parser, subparser, *args):  # noqa
     """
     List the users logged into all the remotes.
     """
+    parser.parse_args(*args)
     remotes = conan_api.remotes.list()
     ret = OrderedDict()
     if not remotes:
diff --git a/conan/cli/commands/report.py b/conan/cli/commands/report.py
new file mode 100644
index 000000000..84b8fc552
--- /dev/null
+++ b/conan/cli/commands/report.py
@@ -0,0 +1,61 @@
+import os
+
+from conan.cli.formatters.report import format_diff_html, format_diff_txt, format_diff_json
+from conan.api.conan_api import ConanAPI
+from conan.cli.command import conan_command, conan_subcommand
+
+
+
+@conan_command(group="Security")
+def report(conan_api: ConanAPI, parser, *args):
+    """
+    Gets information about the recipe and its sources.
+    """
+
+
+@conan_subcommand(formatters={"text": format_diff_txt,
+                              "json": format_diff_json,
+                              "html": format_diff_html})
+def report_diff(conan_api, parser, subparser, *args):
+    """
+    Get the difference between two recipes with their sources.
+    It can be used to compare two different versions of the same recipe, or two different recipe revisions.
+
+    Each old/new recipe can be specified by a path to a conanfile.py and a companion reference,
+    or by a reference only.
+
+    If only a reference is specified, it will be searched in the local cache,
+    or downloaded from the specified remotes. If no revision is specified, the latest revision will be used.
+    """
+
+    ref_help = ("{type} reference, e.g. 'mylib/1.0'. "
+                "If used on its own, it can contain a revision, which will be resolved to the latest one if not provided, "
+                "but it will be ignored if a path is specified. "
+                "If used with a path, it will be used to create the reference for the recipe to be compared.")
+
+    subparser.add_argument("-op", "--old-path", help="Path to the old recipe if comparing a local recipe is desired")
+    subparser.add_argument("-or", "--old-reference", help=ref_help.format(type="Old"), required=True)
+
+    subparser.add_argument("-np", "--new-path", help="Path to the new recipe if comparing a local recipe is desired")
+    subparser.add_argument("-nr", "--new-reference", help=ref_help.format(type="New"), required=True)
+
+    subparser.add_argument("-r", "--remote", action="append", default=None,
+                       help='Look in the specified remote or remotes server')
+
+    args = parser.parse_args(*args)
+
+    cwd = os.getcwd()
+    enabled_remotes = conan_api.remotes.list(args.remote or "*")
+
+    result = conan_api.report.diff(args.old_reference, args.new_reference, enabled_remotes,
+                                   old_path=args.old_path, new_path=args.new_path, cwd=cwd)
+    return {
+        "conan_api": conan_api,
+        "diff": result["diff"],
+        "old_export_ref": result["old_export_ref"],
+        "new_export_ref": result["new_export_ref"],
+        "old_cache_path": result["old_cache_path"],
+        "new_cache_path": result["new_cache_path"],
+        "src_prefix": result["src_prefix"],
+        "dst_prefix": result["dst_prefix"],
+    }
diff --git a/conan/cli/commands/version.py b/conan/cli/commands/version.py
index b2509afef..f7b8ee7ee 100644
--- a/conan/cli/commands/version.py
+++ b/conan/cli/commands/version.py
@@ -11,6 +11,7 @@ def version(conan_api, parser, *args):
     """
     Give information about the Conan client version.
     """
+    parser.parse_args(*args)
 
     return {
             'version': str(conan_version),
diff --git a/conan/cli/commands/workspace.py b/conan/cli/commands/workspace.py
index f1ea664e1..9704ad2df 100644
--- a/conan/cli/commands/workspace.py
+++ b/conan/cli/commands/workspace.py
@@ -18,6 +18,7 @@ def workspace_root(conan_api: ConanAPI, parser, subparser, *args):  # noqa
     """
     Return the folder containing the conanws.py/conanws.yml workspace file
     """
+    parser.parse_args(*args)
     ws = conan_api.workspace
     if not ws.folder():
         raise ConanException("No workspace defined, conanws.py file not found")
@@ -101,6 +102,7 @@ def workspace_info(conan_api: ConanAPI, parser, subparser, *args):  # noqa
     """
     Display info for current workspace
     """
+    parser.parse_args(*args)
     return {"info": conan_api.workspace.info()}
 
 
diff --git a/conan/cli/formatters/report/__init__.py b/conan/cli/formatters/report/__init__.py
new file mode 100644
index 000000000..a9d2dd675
--- /dev/null
+++ b/conan/cli/formatters/report/__init__.py
@@ -0,0 +1,1 @@
+from .diff import format_diff_html, format_diff_txt, format_diff_json
diff --git a/conan/cli/formatters/report/diff.py b/conan/cli/formatters/report/diff.py
new file mode 100644
index 000000000..b64259d7a
--- /dev/null
+++ b/conan/cli/formatters/report/diff.py
@@ -0,0 +1,95 @@
+import json
+import os
+import base64
+
+from jinja2 import Template
+
+from conan.api.output import cli_out_write
+from conan.cli.formatters.report.diff_html import diff_html
+
+def _generate_json(result):
+    diff_text = result["diff"]
+    src_prefix = result["src_prefix"]
+    dst_prefix = result["dst_prefix"]
+    ret = {}
+    current_filename = None
+    for line in diff_text.splitlines():
+        if line.startswith("diff --git "):
+            src_filename, dst_filename = _get_filenames(line, src_prefix, dst_prefix)
+            current_filename = src_filename
+            ret[current_filename] = [line]
+        else:
+            ret[current_filename].append(line)
+    return ret
+
+def _get_filenames(line, src_prefix, dst_prefix):
+    """
+    Extracts the source and destination filenames from a diff line.
+    """
+    src_index = line.find(src_prefix)
+    dst_index = line.find(dst_prefix)
+
+    if src_index == -1 or dst_index == -1:
+        return None, None
+
+    src_filename = line[src_index + len(src_prefix) - 1:dst_index - 1].strip()
+    dst_filename = line[dst_index + len(dst_prefix) - 1:].strip()
+
+    return src_filename, dst_filename
+
+def _render_diff(content, template, template_folder, **kwargs):
+    from conan import __version__
+    template = Template(template, autoescape=True)
+    def _safe_filename(filename):
+        # Calculate base64 of the filename
+        return base64.b64encode(filename.encode(), altchars=b'-_').decode()
+
+    def _get_diff_filename(line):
+        return _get_filenames(line, kwargs["src_prefix"], kwargs["dst_prefix"])[0]
+
+    def _remove_prefixes(line):
+        return line.replace(kwargs["src_prefix"][:-1], "").replace(kwargs["dst_prefix"][:-1], "")
+
+    def _replace_cache_paths(line):
+        return line.replace(kwargs["old_cache_path"], "(old)").replace(kwargs["new_cache_path"], "(new)")
+
+    def _replace_paths(line):
+        return _remove_prefixes(_replace_cache_paths(line))
+
+    return template.render(content=content,
+                           base_template_path=template_folder, version=__version__,
+                           safe_filename=_safe_filename,
+                           replace_paths=_replace_paths,
+                           replace_cache_paths=_replace_cache_paths,
+                           remove_prefixes=_remove_prefixes,
+                           get_diff_filename=_get_diff_filename,
+                           **kwargs)
+
+def format_diff_html(result):
+    conan_api = result["conan_api"]
+
+    template_folder = os.path.join(conan_api.cache_folder, "templates")
+    user_template = os.path.join(template_folder, "diff.html")
+    template = diff_html
+    if os.path.isfile(user_template):
+        with open(user_template, 'r', encoding="utf-8", newline="") as handle:
+            template = handle.read()
+
+    content = _generate_json(result)
+
+    cli_out_write(_render_diff(content, template, template_folder,
+                               old_reference=result["old_export_ref"],
+                               new_reference=result["new_export_ref"],
+                               old_cache_path=result["old_cache_path"],
+                               new_cache_path=result["new_cache_path"],
+                               src_prefix=result["src_prefix"],
+                               dst_prefix=result["dst_prefix"]))
+
+
+def format_diff_txt(result):
+    diff_text = result["diff"]
+    cli_out_write(diff_text)
+
+
+def format_diff_json(result):
+    cli_out_write(json.dumps(_generate_json(result), indent=2))
diff --git a/conan/cli/formatters/report/diff_html.py b/conan/cli/formatters/report/diff_html.py
new file mode 100644
index 000000000..1e0c3f173
--- /dev/null
+++ b/conan/cli/formatters/report/diff_html.py
@@ -0,0 +1,138 @@
+diff_html = r"""
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <title>{{ old_reference }} - {{ new_reference }}</title>
+        <style>
+            body { font-family: monospace; margin: 0px; }
+            .container { display: flex; height: 100%; }
+            .sidebar {
+                min-width: 20%;
+                max-width: 20%;
+                padding: 10px;
+                overflow-y: scroll;
+                background: #f4f4f4;
+                border-right: 1px solid #ccc;
+            }
+            .sidebar li { line-height: 1.5; }
+            .content {
+                padding: 20px;
+                background: #fff;
+                overflow-y: scroll;
+            }
+            .content span {
+                white-space: pre-wrap;
+            }
+            .add { background-color: #76ffbb; }
+            .del { background-color: #fdb9c1; }
+            .context, .diff-content { background-color: #f8f8f8; }
+            .filename { background-color: #f0f0f0; }
+            a:visited {
+                color: blue;
+            }
+        </style>
+        <script>
+            let includeSearchQuery = "";
+            let excludeSearchQuery = "";
+
+            async function onExcludeSearchInput(event) {
+                excludeSearchQuery = event.currentTarget.value.toLowerCase();
+                onSearchInput(event);
+            }
+
+            async function onIncludeSearchInput(event) {
+                includeSearchQuery = event.currentTarget.value.toLowerCase();
+                onSearchInput(event);
+            }
+
+            async function onSearchInput(event) {
+                const sidebar = document.querySelectorAll(".sidebar li");
+                const content = document.querySelectorAll(".content .diff-content");
+
+                sidebar.forEach(async function(item) {
+                    const text = item.textContent.toLowerCase();
+                    const shouldInclude = includeSearchQuery === "" || text.includes(includeSearchQuery);
+                    const shouldExclude = excludeSearchQuery !== "" && text.includes(excludeSearchQuery);
+
+                    if (shouldInclude) {
+                        if (shouldExclude) {
+                            item.style.display = "none";
+                        } else {
+                            item.style.display = "list-item";
+                        }
+                    } else {
+                        item.style.display = "none";
+                    }
+
+                });
+
+                content.forEach(async function(item) {
+                    const filename = document.getElementById(item.id + "_filename");
+                    const text = filename.dataset.replacedPaths.toLowerCase();
+                    const shouldInclude = includeSearchQuery === "" || text.includes(includeSearchQuery);
+                    const shouldExclude = excludeSearchQuery !== "" && text.includes(excludeSearchQuery);
+
+                    if (shouldInclude) {
+                        if (shouldExclude) {
+                            item.style.display = "none";
+                        } else {
+                            item.style.display = "block";
+                        }
+                    } else {
+                        item.style.display = "none";
+                    }
+                });
+            }
+        </script>
+    </head>
+    <body>
+        <div class='container'>
+            <div class='sidebar'>
+                <div style="white-space: nowrap;">
+                    <span class="del">--- (old): <b>{{ old_reference.repr_notime() }}</b></span>
+                    <br/>
+                    <span class="add">+++ (new): <b>{{ new_reference.repr_notime() }}</b></span>
+                </div>
+                <h2>File list:</h2>
+                <input type="text" id="search-include" placeholder="Include search..." oninput="onIncludeSearchInput(event)" />
+                <input type="text" id="search-exclude" placeholder="Exclude search..." oninput="onExcludeSearchInput(event)" />
+                <ul>
+                    {%- for filename in content.keys() %}
+                        <li><a href="#diff_{{- safe_filename(filename) -}}" class="side-link">{{ replace_cache_paths(filename) }}</a></li>
+                    {%- endfor %}
+                </ul>
+            </div>
+            <div class='content'>
+                <div><!--placeholder-->
+                {%- for filename, lines in content.items() -%}
+                    </div>
+                    <div id="diff_{{ safe_filename(filename) }}" class="diff-content">
+                    {%- for line in lines -%}
+                        {%- if loop.first -%}
+                            <hr/>
+                            <h3 id="diff_{{ safe_filename(filename) }}_filename" class="filename" data-replaced-paths="{{ replace_cache_paths(filename) }}">{{ remove_prefixes(line) }}</h3>
+                        {%- elif line.startswith('+++') %}
+                            <span class="add">{{ replace_paths(line) }}</span>
+                            <br/>
+                        {%- elif line.startswith('---') %}
+                            <span class="del">{{ replace_paths(line) }}</span>
+                            <br/>
+                        {%- elif line.startswith('+') %}
+                            <span class="add">{{ line }}</span>
+                            <br/>
+                        {%- elif line.startswith('-') %}
+                            <span class="del">{{ line }}</span>
+                            <br/>
+                        {%- else %}
+                            <span class="context">{{ line }}</span>
+                            <br/>
+                        {%- endif %}
+                    {%- endfor -%}
+                {%- endfor -%}
+                <hr/>
+                </div>
+            </div>
+        </div>
+    </body>
+</html>
+"""
diff --git a/conan/internal/default_settings.py b/conan/internal/default_settings.py
index e9c974c77..68c19ba0b 100644
--- a/conan/internal/default_settings.py
+++ b/conan/internal/default_settings.py
@@ -91,7 +91,7 @@ os:
 arch: [x86, x86_64, ppc32be, ppc32, ppc64le, ppc64,
        armv4, armv4i, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, arm64ec,
        sparc, sparcv9,
-       mips, mips64, avr, s390, s390x, asm.js, wasm, sh4le,
+       mips, mips64, avr, s390, s390x, asm.js, wasm, wasm64, sh4le,
        e2k-v2, e2k-v3, e2k-v4, e2k-v5, e2k-v6, e2k-v7,
        riscv64, riscv32,
        xtensalx6, xtensalx106, xtensalx7,
@@ -163,6 +163,13 @@ compiler:
         version: ["1.19", "1.20", "1.21", "1.22", "1.23", "1.24", "1.25"]
         libcxx: [libstdc++, libstdc++11]
         cppstd: [null, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20, 23, gnu23]
+    emcc:
+        # From https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md
+        # There is no ABI compatibility guarantee between versions
+        version: [ANY]
+        libcxx: [null, libstdc++, libstdc++11, libc++]
+        cppstd: [null, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20, 23, gnu23, 26, gnu26]
+        cstd: [null, 99, gnu99, 11, gnu11, 17, gnu17, 23, gnu23]
 
 build_type: [null, Debug, Release, RelWithDebInfo, MinSizeRel]
 """
diff --git a/conan/internal/graph/graph.py b/conan/internal/graph/graph.py
index 8e4dc2e29..a8d9369cb 100644
--- a/conan/internal/graph/graph.py
+++ b/conan/internal/graph/graph.py
@@ -1,5 +1,6 @@
 from collections import OrderedDict
 
+from conan.api.output import ConanOutput
 from conan.internal.graph.graph_error import GraphError
 from conan.api.model import PkgReference
 from conan.api.model import RecipeReference
@@ -61,15 +62,22 @@ class Node:
 
         # real graph model
         self.transitive_deps = OrderedDict()  # of _TransitiveRequirement
-        self.dependencies = []  # Ordered Edges
+        self.edges = []  # Ordered Edges
         self.dependants = []  # Edges
         self.error = None
         self.should_build = False  # If the --build or policy wants to build this binary
         self.build_allowed = False
         self.is_conf = False
-        self.replaced_requires = {}  # To track the replaced requires for self.dependencies[old-ref]
+        self.replaced_requires = {}  # To track the replaced requires for self.edges[old-ref]
         self.skipped_build_requires = False
 
+    @property
+    def dependencies(self):
+        ConanOutput().warning("Node.dependencies is private and shouldn't be used. It is now "
+                              "node.edges. Please fix your code, Node.dependencies will be removed "
+                              "in future versions", warn_tag="deprecated")
+        return self.edges
+
     def subgraph(self):
         nodes = [self]
         opened = [self]
@@ -233,12 +241,12 @@ class Node:
 
     def add_edge(self, edge):
         if edge.src == self:
-            self.dependencies.append(edge)
+            self.edges.append(edge)
         else:
             self.dependants.append(edge)
 
     def neighbors(self):
-        return [edge.dst for edge in self.dependencies]
+        return [edge.dst for edge in self.edges]
 
     def inverse_neighbors(self):
         return [edge.src for edge in self.dependants]
diff --git a/conan/internal/graph/graph_binaries.py b/conan/internal/graph/graph_binaries.py
index 37ef04173..386f69476 100644
--- a/conan/internal/graph/graph_binaries.py
+++ b/conan/internal/graph/graph_binaries.py
@@ -48,8 +48,8 @@ class GraphBinariesAnalyzer:
         with_deps_to_build = False
         # check dependencies, if they are being built, "cascade" will try to build this one too
         if build_mode.cascade:
-            with_deps_to_build = any(dep.dst.binary in (BINARY_BUILD, BINARY_EDITABLE_BUILD)
-                                     for dep in node.dependencies)
+            with_deps_to_build = any(edge.dst.binary in (BINARY_BUILD, BINARY_EDITABLE_BUILD)
+                                     for edge in node.edges)
         if build_mode.forced(conanfile, ref, with_deps_to_build):
             node.should_build = True
             conanfile.output.info('Forced build from source')
diff --git a/conan/internal/graph/install_graph.py b/conan/internal/graph/install_graph.py
index 7d7d9aec7..4062bee83 100644
--- a/conan/internal/graph/install_graph.py
+++ b/conan/internal/graph/install_graph.py
@@ -171,7 +171,7 @@ class _InstallRecipeReference:
             install_pkg_ref = _InstallPackageReference.create(node)
             self.packages[node.package_id] = install_pkg_ref
 
-        for dep in node.dependencies:
+        for dep in node.edges:
             if dep.dst.binary != BINARY_SKIP:
                 if dep.dst.ref == node.ref:  # If the node is itself, then it is internal dep
                     install_pkg_ref.depends.append(dep.dst.pref.package_id)
@@ -270,7 +270,7 @@ class _InstallConfiguration:
         result.info = node.conanfile.info.serialize()
 
         result.nodes.append(node)
-        for dep in node.dependencies:
+        for dep in node.edges:
             if dep.dst.binary != BINARY_SKIP:
                 if dep.dst.pref not in result.depends:
                     result.depends.append(dep.dst.pref)
@@ -284,10 +284,10 @@ class _InstallConfiguration:
         # assert self.context == node.context
         self.nodes.append(node)
 
-        for dep in node.dependencies:
-            if dep.dst.binary != BINARY_SKIP:
-                if dep.dst.pref not in self.depends:
-                    self.depends.append(dep.dst.pref)
+        for edge in node.edges:
+            if edge.dst.binary != BINARY_SKIP:
+                if edge.dst.pref not in self.depends:
+                    self.depends.append(edge.dst.pref)
 
     def _build_args(self):
         if self.binary not in (BINARY_BUILD, BINARY_EDITABLE_BUILD):
diff --git a/conan/internal/graph/installer.py b/conan/internal/graph/installer.py
index 7eeb51099..fa09f90ba 100644
--- a/conan/internal/graph/installer.py
+++ b/conan/internal/graph/installer.py
@@ -355,12 +355,12 @@ class BinaryInstaller:
         base_path = os.path.dirname(conanfile_path)
 
         conanfile.folders.set_base_folders(base_path, output_folder)
-        output = conanfile.output
-        output.info("Rewriting files of editable package "
-                    "'{}' at '{}'".format(conanfile.name, conanfile.generators_folder))
-        write_generators(conanfile, self._hook_manager, self._home_folder)
 
         if node.binary == BINARY_EDITABLE_BUILD:
+            output = conanfile.output
+            output.info("Rewriting files of editable package "
+                        "'{}' at '{}'".format(conanfile.name, conanfile.generators_folder))
+            write_generators(conanfile, self._hook_manager, self._home_folder)
             run_build_method(conanfile, self._hook_manager)
 
         rooted_base_path = base_path if conanfile.folders.root is None else \
diff --git a/conan/internal/rest/remote_manager.py b/conan/internal/rest/remote_manager.py
index b5bfb0e2d..98ca71c68 100644
--- a/conan/internal/rest/remote_manager.py
+++ b/conan/internal/rest/remote_manager.py
@@ -277,9 +277,9 @@ class RemoteManager:
         if remote.disabled and enforce_disabled:
             raise ConanException("Remote '%s' is disabled" % remote.name)
         local_folder_remote = self._local_folder_remote(remote)
-        if local_folder_remote is not None:
-            return local_folder_remote.call_method(method, *args, **kwargs)
         try:
+            if local_folder_remote is not None:
+                return local_folder_remote.call_method(method, *args, **kwargs)
             return self._auth_manager.call_rest_api_method(remote, method, *args, **kwargs)
         except ConnectionError as exc:
             raise ConanConnectionError(("%s\n\nUnable to connect to remote %s=%s\n"
diff --git a/conan/internal/rest/rest_client_local_recipe_index.py b/conan/internal/rest/rest_client_local_recipe_index.py
index 179b38b9b..bde8e2ac4 100644
--- a/conan/internal/rest/rest_client_local_recipe_index.py
+++ b/conan/internal/rest/rest_client_local_recipe_index.py
@@ -199,6 +199,9 @@ class _LocalRecipesIndexLayout:
     def get_recipes_references(self, pattern):
         name_pattern = pattern.split("/", 1)[0]
         recipes_dir = os.path.join(self._base_folder, "recipes")
+        if not os.path.isdir(recipes_dir):
+            raise ConnectionError("Cannot connect to 'local-recipes-index' repository, missing "
+                                  f"'recipes' folder: {recipes_dir}")
         recipes = os.listdir(recipes_dir)
         recipes.sort()
         ret = []
diff --git a/conan/tools/build/cppstd.py b/conan/tools/build/cppstd.py
index 43c3e9da4..b7c182276 100644
--- a/conan/tools/build/cppstd.py
+++ b/conan/tools/build/cppstd.py
@@ -104,6 +104,7 @@ def supported_cppstd(conanfile, compiler=None, compiler_version=None):
             "clang": _clang_supported_cppstd,
             "mcst-lcc": _mcst_lcc_supported_cppstd,
             "qcc": _qcc_supported_cppstd,
+            "emcc": _emcc_supported_cppstd,
             }.get(compiler)
     if func:
         return func(Version(compiler_version))
@@ -267,3 +268,16 @@ def _qcc_supported_cppstd(version):
         return ["98", "gnu98"]
     else:
         return ["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17"]
+
+def _emcc_supported_cppstd(version):
+    """
+    emcc is based on clang but follow different versioning scheme.
+    """
+    if version <= "3.0.1":
+        return _clang_supported_cppstd(Version("14"))
+    if version <= "3.1.50":
+        return _clang_supported_cppstd(Version("18"))
+    if version <= "4.0.1":
+        return _clang_supported_cppstd(Version("20"))
+    # Since emcc 4.0.2 clang version is 21
+    return _clang_supported_cppstd(Version("21"))
diff --git a/conan/tools/build/cstd.py b/conan/tools/build/cstd.py
index 2a762d775..59e3f1145 100644
--- a/conan/tools/build/cstd.py
+++ b/conan/tools/build/cstd.py
@@ -110,6 +110,7 @@ def supported_cstd(conanfile, compiler=None, compiler_version=None):
             "gcc": _gcc_supported_cstd,
             "msvc": _msvc_supported_cstd,
             "clang": _clang_supported_cstd,
+            "emcc": _emcc_supported_cstd,
             }.get(compiler)
     if func:
         return func(Version(compiler_version))
@@ -190,3 +191,18 @@ def _clang_supported_cstd(version):
     if version < "18":
         return ["99", "gnu99", "11", "gnu11", "17", "gnu17"]
     return ["99", "gnu99", "11", "gnu11", "17", "gnu17", "23", "gnu23"]
+
+def _emcc_supported_cstd(version):
+    """
+    emcc is based on clang but follow different versioning scheme.
+    """
+    if version <= "3.0.1":
+        return _clang_supported_cstd(Version("14"))
+    if version <= "3.1.50":
+        return _clang_supported_cstd(Version("18"))
+    if version <= "4.0.1":
+        return _clang_supported_cstd(Version("20"))
+    # Since emcc 4.0.2 clang version is 21
+    return _clang_supported_cstd(Version("21"))
+
+
diff --git a/conan/tools/build/flags.py b/conan/tools/build/flags.py
index 98d068252..441aca0af 100644
--- a/conan/tools/build/flags.py
+++ b/conan/tools/build/flags.py
@@ -66,6 +66,13 @@ def architecture_flag(conanfile):
                 "e2k-v5": "-march=elbrus-v5",
                 "e2k-v6": "-march=elbrus-v6",
                 "e2k-v7": "-march=elbrus-v7"}.get(arch, "")
+    elif compiler == "emcc":
+        # Emscripten default output is WASM since 1.37.x (long time ago)
+        if arch == "wasm64":
+            return "-sMEMORY64=1"
+        # Deactivate WASM output forcing asm.js output instead
+        elif arch == "asm.js":
+            return "-sWASM=0"
     return ""
 
 
@@ -78,7 +85,7 @@ def libcxx_flags(conanfile):
     if compiler == "apple-clang":
         # In apple-clang 2 only values atm are "libc++" and "libstdc++"
         lib = f'-stdlib={libcxx}'
-    elif compiler == "clang" or compiler == "intel-cc":
+    elif compiler in ("clang", "intel-cc", "emcc"):
         if libcxx == "libc++":
             lib = "-stdlib=libc++"
         elif libcxx == "libstdc++" or libcxx == "libstdc++11":
@@ -93,7 +100,7 @@ def libcxx_flags(conanfile):
     elif compiler == "qcc":
         lib = f'-Y _{libcxx}'
 
-    if compiler in ['clang', 'apple-clang', 'gcc']:
+    if compiler in ['clang', 'apple-clang', 'gcc', 'emcc']:
         if libcxx == "libstdc++":
             stdlib11 = "_GLIBCXX_USE_CXX11_ABI=0"
         elif libcxx == "libstdc++11" and conanfile.conf.get("tools.gnu:define_libcxx11_abi",
@@ -134,14 +141,15 @@ def build_type_flags(conanfile):
     if not compiler or not build_type:
         return []
 
-    # https://github.com/Kitware/CMake/blob/d7af8a34b67026feaee558433db3a835d6007e06/
-    # Modules/Platform/Windows-MSVC.cmake
     comp_exes = conanfile.conf.get("tools.build:compiler_executables", check_type=dict,
                                    default={})
     clangcl = "clang-cl" in (comp_exes.get("c") or comp_exes.get("cpp", ""))
 
     if compiler == "msvc" or clangcl:
-        if clangcl or (vs_toolset and "clang" in vs_toolset):
+        # https://github.com/Kitware/CMake/blob/d7af8a34b67026feaee558433db3a835d6007e06/
+        # Modules/Platform/Windows-MSVC.cmake
+        # FIXME: This condition seems legacy, as no more "clang" exists in Conan toolsets
+        if vs_toolset and "clang" in vs_toolset:
             flags = {"Debug": ["-gline-tables-only", "-fno-inline", "-O0"],
                      "Release": ["-O2"],
                      "RelWithDebInfo": ["-gline-tables-only", "-O2", "-fno-inline"],
diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py
index 0d35f80f8..6ebe55a02 100644
--- a/conan/tools/cmake/presets.py
+++ b/conan/tools/cmake/presets.py
@@ -5,7 +5,7 @@ import textwrap
 
 from conan.api.output import ConanOutput, Color
 from conan.tools.cmake.layout import get_build_folder_custom_vars
-from conan.tools.cmake.toolchain.blocks import GenericSystemBlock, CompilersBlock
+from conan.tools.cmake.toolchain.blocks import GenericSystemBlock
 from conan.tools.cmake.utils import is_multi_configuration
 from conan.tools.build import build_jobs
 from conan.tools.microsoft import is_msvc
@@ -307,6 +307,9 @@ class _IncludingPresets:
         if not absolute_paths:
             try:  # Make it relative to the CMakeUserPresets.json if possible
                 preset_path = os.path.relpath(preset_path, output_dir)
+                # If we don't normalize, path will be removed in Linux shared folders
+                # https://github.com/conan-io/conan/issues/18434
+                preset_path = preset_path.replace("\\", "/")
             except ValueError:
                 pass
         data = _IncludingPresets._append_user_preset_path(data, preset_path, output_dir)
diff --git a/conan/tools/gnu/autotoolstoolchain.py b/conan/tools/gnu/autotoolstoolchain.py
index 785f3b05c..f2e454474 100644
--- a/conan/tools/gnu/autotoolstoolchain.py
+++ b/conan/tools/gnu/autotoolstoolchain.py
@@ -174,7 +174,9 @@ class AutotoolsToolchain:
             if self._conanfile.settings.compiler.runtime == "dynamic":
                 runtime_type = self._conanfile.settings.get_safe("compiler.runtime_type")
                 library = "msvcrtd" if runtime_type == "Debug" else "msvcrt"
-                return f"-D_DLL -D_MT -Xclang --dependent-lib={library}"
+                # The -D_DEBUG is important to link with the Debug MSVCP140D.dll
+                debug = "-D_DEBUG " if runtime_type == "Debug" else ""
+                return f"{debug}-D_DLL -D_MT -Xclang --dependent-lib={library}"
             return ""  # By default it already link statically
 
         flag = msvc_runtime_flag(self._conanfile)
diff --git a/conan/tools/gnu/get_gnu_triplet.py b/conan/tools/gnu/get_gnu_triplet.py
index dc7e3b68d..9b5eb7a86 100644
--- a/conan/tools/gnu/get_gnu_triplet.py
+++ b/conan/tools/gnu/get_gnu_triplet.py
@@ -10,6 +10,7 @@ def _get_gnu_arch(os_, arch):
                "armv8.3": "aarch64",
                "asm.js": "asmjs",
                "wasm": "wasm32",
+               "wasm64": "wasm64",
                }.get(arch, None)
 
     if not machine:
diff --git a/conan/tools/gnu/gnutoolchain.py b/conan/tools/gnu/gnutoolchain.py
index e33382831..800cb35b4 100644
--- a/conan/tools/gnu/gnutoolchain.py
+++ b/conan/tools/gnu/gnutoolchain.py
@@ -239,7 +239,8 @@ class GnuToolchain:
             if self._conanfile.settings.compiler.runtime == "dynamic":
                 runtime_type = self._conanfile.settings.get_safe("compiler.runtime_type")
                 library = "msvcrtd" if runtime_type == "Debug" else "msvcrt"
-                return f"-D_DLL -D_MT -Xclang --dependent-lib={library}"
+                debug = "-D_DEBUG " if runtime_type == "Debug" else ""
+                return f"{debug}-D_DLL -D_MT -Xclang --dependent-lib={library}"
             return ""  # By default it already link statically
 
         flag = msvc_runtime_flag(self._conanfile)
diff --git a/conan/tools/meson/helpers.py b/conan/tools/meson/helpers.py
index c3e40a806..fcf49fd40 100644
--- a/conan/tools/meson/helpers.py
+++ b/conan/tools/meson/helpers.py
@@ -49,6 +49,7 @@ _meson_cpu_family_map = {
     'sparc': ('sparc', 'sparc', 'big'),
     'sparcv9': ('sparc64', 'sparc64', 'big'),
     'wasm': ('wasm32', 'wasm32', 'little'),
+    'wasm64': ('wasm64', 'wasm64', 'little'),
     'x86': ('x86', 'x86', 'little'),
     'x86_64': ('x86_64', 'x86_64', 'little'),
     'riscv32': ('riscv32', 'riscv32', 'little'),
diff --git a/conan/tools/qbs/common.py b/conan/tools/qbs/common.py
index dd45f434e..bf6486704 100644
--- a/conan/tools/qbs/common.py
+++ b/conan/tools/qbs/common.py
@@ -26,6 +26,7 @@ architecture_map = {
     's390x': 's390x',
     'asm.js': None,
     'wasm': None,
+    'wasm64': None,
     'sh4le': 'sh'
 }
 
@@ -84,4 +85,4 @@ runtime_library_map = {
     'MT': 'static',
     'MDd': 'dynamic',
     'MTd': 'static',
-}
\ No newline at end of file
+}
diff --git a/conan/tools/sbom/cyclonedx.py b/conan/tools/sbom/cyclonedx.py
index e15585601..19b1993e0 100644
--- a/conan/tools/sbom/cyclonedx.py
+++ b/conan/tools/sbom/cyclonedx.py
@@ -41,11 +41,11 @@ def cyclonedx_1_4(conanfile, name=None, add_build=False, add_tests=False, **kwar
     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.dependencies]}
+                              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}"}
-        dep = [d for d in c.dependencies if (d.dst.context == "host" or add_build) and (not d.dst.test or add_tests)]
+        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]
         if depends_on:
@@ -132,11 +132,11 @@ def cyclonedx_1_6(conanfile, name=None, add_build=False, add_tests=False, **kwar
     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.dependencies]}
+                              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}"}
-        dep = [d for d in c.dependencies if (d.dst.context == "host" or add_build) and (not d.dst.test or add_tests)]
+        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]
         if depends_on:
diff --git a/conan/tools/scm/git.py b/conan/tools/scm/git.py
index 48ad9fe5d..190711e18 100644
--- a/conan/tools/scm/git.py
+++ b/conan/tools/scm/git.py
@@ -88,6 +88,9 @@ class Git:
             name, url = r.split(maxsplit=1)
             if name == remote:
                 url, _ = url.rsplit(None, 1)
+                # if the url still has (fetch) or (push) at the end
+                if url.endswith("(fetch)") or url.endswith("(push)"):
+                    url, _ = url.rsplit(None, 1)
                 if os.path.exists(url):  # Windows local directory
                     url = url.replace("\\", "/")
                 return url


Test patch:
diff --git a/.ci/docker/conan-tests b/.ci/docker/conan-tests
index ef26a5530..5f7ee6f29 100644
--- a/.ci/docker/conan-tests
+++ b/.ci/docker/conan-tests
@@ -25,7 +25,8 @@ ENV PY36=3.6.15 \
     CLANGXX_14=/usr/bin/clang++-14 \
     BAZEL_6=6.5.0 \
     BAZEL_7=7.4.1 \
-    BAZEL_8=8.0.0
+    BAZEL_8=8.0.0 \
+    EMSDK=4.0.10
 
 RUN apt-get update && \
     apt-get install -y --no-install-recommends \
@@ -169,6 +170,21 @@ RUN cd /tmp && \
     mv qbs-linux-x86_64-2.6.0 /usr/share/qbs && \
     rm qbs-linux-x86_64-2.6.0.tar.gz
 
+RUN cd /tmp && \
+    wget https://github.com/emscripten-core/emsdk/archive/refs/tags/${EMSDK}.tar.gz && \
+    tar xzf ${EMSDK}.tar.gz --directory /usr/share && \
+    cd /usr/share/emsdk-${EMSDK} && \
+    ./emsdk update && \
+    ./emsdk install latest && \
+    ./emsdk activate latest --permanent && \
+    . /usr/share/emsdk-${EMSDK}/emsdk_env.sh && \
+    embuilder build MINIMAL && \
+    embuilder build MINIMAL --wasm64
+    # echo "EMSDK_QUIET=1 . /usr/share/emsdk-${EMSDK}/emsdk_env.sh" >> /etc/bash.bashrc
+# Manually add the emsdk binaries to the PATH and set emcc cache directory to a writable location
+ENV PATH="/usr/share/emsdk-$EMSDK:/usr/share/emsdk-$EMSDK/upstream/emscripten:/usr/share/emsdk-$EMSDK/node/22.16.0_64bit/bin:$PATH" \
+    EM_CACHE=$HOME/.emscripten_cache
+
 RUN echo "export QT_NO_GLIB=1" >> /etc/profile.d/qt.sh
 
 USER conan
diff --git a/.github/actions/test-coverage/action.yml b/.github/actions/test-coverage/action.yml
index 70f4e93ac..86127f8d0 100644
--- a/.github/actions/test-coverage/action.yml
+++ b/.github/actions/test-coverage/action.yml
@@ -30,7 +30,7 @@ runs:
         if: ${{ inputs.tests }}
         shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash' }}
         run: |
-          pytest ${{ inputs.tests }} -rf --durations=${{ inputs.duration }} -n ${{ inputs.workers }} ${{ github.ref == 'refs/heads/develop2' && '--cov=conan --cov=conans --cov=test --cov-report=' || '' }}
+          pytest ${{ inputs.tests }} --durations=${{ inputs.duration }} -n ${{ inputs.workers }} ${{ github.ref == 'refs/heads/develop2' && '--cov=conan --cov=conans --cov=test --cov-report=' || '' }}
 
       - name: Rename coverage file
         if: github.ref == 'refs/heads/develop2'
diff --git a/.github/workflows/osx-tests.yml b/.github/workflows/osx-tests.yml
index c02a16596..f6196ecd2 100644
--- a/.github/workflows/osx-tests.yml
+++ b/.github/workflows/osx-tests.yml
@@ -47,7 +47,7 @@ jobs:
 
       - name: Install homebrew dependencies
         run: |
-          brew install xcodegen make libtool zlib autoconf automake ninja
+          brew install xcodegen make libtool zlib autoconf automake ninja emscripten
 
       - name: Cache CMake and Bazel installations
         id: cache-tools
@@ -168,7 +168,7 @@ jobs:
 
       - name: Install homebrew dependencies
         run: |
-          brew install xcodegen make libtool zlib autoconf automake ninja
+          brew install xcodegen make libtool zlib autoconf automake ninja emscripten
           export PATH=${HOME}/Applications/CMake/3.15.7/bin:$PATH:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin
           cmake --version
           bazel --version
diff --git a/test/conftest.py b/test/conftest.py
index a0f6ead5a..a189d4aaa 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -190,6 +190,8 @@ tools_locations = {
             "path": {'Linux': '/usr/share/qbs/bin'}
         }
     },
+    "emcc": {},
+    "node": {},
     # TODO: Intel oneAPI is not installed in CI yet. Uncomment this line whenever it's done.
     # "intel_oneapi": {
     #     "default": "2021.3",
diff --git a/test/functional/command/report_test.py b/test/functional/command/report_test.py
new file mode 100644
index 000000000..fa3b0270c
--- /dev/null
+++ b/test/functional/command/report_test.py
@@ -0,0 +1,107 @@
+import json
+import os
+import textwrap
+
+import pytest
+
+from conan.test.utils.tools import TestClient
+
+
+@pytest.mark.parametrize("old_args", [
+    "-op=v1 -or=pkg/1.0",
+    "-or=pkg/1.0"
+])
+@pytest.mark.parametrize("new_args", [
+    "-np=v2 -nr=pkg/2.0",
+    "-nr=pkg/2.0#{revision}",
+])
+@pytest.mark.parametrize("formatter", [
+    "-f=json --out-file=output.json",
+    "-f=html --out-file=output.html"
+])
+@pytest.mark.tool("git")
+def test_compare_paths(old_args, new_args, formatter):
+    tc = TestClient(light=True, default_server_user=True)
+
+    patch_file = textwrap.dedent("""
+    diff --git a/foo.txt b/foo.txt
+    new file mode 100644
+    index 0000000..e311fbb
+    --- /dev/null
+    +++ b/foo.txt
+    @@ -0,0 +1 @@
+    +{version}
+    """)
+
+    conandata_yml = textwrap.dedent("""
+    patches:
+      "{version}":
+        - patch_file: "patches/patch.patch"
+    """)
+
+    conanfile = textwrap.dedent("""
+    from conan import ConanFile
+    from conan.tools.files import apply_conandata_patches, export_conandata_patches, save
+
+    class TestConan(ConanFile):
+        name = "pkg"
+
+        exports = "{version}.txt"
+
+        def export_sources(self):
+            export_conandata_patches(self)
+
+        def source(self):
+            save(self, "myfile.txt", "{version}")
+            if self.version == "2.0":
+                save(self, "new-file-for-v2.txt", "a new file for the new version")
+            apply_conandata_patches(self)
+    """)
+
+    tc.save({
+        "v1/conanfile.py": conanfile.format(version="v1"),
+        "v1/conandata.yml": conandata_yml.format(version="1.0"),
+        "v1/v1.txt": "1.0",
+        "v1/patches/patch.patch": patch_file.format(version="1.0"),
+
+        "v2/conanfile.py": conanfile.format(version="v2"),
+        "v2/conandata.yml": conandata_yml.format(version="2.0"),
+        "v2/v2.txt": "2.0",
+        "v2/patches/patch.patch": patch_file.format(version="2.0"),
+    })
+
+    tc.run("create v1 --version=1.0")
+    v1_layout = tc.exported_layout()
+    v1_path = v1_layout.export()
+
+    tc.run("create v2 --version=2.0")
+    v2_revision = tc.exported_layout().reference.revision
+
+    tc.run("upload * -r=default -c")
+    tc.run("remove * -c")
+
+    new_args = new_args.format(revision=v2_revision)
+
+    tc.run(f'report diff {old_args} {new_args} {formatter}')
+
+    if "json" in formatter:
+        output_json = json.loads(tc.load("output.json"))
+        assert any(os.path.join(v1_path, "conanfile.py").replace("\\", "/") in k for k in output_json.keys())
+        assert any(os.path.join(v1_path, "conanmanifest.txt").replace("\\", "/") in k for k in output_json.keys())
+        # We have patch information
+        assert any(os.path.abspath(os.path.join(v1_path, "..", "es", "patches", "patch.patch")).replace("\\", "/") in k for k in output_json.keys())
+        # '/private/var/folders/lw/6bflvp3s3t5b56n2p_bj_vx80000gn/T/tmptcto18wfconans/path with spaces/.conan2/p/pkg8fd97e4b9cf55/es/patches/patch.patch'
+        # We have exports information
+        assert any(os.path.abspath(os.path.join(v1_path, "..", "e", "v1.txt")).replace("\\", "/") in k for k in output_json.keys())
+        # New files
+        assert any("new-file-for-v2.txt" in k for k in output_json.keys())
+
+    elif "html" in formatter:
+        output_html = tc.load("output.html")
+        # We have patch information
+        assert f"""(new)/es/patches/patch.patch""" in output_html
+        # We have exports information
+        assert f"""(old)/e/v1.txt""" in output_html
+        # New files appear both in the menu and in the diff
+        assert output_html.count("(new)/s/new-file-for-v2.txt") == 3
+
diff --git a/test/functional/toolchains/emscripten/test_emcc.py b/test/functional/toolchains/emscripten/test_emcc.py
new file mode 100644
index 000000000..aee84ad07
--- /dev/null
+++ b/test/functional/toolchains/emscripten/test_emcc.py
@@ -0,0 +1,173 @@
+# Test suite to check conan capabilities for cross compiling to web assembly and asmjs
+import textwrap
+import os
+import platform
+from shutil import rmtree
+import pytest
+import sys
+
+from conan.test.utils.tools import TestClient
+
+EMCC_MIN_PYTHON_VERSION = (3, 8)
+
+base_emscripten_profile = textwrap.dedent(
+    """
+    [settings]
+    build_type=Release
+    compiler=emcc
+    compiler.cppstd=17
+    compiler.libcxx=libc++
+    compiler.version=4.0.10
+    os=Emscripten
+
+    [conf]
+    tools.build:exelinkflags=['-sALLOW_MEMORY_GROWTH=1']
+    tools.build:sharedlinkflags=['-sALLOW_MEMORY_GROWTH=1']
+
+    # Define the emcc executable paths
+    tools.build:compiler_executables={'c':'emcc', 'cpp':'em++'}
+
+    # Set Ninja as default generator as it is faster and will sove issues on Windows
+    tools.cmake.cmaketoolchain:generator=Ninja
+    # Verbosity to see emcc invocations
+    tools.compilation:verbosity=verbose
+    # Distinguish between architectures
+    tools.cmake.cmake_layout:build_folder_vars=['settings.build_type', 'settings.arch']
+
+    [buildenv]
+    AR=emar
+    NM=emnm
+    RANLIB=emranlib
+    STRIP=emstrip
+"""
+)
+
+wasm32_profile = textwrap.dedent(
+    """
+    include(base_emscripten_profile)
+    [settings]
+    arch=wasm
+
+    [conf]
+    tools.build:exelinkflags+=['-sMAXIMUM_MEMORY=4GB', '-sINITIAL_MEMORY=64MB']
+    tools.build:sharedlinkflags+=['-sMAXIMUM_MEMORY=4GB', '-sINITIAL_MEMORY=64MB']
+    """
+)
+
+wasm_64_profile = textwrap.dedent(
+    """
+    include(base_emscripten_profile)
+    [settings]
+    arch=wasm64
+    [conf]
+    tools.build:exelinkflags+=['-sMAXIMUM_MEMORY=16GB', '-sINITIAL_MEMORY=16GB']
+    tools.build:sharedlinkflags+=['-sMAXIMUM_MEMORY=16GB', '-sINITIAL_MEMORY=16GB']
+    """
+)
+
+
+asmjs_profile = textwrap.dedent(
+    """
+    include(base_emscripten_profile)
+    [settings]
+    arch=asm.js
+
+    [conf]
+    tools.build:exelinkflags+=['-sMAXIMUM_MEMORY=2GB', '-sINITIAL_MEMORY=64MB']
+    tools.build:sharedlinkflags+=['-sMAXIMUM_MEMORY=2GB', '-sINITIAL_MEMORY=64MB']
+    """
+)
+
+
+@pytest.mark.tool("cmake")
+@pytest.mark.tool("emcc")
+@pytest.mark.tool("node")
+@pytest.mark.skipif(sys.version_info < EMCC_MIN_PYTHON_VERSION, reason = "emcc requires Python 3.8 or higher")
+@pytest.mark.skipif(platform.system() == "Windows", reason = "Emscripten not installed in Windows")
+def test_cmake_emscripten():
+    client = TestClient()
+
+    client.run("new cmake_exe -d name=hello -d version=0.1")
+    client.save({"wasm32": wasm32_profile, "asmjs": asmjs_profile, "base_emscripten_profile": base_emscripten_profile,})
+
+    client.run("build . -pr:h=wasm32")
+    assert "Conan toolchain: Defining libcxx as C++ flags: -stdlib=libc++" in client.out
+    assert os.path.exists(os.path.join(client.current_folder, "build/release-wasm" , "hello.wasm"))
+
+    # Run JavaScript generated code which uses .wasm file
+    client.run_command("node ./build/release-wasm/hello")
+    assert "Hello World Release!" in client.out
+
+    client.run("build . -pr:h=asmjs")
+    assert "WASM=0" in client.out
+    # No wasm should have been generated for asm.js architecture
+    assert not os.path.exists(os.path.join(client.current_folder, "build/release-asm.js" , "hello.wasm"))
+    client.run_command("node ./build/release-asm.js/hello")
+    assert "Hello World Release!" in client.out
+
+
+@pytest.mark.tool("meson")
+@pytest.mark.tool("emcc")
+@pytest.mark.tool("node")
+@pytest.mark.skipif(sys.version_info < EMCC_MIN_PYTHON_VERSION, reason = "emcc requires Python 3.8 or higher")
+@pytest.mark.skipif(platform.system() == "Windows", reason = "Emscripten not installed in Windows")
+def test_meson_emscripten():
+    client = TestClient()
+    client.run("new meson_exe -d name=hello -d version=0.1")
+
+    client.save({"wasm32": wasm32_profile, "wasm64": wasm_64_profile, "asmjs": asmjs_profile, "base_emscripten_profile": base_emscripten_profile,})
+    client.run("build . -pr:h=wasm64")
+    assert "C++ compiler for the host machine: em++" in client.out
+    assert "C++ linker for the host machine: em++ ld.wasm" in client.out
+    assert "Host machine cpu family: wasm64" in client.out
+    assert os.path.exists(os.path.join(client.current_folder, "build", "hello.wasm"))
+    # wasm64 only supported since node v23 so only run in MacOS where it is available
+    if platform.system() == "Darwin":
+        client.run_command("node ./build/hello")
+        assert "Hello World Release!" in client.out
+
+    rmtree(os.path.join(client.current_folder, "build"))
+    client.run("build . -pr:h=asmjs")
+    assert "C++ compiler for the host machine: em++" in client.out
+    assert "C++ linker for the host machine: em++ ld.wasm" in client.out
+    assert "Host machine cpu family: asm.js" in client.out
+    assert "WASM=0" in client.out
+
+    assert not os.path.exists(os.path.join(client.current_folder, "build", "hello.wasm"))
+    client.run_command("node ./build/hello")
+    assert "Hello World Release!" in client.out
+
+
+@pytest.mark.tool("autotools")
+@pytest.mark.tool("emcc")
+@pytest.mark.tool("node")
+@pytest.mark.skipif(sys.version_info < EMCC_MIN_PYTHON_VERSION, reason = "emcc requires Python 3.8 or higher")
+@pytest.mark.skipif(platform.system() == "Windows", reason = "Emscripten not installed in Windows")
+def test_autotools_emscripten():
+    client = TestClient(path_with_spaces=False)
+    client.run("new autotools_exe -d name=hello -d version=0.1")
+    client.save({"wasm32": wasm32_profile, "asmjs": asmjs_profile, "base_emscripten_profile": base_emscripten_profile,})
+    client.run("build . -pr:h=wasm32")
+    assert "checking for wasm32-local-emscripten-ranlib... emranlib" in client.out
+    assert "checking for wasm32-local-emscripten-gcc... emcc" in client.out
+    assert "checking for wasm32-local-emscripten-ar... emar" in client.out
+    assert "checking the archiver (emar) interface... ar" in client.out
+    assert "checking for wasm32-local-emscripten-strip... emstrip" in client.out
+
+    assert os.path.exists(os.path.join(client.current_folder, "build-release", "src", "hello.wasm"))
+    # Run JavaScript generated code which uses .wasm file
+    client.run_command("node ./build-release/src/hello")
+    assert "Hello World Release!" in client.out
+
+    rmtree(os.path.join(client.current_folder, "build-release"))
+    client.run("build . -pr:h=asmjs")
+    assert "WASM=0" in client.out
+    # No wasm should have been generated for asm.js architecture
+    assert not os.path.exists(os.path.join(client.current_folder, "build-release", "hello.wasm"))
+    client.run_command("node ./build-release/src/hello")
+    assert "Hello World Release!" in client.out
+
+
+# TODO: test_bazel_emscripten(): need WIP new bazel toolchain
+# TODO: test_msbuild_emscripten(): give support to msbuild
+# TODO: test_premake_emscripten(): premake tests
diff --git a/test/functional/toolchains/gnu/autotools/test_win_bash.py b/test/functional/toolchains/gnu/autotools/test_win_bash.py
index 47ab66e17..b46840171 100644
--- a/test/functional/toolchains/gnu/autotools/test_win_bash.py
+++ b/test/functional/toolchains/gnu/autotools/test_win_bash.py
@@ -70,7 +70,8 @@ def test_autotools_bash_complete():
 @pytest.mark.tool("msys2")
 @pytest.mark.parametrize("frontend", ("clang", "clang-cl"))
 @pytest.mark.parametrize("runtime", ("static", "dynamic"))
-def test_autotools_bash_complete_clang(frontend, runtime):
+@pytest.mark.parametrize("build_type", ("Debug", "Release"))
+def test_autotools_bash_complete_clang(frontend, runtime, build_type):
     client = TestClient(path_with_spaces=False)
     # Problem is that msys2 also has clang in the path, so we need to make it explicit
     clangpath = tools_locations["clang"]["18"]["path"]["Windows"]
@@ -81,7 +82,7 @@ def test_autotools_bash_complete_clang(frontend, runtime):
         [settings]
         os=Windows
         arch=x86_64
-        build_type=Release
+        build_type={build_type}
         compiler=clang
         compiler.version=18
         compiler.cppstd=14
@@ -92,6 +93,7 @@ def test_autotools_bash_complete_clang(frontend, runtime):
         tools.build:compiler_executables={comps}
         tools.microsoft.bash:subsystem=msys2
         tools.microsoft.bash:path=bash
+        tools.compilation:verbosity=verbose
         """)
 
     main = gen_function_cpp(name="main")
@@ -128,16 +130,17 @@ def test_autotools_bash_complete_clang(frontend, runtime):
                  "main.cpp": main,
                  "profile_win": profile_win})
     client.run("build . -pr=profile_win")
+    print(client.out)
     client.run_command("main.exe")
     assert "__GNUC__" not in client.out
     assert "main __clang_major__18" in client.out
-    check_exe_run(client.out, "main", "clang", None, "Release", "x86_64", None)
+    check_exe_run(client.out, "main", "clang", None, build_type, "x86_64", None)
 
     bat_contents = client.load("conanbuild.bat")
     assert "conanvcvars.bat" in bat_contents
 
     static_runtime = runtime == "static"
-    check_vs_runtime("main.exe", client, "17", build_type="Release", static_runtime=static_runtime)
+    check_vs_runtime("main.exe", client, "17", build_type=build_type, static_runtime=static_runtime)
 
 
 @pytest.mark.skipif(platform.system() != "Windows", reason="Requires Windows")
diff --git a/test/functional/toolchains/test_nmake_toolchain.py b/test/functional/toolchains/test_nmake_toolchain.py
index 9837d716b..36a4121e4 100644
--- a/test/functional/toolchains/test_nmake_toolchain.py
+++ b/test/functional/toolchains/test_nmake_toolchain.py
@@ -85,3 +85,78 @@ def test_toolchain_nmake(compiler, version, runtime, cppstd, build_type,
     client.run_command("simple.exe")
     assert "dep/1.0" in client.out
     check_exe_run(client.out, "main", "msvc", version, build_type, "x86_64", cppstd, conf_preprocessors)
+
+
+@pytest.mark.skipif(platform.system() != "Windows", reason="Requires Windows")
+@pytest.mark.tool("clang", "18")
+def test_toolchain_nmake_clang():
+    compiler = "clang"
+    version = "18"
+    runtime = "dynamic"
+    cppstd = "14"
+    build_type = "Debug"
+    defines = ["TEST_DEFINITION1", "TEST_DEFINITION2=0", "TEST_DEFINITION3=", "TEST_DEFINITION4=TestPpdValue4",
+          "TEST_DEFINITION5=__declspec(dllexport)", "TEST_DEFINITION6=foo bar"]
+    cflags = cxxflags = sharedlinkflags = exelinkflags = []
+    client = TestClient(path_with_spaces=False)
+    settings = {"compiler": compiler,
+                "compiler.version": version,
+                "compiler.cppstd": cppstd,
+                "compiler.runtime": runtime,
+                "build_type": build_type,
+                "compiler.runtime_version": "v144",
+                "arch": "x86_64"}
+
+    serialize_array = lambda arr: "[{}]".format(",".join([f"'{v}'" for v in arr]))
+    conf = {
+        "tools.build:defines": serialize_array(defines) if defines else "",
+        "tools.build:cflags": serialize_array(cflags) if cflags else "",
+        "tools.build:cxxflags": serialize_array(cxxflags) if cxxflags else "",
+        "tools.build:sharedlinkflags": serialize_array(sharedlinkflags) if sharedlinkflags else "",
+        "tools.build:exelinkflags": serialize_array(exelinkflags) if exelinkflags else "",
+        "tools.build:compiler_executables": r'{\"c\": \"clang-cl\", \"cpp\": \"clang-cl\"}',
+        "tools.cmake.cmaketoolchain:generator": "Visual Studio 17",
+    }
+
+    # Build the profile according to the settings provided
+    settings = " ".join('-s %s="%s"' % (k, v) for k, v in settings.items() if v)
+
+    client.run("new cmake_lib -d name=dep -d version=1.0")
+    conf = " ".join(f'-c {k}="{v}"' for k, v in conf.items() if v)
+    client.run(f'create . -tf=\"\" {settings} {conf}')
+
+    # Rearrange defines to macro / value dict
+    conf_preprocessors = {}
+    for define in defines:
+        if "=" in define:
+            key, value = define.split("=", 1)
+            # gen_function_cpp doesn't properly handle empty macros
+            if value:
+                conf_preprocessors[key] = value
+        else:
+            conf_preprocessors[define] = "1"
+
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            settings = "os", "compiler", "build_type", "arch"
+            requires = "dep/1.0"
+            generators = "NMakeToolchain", "NMakeDeps"
+
+            def build(self):
+                self.run(f"nmake /f makefile")
+            """)
+    makefile = textwrap.dedent("""\
+        all: simple.exe
+
+        simple.exe: simple.cpp
+          $(CPP) simple.cpp -o simple.exe
+        """)
+    client.save({"conanfile.py": conanfile,
+                 "makefile": makefile,
+                 "simple.cpp": gen_function_cpp(name="main", includes=["dep"], calls=["dep"], preprocessor=conf_preprocessors.keys())},
+                clean_first=True)
+    client.run(f"build . {settings} {conf}")
+    client.run_command("simple.exe")
+    assert "dep/1.0" in client.out
+    check_exe_run(client.out, "main", "clang", "19.1", build_type, "x86_64", cppstd, conf_preprocessors)
diff --git a/test/functional/tools/scm/test_git.py b/test/functional/tools/scm/test_git.py
index 7e0a46d09..89f3c9a1d 100644
--- a/test/functional/tools/scm/test_git.py
+++ b/test/functional/tools/scm/test_git.py
@@ -1253,3 +1253,63 @@ class TestGitShallowTagClone:
             assert "pkg/0.1: URL: {}".format(url) in c.out
             assert "pkg/0.1: COMMIT IN REMOTE: False" in c.out
             assert "pkg/0.1: DIRTY: False" in c.out
+
+
+@pytest.mark.tool("git")
+class TestGitTreelessRemote:
+
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        from conan.tools.scm import Git
+        import os
+
+        class Pkg(ConanFile):
+            name = "pkg"
+            version = "0.1"
+
+            def layout(self):
+                self.folders.source = "source"
+
+            def export(self):
+                git = Git(self)
+                git.clone(url="{url}", args=["--filter=tree:0"], target="target")
+                git.folder = "target"
+                cloned_url = git.run("remote -v")
+                self.output.info("git remote: %s ===" % cloned_url)
+                cloned_url = git.get_remote_url()
+                self.output.info("get_remote_url(): %s ===" % cloned_url)
+        """)
+
+    def test_treeless_clone(self):
+        """
+        When cloning a git repository with the `--filter=tree:0` option,
+        the Git.get_remote_url() should only the URL of the repository.
+
+        Validate the issue https://github.com/conan-io/conan/issues/18415
+        """
+        repository = temp_folder(path_with_spaces=False)
+        url, commit = create_local_git_repo(files={"README": "Lumen naturale ratum est"},
+                                            folder=repository)
+
+        client = TestClient()
+        client.save({"conanfile.py": self.conanfile.format(url=url)})
+        client.run("export .")
+        # We expect [tree:0] for regular git remote command. Requires Git +2.43
+        assert f"git remote: origin\t{url} (fetch) [tree:0]" in client.out
+        # Then get_remote_url filters it to only the URL
+        assert f"get_remote_url(): {url} ===" in client.out
+
+    def test_treeless_clone_with_parenthesis(self):
+        """
+        Windows can use C:\Program Files (x86) path, which contains parenthesis.
+        As the URL will have (fetch) or (push), get_remote_url() should be able to parse it.
+        """
+        repository = os.path.join(temp_folder(), "Program Files (x86)", "myrepo")
+        os.makedirs(repository)
+        url, commit = create_local_git_repo(files={"README": "Pacem in maribus."},
+                                            folder=repository)
+
+        client = TestClient()
+        client.save({"conanfile.py": self.conanfile.format(url=url)})
+        client.run("export .")
+        assert f"get_remote_url(): {url} ===" in client.out
diff --git a/test/integration/command/export_pkg_test.py b/test/integration/command/export_pkg_test.py
index 87da54348..a30c60156 100644
--- a/test/integration/command/export_pkg_test.py
+++ b/test/integration/command/export_pkg_test.py
@@ -280,6 +280,19 @@ class TestConan(ConanFile):
         cmakeinfo = client.load("hello1-release-data.cmake")
         self.assertIn("set(hello1_LIBS_RELEASE mycoollib)", cmakeinfo)
 
+    def test_export_pkg_version_type(self):
+        client = TestClient(light=True)
+        conanfile = textwrap.dedent("""
+            from conan import ConanFile
+            class Hello(ConanFile):
+                def requirements(self):
+                    assert type(self.version) is str, "Version should be a string"
+            """)
+        client.save({"conanfile.py": conanfile})
+        client.run("create . --name=hello --version=0.1")
+        # This used to trigger the assertion error
+        client.run("export-pkg . --name=hello --version=0.1")
+
     def test_export_pkg_json(self):
         client = TestClient()
         client.save({"conanfile.py": GenConanfile("pkg", "0.1")})
diff --git a/test/integration/command/help_test.py b/test/integration/command/help_test.py
index 2af1c84a0..25bb560bf 100644
--- a/test/integration/command/help_test.py
+++ b/test/integration/command/help_test.py
@@ -1,4 +1,10 @@
+import argparse
+
+import pytest
+from mock import patch
+
 from conan import __version__
+from conan.test.utils.env import environment_update
 from conan.test.utils.tools import TestClient
 
 
@@ -43,3 +49,30 @@ class TestHelp:
         assert "Perform file operations in the local cache (of recipes and/or packages)" in c.out
         c.run("cache path -h")
         assert "Show the path to the Conan cache for a given reference" in c.out
+
+
+def test_all_commands_call_args_parse():
+    tc = TestClient(light=True)
+    tc.run("-h")
+    commands = tc.api.command.cli._builtin_commands
+    with environment_update({"CONAN_WORKSPACE_ENABLE": "will_break_next"}):
+        for command, info in commands.items():
+            if len(info._subcommands) > 0:
+                for subcommand in info._subcommands.values():
+                    with patch("conan.cli.command.ConanArgumentParser.parse_args",
+                               side_effect=Exception("called")) as mock_run:
+                        try:
+                            tc.run(f"{command} {subcommand.name} -h")
+                        except:
+                            pass
+                        finally:
+                            assert mock_run.called, f'Command "conan {command} {subcommand.name}" did not call parse_args()'
+            else:
+                with patch("conan.cli.command.ConanArgumentParser.parse_args",
+                           side_effect=Exception("called")) as mock_run:
+                    try:
+                        tc.run(f"{command} -h")
+                    except:
+                        pass
+                    finally:
+                        assert mock_run.called, f'Command "conan {command}" did not call parse_args()'
diff --git a/test/integration/command/test_audit.py b/test/integration/command/test_audit.py
index 75d5dd19b..ca9433bfa 100644
--- a/test/integration/command/test_audit.py
+++ b/test/integration/command/test_audit.py
@@ -11,6 +11,48 @@ from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.env import environment_update
 from conan.test.utils.tools import TestClient
 
+_sbom_zlib_1_2_11 = """
+{
+  "components" : [ {
+    "author" : "<Put your name here> <And your email here>",
+    "bom-ref" : "pkg:conan/zlib@1.2.11?rref=6754320047c5dd54830baaaf9fc733c4",
+    "description" : "<Description of zlib package here>",
+    "licenses" : [ {
+      "license" : {
+        "name" : "<Put the package license here>"
+      }
+    } ],
+    "name" : "zlib",
+    "purl" : "pkg:conan/zlib@1.2.11",
+    "type" : "library",
+    "version" : "1.2.11"
+  } ],
+  "dependencies" : [ {
+    "ref" : "pkg:conan/zlib@1.2.11?rref=6754320047c5dd54830baaaf9fc733c4"
+  } ],
+  "metadata" : {
+    "component" : {
+      "author" : "<Put your name here> <And your email here>",
+      "bom-ref" : "pkg:conan/zlib@1.2.11?rref=6754320047c5dd54830baaaf9fc733c4",
+      "name" : "zlib/1.2.11",
+      "type" : "library"
+    },
+    "timestamp" : "2025-06-10T08:13:11Z",
+    "tools" : [ {
+      "externalReferences" : [ {
+        "type" : "website",
+        "url" : "https://github.com/conan-io/conan"
+      } ],
+      "name" : "Conan-io"
+    } ]
+  },
+  "serialNumber" : "urn:uuid:4f7ce240-4c6d-4a87-bfb6-78d0fbc839e3",
+  "bomFormat" : "CycloneDX",
+  "specVersion" : "1.4",
+  "version" : 1
+}
+"""
+
 
 @contextmanager
 def proxy_response(status, data, retry_after=60):
@@ -63,10 +105,14 @@ def test_conan_audit_proxy():
         "error": None
     }
 
-    tc = TestClient(light=True)
+    tc = TestClient(light=True, default_server_user=True)
 
-    tc.save({"conanfile.py": GenConanfile("zlib", "1.2.11")})
-    tc.run("export .")
+    tc.save({"conanfile.py": GenConanfile("zlib", "1.2.11"),
+             "sbom.cdx.json": _sbom_zlib_1_2_11})
+    tc.run("create . --lockfile-out=conan.lock")
+    tc.run("upload * -c -r=default")
+
+    tc.run("list * -r=default -f=json", redirect_stdout="pkglist_remote.json")
 
     tc.run("list '*' -f=json", redirect_stdout="pkglist.json")
 
@@ -82,6 +128,15 @@ def test_conan_audit_proxy():
         tc.run("audit list -l=pkglist.json")
         assert "zlib/1.2.11 1 vulnerability found" in tc.out
 
+        tc.run("audit list -l=pkglist_remote.json -r=default")
+        assert "zlib/1.2.11 1 vulnerability found" in tc.out
+
+        tc.run("audit list --lockfile=conan.lock")
+        assert "zlib/1.2.11 1 vulnerability found" in tc.out
+
+        tc.run("audit list --sbom=sbom.cdx.json")
+        assert "zlib/1.2.11 1 vulnerability found" in tc.out
+
         tc.run("audit scan --requires=zlib/1.2.11")
         assert "zlib/1.2.11 1 vulnerability found" in tc.out
 
@@ -175,8 +230,9 @@ def test_conan_audit_private():
 
     tc = TestClient(light=True)
 
-    tc.save({"conanfile.py": GenConanfile("zlib", "1.2.11")})
-    tc.run("export .")
+    tc.save({"conanfile.py": GenConanfile("zlib", "1.2.11"),
+             "sbom.cdx.json": _sbom_zlib_1_2_11})
+    tc.run("create . --lockfile-out=conan.lock")
 
     tc.run("list '*' -f=json", redirect_stdout="pkglist.json")
 
@@ -199,6 +255,12 @@ def test_conan_audit_private():
         tc.run("audit list -l=pkglist.json -p=myprivate")
         assert "zlib/1.2.11 1 vulnerability found" in tc.out
 
+        tc.run("audit list --lockfile=conan.lock -p=myprivate")
+        assert "zlib/1.2.11 1 vulnerability found" in tc.out
+
+        tc.run("audit list --sbom=sbom.cdx.json -p=myprivate")
+        assert "zlib/1.2.11 1 vulnerability found" in tc.out
+
         tc.run("audit scan --requires=zlib/1.2.11  -p=myprivate")
         assert "zlib/1.2.11 1 vulnerability found" in tc.out
 
@@ -248,7 +310,7 @@ def test_audit_list_conflicting_args():
     tc = TestClient(light=True)
     tc.save({"pkglist.json": '{"Local Cache": {"zlib/1.2.11": {}}}'})
     tc.run("audit list zlib/1.2.11 -l=pkglist.json", assert_error=True)
-    assert "Please specify a reference or a pkglist file, not both" in tc.out
+    assert "argument -l/--list: not allowed with argument reference" in tc.out
 
 
 def test_audit_provider_add_missing_url():
@@ -266,7 +328,7 @@ def test_audit_provider_remove_nonexistent():
 def test_audit_list_missing_arguments():
     tc = TestClient(light=True)
     tc.run("audit list", assert_error=True)
-    assert "Please specify a reference or a pkglist file" in tc.out
+    assert "one of the arguments reference" in tc.out
 
 
 def test_audit_provider_env_credentials_with_proxy(monkeypatch):
diff --git a/test/integration/editable/test_editable_layout.py b/test/integration/editable/test_editable_layout.py
index 8c5321390..1f9beefd2 100644
--- a/test/integration/editable/test_editable_layout.py
+++ b/test/integration/editable/test_editable_layout.py
@@ -40,7 +40,7 @@ def test_editable_folders_root():
             "conanfile.py": app})
 
     c.run("editable add libs/pkg")
-    c.run("install .")
+    c.run("install . --build=editable")
     libdir = os.path.join(c.current_folder, "libs", "mybuild", "mylibdir")
     assert f"conanfile.py: PKG LIBDIR {libdir}" in c.out
     assert f"pkg/0.1: PKG package_folder {c.current_folder}" in c.out
@@ -82,7 +82,7 @@ def test_editable_folders_sibiling_root():
             "app/conanfile.py": app})
 
     c.run("editable add pkg")
-    c.run("install app")
+    c.run("install app --build=editable")
     libdir = os.path.join(c.current_folder, "mybuild", "mylibdir")
     assert f"conanfile.py: PKG LIBDIR {libdir}" in c.out
     assert f"pkg/0.1: PKG source_folder {c.current_folder}" in c.out
@@ -145,6 +145,6 @@ def test_install_editable_build_folder_vars():
             "profile": profile})
     c.run("editable add lib")
     c.run("build lib -pr=profile")
-    c.run("install app -pr=profile")
+    c.run("install app -pr=profile --build=editable")
     path = os.path.join(c.current_folder, "lib", "build", "windows-x86_64", "Release", "generators")
     assert f"lib/0.1: Writing generators to {path}" in c.out
diff --git a/test/integration/graph/core/graph_manager_base.py b/test/integration/graph/core/graph_manager_base.py
index 7c56c29d2..fb899112a 100644
--- a/test/integration/graph/core/graph_manager_base.py
+++ b/test/integration/graph/core/graph_manager_base.py
@@ -134,7 +134,7 @@ class GraphManagerTest(unittest.TestCase):
         if conanfile:
             self.assertEqual(conanfile.name, ref.name)
 
-        self.assertEqual(len(node.dependencies), len(deps))
+        self.assertEqual(len(node.edges), len(deps))
         for d in node.neighbors():
             assert d in deps
 
diff --git a/test/integration/graph/core/graph_manager_test.py b/test/integration/graph/core/graph_manager_test.py
index 4b351bc07..a010780d7 100644
--- a/test/integration/graph/core/graph_manager_test.py
+++ b/test/integration/graph/core/graph_manager_test.py
@@ -38,7 +38,7 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(2, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
+        libb = app.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[], dependents=[app])
@@ -66,8 +66,8 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -90,8 +90,8 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -112,8 +112,8 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         for r, t in libb.transitive_deps.items():
             assert r.package_id_mode == "minor_mode"
@@ -139,8 +139,8 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -161,8 +161,8 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -187,8 +187,8 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -210,8 +210,8 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -234,8 +234,8 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -256,8 +256,8 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -280,8 +280,8 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -303,8 +303,8 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -335,8 +335,8 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         # node, headers, lib, build, run
         _check_transitive(app, [(libb, True, True, False, False)])
@@ -354,7 +354,7 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(2, len(deps_graph.nodes))
         app = deps_graph.root
-        cmake = app.dependencies[0].dst
+        cmake = app.edges[0].dst
 
         # node, headers, lib, build, run
         run = package_type in ("application", "shared-library", "build-scripts")
@@ -369,7 +369,7 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(2, len(deps_graph.nodes))
         app = deps_graph.root
-        liba = app.dependencies[0].dst
+        liba = app.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[liba])
         self._check_node(liba, "liba/0.1#123", dependents=[app])
@@ -388,8 +388,8 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -413,9 +413,9 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        liba = app.dependencies[0].dst
-        libb = liba.dependencies[0].dst
-        libc = liba.dependencies[1].dst
+        liba = app.edges[0].dst
+        libb = liba.edges[0].dst
+        libc = liba.edges[1].dst
 
         self._check_node(app, "app/0.1", deps=[liba])
         self._check_node(liba, "liba/0.1#123", deps=[libb, libc], dependents=[app])
@@ -444,10 +444,10 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        liba = libd.dependencies[0].dst
-        libb = liba.dependencies[0].dst
-        libc = liba.dependencies[1].dst
+        libd = app.edges[0].dst
+        liba = libd.edges[0].dst
+        libb = liba.edges[0].dst
+        libc = liba.edges[1].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[liba], dependents=[app])
@@ -481,10 +481,10 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        liba = libd.dependencies[0].dst
-        libb = liba.dependencies[0].dst
-        libc = liba.dependencies[1].dst
+        libd = app.edges[0].dst
+        liba = libd.edges[0].dst
+        libb = liba.edges[0].dst
+        libc = liba.edges[1].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[liba], dependents=[app])
@@ -518,9 +518,9 @@ class TestLinear(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -545,9 +545,9 @@ class TestLinearFourLevels(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -571,9 +571,9 @@ class TestLinearFourLevels(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -607,9 +607,9 @@ class TestLinearFourLevels(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -643,9 +643,9 @@ class TestLinearFourLevels(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -678,9 +678,9 @@ class TestLinearFourLevels(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -711,9 +711,9 @@ class TestLinearFourLevels(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -745,9 +745,9 @@ class TestLinearFourLevels(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -780,9 +780,9 @@ class TestLinearFourLevels(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -816,9 +816,9 @@ class TestLinearFourLevels(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -847,9 +847,9 @@ class TestLinearFourLevels(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -880,9 +880,9 @@ class TestLinearFourLevels(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -912,9 +912,9 @@ class TestLinearFourLevels(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc, liba])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -946,10 +946,10 @@ class TestLinearFiveLevelsHeaders(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libc = libd.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libc = libd.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[libc], dependents=[app])
@@ -980,10 +980,10 @@ class TestLinearFiveLevelsHeaders(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libc = libd.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libc = libd.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[libc], dependents=[app])
@@ -1012,10 +1012,10 @@ class TestLinearFiveLevelsHeaders(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libc = libd.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libc = libd.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[libc], dependents=[app])
@@ -1045,10 +1045,10 @@ class TestLinearFiveLevelsHeaders(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libc = libd.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libc = libd.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[libc], dependents=[app])
@@ -1079,10 +1079,10 @@ class TestLinearFiveLevelsHeaders(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libc = libd.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libc = libd.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[libc], dependents=[app])
@@ -1112,10 +1112,10 @@ class TestLinearFiveLevelsHeaders(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libc = libd.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libc = libd.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[libc], dependents=[app])
@@ -1144,10 +1144,10 @@ class TestLinearFiveLevelsHeaders(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libc = libd.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libc = libd.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[libc], dependents=[app])
@@ -1177,10 +1177,10 @@ class TestLinearFiveLevelsHeaders(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libc = libd.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libc = libd.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[libc], dependents=[app])
@@ -1207,10 +1207,10 @@ class TestLinearFiveLevelsHeaders(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libc = libd.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libc = libd.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[libc], dependents=[app])
@@ -1237,10 +1237,10 @@ class TestLinearFiveLevelsHeaders(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libc = libd.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libc = libd.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[libc], dependents=[app])
@@ -1273,10 +1273,10 @@ class TestLinearFiveLevelsLibraries(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libc = libd.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libc = libd.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[libc], dependents=[app])
@@ -1307,10 +1307,10 @@ class TestLinearFiveLevelsLibraries(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libc = libd.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libc = libd.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[libc], dependents=[app])
@@ -1339,9 +1339,9 @@ class TestDiamond(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba = libb.edges[0].dst
 
         # TODO: No Revision??? Because of consumer?
         self._check_node(app, "app/0.1", deps=[libb, libc])
@@ -1371,9 +1371,9 @@ class TestDiamond(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba = libb.edges[0].dst
 
         # TODO: No Revision??? Because of consumer?
         self._check_node(app, "app/0.1", deps=[libb, libc])
@@ -1396,8 +1396,8 @@ class TestDiamond(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        liba = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
+        liba = app.edges[0].dst
+        libc = app.edges[1].dst
 
         # TODO: No Revision??? Because of consumer?
         self._check_node(app, "app/0.1", deps=[liba, libc])
@@ -1423,8 +1423,8 @@ class TestDiamond(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        liba = app.dependencies[1].dst
+        libc = app.edges[0].dst
+        liba = app.edges[1].dst
 
         # TODO: No Revision??? Because of consumer?
         self._check_node(app, "app/0.1", deps=[liba, libc])
@@ -1450,10 +1450,10 @@ class TestDiamond(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
-        liba1 = libc.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba = libb.edges[0].dst
+        liba1 = libc.edges[0].dst
 
         assert liba is liba1
 
@@ -1484,11 +1484,11 @@ class TestDiamond(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libb = libd.dependencies[0].dst
-        libc = libd.dependencies[1].dst
-        liba = libb.dependencies[0].dst
-        liba2 = libc.dependencies[0].dst
+        libd = app.edges[0].dst
+        libb = libd.edges[0].dst
+        libc = libd.edges[1].dst
+        liba = libb.edges[0].dst
+        liba2 = libc.edges[0].dst
 
         assert liba is liba2
 
@@ -1520,10 +1520,10 @@ class TestDiamond(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba1 = libb.dependencies[0].dst
-        liba2 = libc.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba1 = libb.edges[0].dst
+        liba2 = libc.edges[0].dst
 
         assert liba1 is not liba2
 
@@ -1555,9 +1555,9 @@ class TestDiamond(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba1 = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba1 = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb, libc])
         self._check_node(libb, "libb/0.1#123", deps=[liba1], dependents=[app])
@@ -1579,9 +1579,9 @@ class TestDiamond(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba1 = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba1 = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb, libc])
         self._check_node(libb, "libb/0.1#123", deps=[liba1], dependents=[app])
@@ -1608,10 +1608,10 @@ class TestDiamond(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libb = libd.dependencies[0].dst
-        libc = libd.dependencies[1].dst
-        liba1 = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libb = libd.edges[0].dst
+        libc = libd.edges[1].dst
+        liba1 = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libd])
         self._check_node(libd, "libd/0.1#123", deps=[libb, libc], dependents=[app])
@@ -1635,10 +1635,10 @@ class TestDiamond(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         libc = deps_graph.root
-        liba = libc.dependencies[0].dst
-        libb = libc.dependencies[1].dst
-        liba1 = libb.dependencies[0].dst
-        zlib = liba.dependencies[0].dst
+        liba = libc.edges[0].dst
+        libb = libc.edges[1].dst
+        liba1 = libb.edges[0].dst
+        zlib = liba.edges[0].dst
 
         assert liba is liba1
         # TODO: No Revision??? Because of consumer?
@@ -1670,10 +1670,10 @@ class TestDiamond(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        liba = app.dependencies[0].dst
-        spdlog = liba.dependencies[0].dst
-        fmt01 = spdlog.dependencies[0].dst
-        fmt02 = app.dependencies[1].dst
+        liba = app.edges[0].dst
+        spdlog = liba.edges[0].dst
+        fmt01 = spdlog.edges[0].dst
+        fmt02 = app.edges[1].dst
 
         self._check_node(app, "app/0.1", deps=[liba, fmt02])
         self._check_node(liba, "liba/0.1#123", deps=[spdlog], dependents=[app])
@@ -1703,12 +1703,12 @@ class TestDiamondMultiple(GraphManagerTest):
 
         self.assertEqual(7, len(deps_graph.nodes))
         app = deps_graph.root
-        libe = app.dependencies[0].dst
-        libf = app.dependencies[1].dst
-        libd = libe.dependencies[0].dst
-        libb = libd.dependencies[0].dst
-        libc = libd.dependencies[1].dst
-        liba = libc.dependencies[0].dst
+        libe = app.edges[0].dst
+        libf = app.edges[1].dst
+        libd = libe.edges[0].dst
+        libb = libd.edges[0].dst
+        libc = libd.edges[1].dst
+        liba = libc.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libe, libf])
         self._check_node(libe, "libe/0.1#123", deps=[libd], dependents=[app])
@@ -1742,12 +1742,12 @@ class TestDiamondMultiple(GraphManagerTest):
 
         self.assertEqual(7, len(deps_graph.nodes))
         app = deps_graph.root
-        libe = app.dependencies[0].dst
-        libf = app.dependencies[1].dst
-        libd = libe.dependencies[0].dst
-        libb = libd.dependencies[0].dst
-        libc = libd.dependencies[1].dst
-        liba = libc.dependencies[0].dst
+        libe = app.edges[0].dst
+        libf = app.edges[1].dst
+        libd = libe.edges[0].dst
+        libb = libd.edges[0].dst
+        libc = libd.edges[1].dst
+        liba = libc.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libe, libf])
         self._check_node(libe, "libe/0.1#123", deps=[libd], dependents=[app])
@@ -1780,10 +1780,10 @@ class TestDiamondMultiple(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        libd = app.dependencies[2].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        libd = app.edges[2].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb, libc, libd])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -1806,10 +1806,10 @@ class TestDiamondMultiple(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        libd = app.dependencies[2].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        libd = app.edges[2].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb, libc, libd])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -1831,9 +1831,9 @@ class TestDiamondMultiple(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        libb = app.dependencies[2].dst
+        libd = app.edges[0].dst
+        libc = app.edges[1].dst
+        libb = app.edges[2].dst
 
         self._check_node(app, "app/0.1", deps=[libd, libc, libb])
         self._check_node(libd, "libd/0.1#123", dependents=[app, libc])
@@ -1856,9 +1856,9 @@ class TestDiamondMultiple(GraphManagerTest):
         self.assertEqual(4, len(deps_graph.nodes))
 
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -1880,9 +1880,9 @@ class TransitiveOverridesGraphTest(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = app.dependencies[1].dst
-        liba2 = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = app.edges[1].dst
+        liba2 = libb.edges[0].dst
 
         assert liba is liba2
 
@@ -1905,7 +1905,7 @@ class TransitiveOverridesGraphTest(GraphManagerTest):
 
         self.assertEqual(2, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
+        libb = app.edges[0].dst
 
         # TODO: No Revision??? Because of consumer?
         self._check_node(app, "app/0.1", deps=[libb])
@@ -1925,10 +1925,10 @@ class TransitiveOverridesGraphTest(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba1 = libb.dependencies[0].dst
-        liba2 = libc.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba1 = libb.edges[0].dst
+        liba2 = libc.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb, libc])
         self._check_node(libb, "libb/0.1#123", deps=[liba1], dependents=[app])
@@ -1956,8 +1956,8 @@ class TransitiveOverridesGraphTest(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        dep1 = app.dependencies[0].dst
-        dep2 = app.dependencies[1].dst
+        dep1 = app.edges[0].dst
+        dep2 = app.edges[1].dst
 
         self._check_node(app, "app/0.1", deps=[dep1, dep2])
         self._check_node(dep1, "dep1/2.0#123", deps=[], dependents=[app, dep2])
@@ -1976,8 +1976,8 @@ class TransitiveOverridesGraphTest(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        dep1 = app.dependencies[0].dst
-        dep2 = app.dependencies[1].dst
+        dep1 = app.edges[0].dst
+        dep2 = app.edges[1].dst
         self._check_node(app, "app/0.1", deps=[dep1, dep2])
         self._check_node(dep1, "dep1/2.0#123", deps=[], dependents=[app])
         # dep2 no dependency, it was not resolved due to conflict
@@ -1996,9 +1996,9 @@ class TransitiveOverridesGraphTest(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba2 = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba2 = app.edges[1].dst
+        liba = libb.edges[0].dst
 
         # TODO: No Revision??? Because of consumer?
         self._check_node(app, "app/0.1", deps=[libb, liba2])
@@ -2021,8 +2021,8 @@ class PureOverrideTest(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         # TODO: No Revision??? Because of consumer?
         self._check_node(app, "app/0.1", deps=[libb])
@@ -2052,8 +2052,8 @@ class PureOverrideTest(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         # TODO: No Revision??? Because of consumer?
         self._check_node(app, "app/0.1", deps=[libb])
@@ -2077,9 +2077,9 @@ class PureOverrideTest(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         # TODO: No Revision??? Because of consumer?
         self._check_node(app, "app/0.1", deps=[libc])
@@ -2103,9 +2103,9 @@ class PureOverrideTest(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         # TODO: No Revision??? Because of consumer?
         self._check_node(app, "app/0.1", deps=[libc])
@@ -2128,9 +2128,9 @@ class PureOverrideTest(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         libc = deps_graph.root
-        liba = libc.dependencies[0].dst
-        libb = libc.dependencies[1].dst
-        liba1 = libb.dependencies[0].dst
+        liba = libc.edges[0].dst
+        libb = libc.edges[1].dst
+        liba1 = libb.edges[0].dst
 
         assert liba is liba1
         # TODO: No Revision??? Because of consumer?
@@ -2162,10 +2162,10 @@ class PackageIDDeductions(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         project = deps_graph.root
-        app1 = project.dependencies[0].dst
-        app2 = project.dependencies[1].dst
-        lib = app1.dependencies[0].dst
-        lib2 = app2.dependencies[0].dst
+        app1 = project.edges[0].dst
+        app2 = project.edges[1].dst
+        lib = app1.edges[0].dst
+        lib2 = app2.edges[0].dst
 
         assert lib is lib2
 
@@ -2202,10 +2202,10 @@ class TestProjectApp(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         project = deps_graph.root
-        app1 = project.dependencies[0].dst
-        app2 = project.dependencies[1].dst
-        lib = app1.dependencies[0].dst
-        lib2 = app2.dependencies[0].dst
+        app1 = project.edges[0].dst
+        app2 = project.edges[1].dst
+        lib = app1.edges[0].dst
+        lib2 = app2.edges[0].dst
 
         assert lib is lib2
 
@@ -2252,10 +2252,10 @@ class TestProjectApp(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         project = deps_graph.root
-        app1 = project.dependencies[0].dst
-        app2 = project.dependencies[1].dst
-        lib = app1.dependencies[0].dst
-        lib2 = app2.dependencies[0].dst
+        app1 = project.edges[0].dst
+        app2 = project.edges[1].dst
+        lib = app1.edges[0].dst
+        lib2 = app2.edges[0].dst
 
         assert lib is lib2
 
@@ -2305,10 +2305,10 @@ class TestProjectApp(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         project = deps_graph.root
-        app1 = project.dependencies[0].dst
-        app2 = project.dependencies[1].dst
-        lib1 = app1.dependencies[0].dst
-        lib2 = app2.dependencies[0].dst
+        app1 = project.edges[0].dst
+        app2 = project.edges[1].dst
+        lib1 = app1.edges[0].dst
+        lib2 = app2.edges[0].dst
 
         assert lib1 is not lib2
 
diff --git a/test/integration/graph/core/test_alias.py b/test/integration/graph/core/test_alias.py
index 7be872ab7..496cc3418 100644
--- a/test/integration/graph/core/test_alias.py
+++ b/test/integration/graph/core/test_alias.py
@@ -16,7 +16,7 @@ class TestAlias(GraphManagerTest):
 
         self.assertEqual(2, len(deps_graph.nodes))
         app = deps_graph.root
-        liba = app.dependencies[0].dst
+        liba = app.edges[0].dst
 
         self._check_node(liba, "liba/0.1#123", dependents=[app])
         self._check_node(app, "app/0.1", deps=[liba])
@@ -32,8 +32,8 @@ class TestAlias(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        liba = app.dependencies[0].dst
-        libb = app.dependencies[1].dst
+        liba = app.edges[0].dst
+        libb = app.edges[1].dst
 
         self._check_node(liba, "liba/0.1#123", dependents=[app, libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -60,8 +60,8 @@ class TestAlias(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        liba = app.dependencies[0].dst
-        libb = app.dependencies[1].dst
+        liba = app.edges[0].dst
+        libb = app.edges[1].dst
 
         self._check_node(liba, "liba/0.1#123", dependents=[app, libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -90,9 +90,9 @@ class TestAlias(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba = libb.edges[0].dst
 
         self._check_node(liba, "liba/0.1#123", dependents=[libb, libc])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -119,9 +119,9 @@ class TestAlias(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba = libb.edges[0].dst
 
         self._check_node(liba, "liba/0.1#123", dependents=[libb, libc])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -147,7 +147,7 @@ class TestAlias(GraphManagerTest):
 
         self.assertEqual(2, len(deps_graph.nodes))
         app = deps_graph.root
-        liba = app.dependencies[0].dst
+        liba = app.edges[0].dst
 
         self._check_node(liba, "liba/0.1#123", dependents=[app])
         self._check_node(app, "app/0.1", deps=[liba])
@@ -172,9 +172,9 @@ class AliasBuildRequiresTest(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba_build = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba_build = app.edges[1].dst
+        liba = libb.edges[0].dst
 
         self._check_node(liba, "liba/0.1#123", dependents=[libb])
         self._check_node(liba_build, "liba/0.2#123", dependents=[app])
diff --git a/test/integration/graph/core/test_build_requires.py b/test/integration/graph/core/test_build_requires.py
index b806d09c9..4a7bf8fdd 100644
--- a/test/integration/graph/core/test_build_requires.py
+++ b/test/integration/graph/core/test_build_requires.py
@@ -45,7 +45,7 @@ class BuildRequiresGraphTest(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(2, len(deps_graph.nodes))
         app = deps_graph.root
-        cmake = app.dependencies[0].dst
+        cmake = app.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[cmake], dependents=[])
         self._check_node(cmake, "cmake/0.1#123", deps=[], dependents=[app])
@@ -58,8 +58,8 @@ class BuildRequiresGraphTest(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        cmake = lib.dependencies[0].dst
+        lib = app.edges[0].dst
+        cmake = lib.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[lib], dependents=[])
         self._check_node(lib, "lib/0.1#123", deps=[cmake], dependents=[app])
@@ -90,9 +90,9 @@ class BuildRequiresGraphTest(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        cmake = lib.dependencies[0].dst
-        cmakelib = cmake.dependencies[0].dst
+        lib = app.edges[0].dst
+        cmake = lib.edges[0].dst
+        cmakelib = cmake.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[lib], dependents=[], settings={"os": "Linux"})
         self._check_node(lib, "lib/0.1#123", deps=[cmake], dependents=[app],
@@ -120,9 +120,9 @@ class BuildRequiresGraphTest(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        cmake2 = lib.dependencies[0].dst
-        cmake1 = cmake2.dependencies[0].dst
+        lib = app.edges[0].dst
+        cmake2 = lib.edges[0].dst
+        cmake1 = cmake2.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[lib], dependents=[])
         self._check_node(lib, "lib/0.1#123", deps=[cmake2], dependents=[app])
@@ -143,9 +143,9 @@ class BuildRequiresGraphTest(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        cmake = lib.dependencies[0].dst
-        zlib = cmake.dependencies[0].dst
+        lib = app.edges[0].dst
+        cmake = lib.edges[0].dst
+        zlib = cmake.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[lib], dependents=[])
         self._check_node(lib, "lib/0.1#123", deps=[cmake], dependents=[app])
@@ -173,11 +173,11 @@ class TestBuildRequiresTransitivityDiamond(GraphManagerTest):
 
         self.assertEqual(6, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        cmake = lib.dependencies[0].dst
-        mingw = lib.dependencies[1].dst
-        zlib1 = cmake.dependencies[0].dst
-        zlib2 = mingw.dependencies[0].dst
+        lib = app.edges[0].dst
+        cmake = lib.edges[0].dst
+        mingw = lib.edges[1].dst
+        zlib1 = cmake.edges[0].dst
+        zlib2 = mingw.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[lib], dependents=[])
         self._check_node(lib, "lib/0.1#123", deps=[cmake, mingw], dependents=[app])
@@ -212,11 +212,11 @@ class TestBuildRequiresTransitivityDiamond(GraphManagerTest):
 
         self.assertEqual(6, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        cmake = lib.dependencies[0].dst
-        mingw = lib.dependencies[1].dst
-        zlib1 = cmake.dependencies[0].dst
-        zlib2 = mingw.dependencies[0].dst
+        lib = app.edges[0].dst
+        cmake = lib.edges[0].dst
+        mingw = lib.edges[1].dst
+        zlib1 = cmake.edges[0].dst
+        zlib2 = mingw.edges[0].dst
 
         assert zlib1 is not zlib2
 
@@ -242,9 +242,9 @@ class TestBuildRequiresTransitivityDiamond(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         cheetah = deps_graph.root
-        gazelle = cheetah.dependencies[0].dst
-        grass2 = cheetah.dependencies[1].dst
-        grass1 = gazelle.dependencies[0].dst
+        gazelle = cheetah.edges[0].dst
+        grass2 = cheetah.edges[1].dst
+        grass1 = gazelle.edges[0].dst
         self._check_node(cheetah, "cheetah/0.1", deps=[gazelle, grass2])
         self._check_node(gazelle, "gazelle/0.1#123", deps=[grass1], dependents=[cheetah])
         self._check_node(grass1, "grass/0.1#123", deps=[], dependents=[gazelle])
@@ -261,9 +261,9 @@ class TestBuildRequiresVisible(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[libc], dependents=[])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -289,7 +289,7 @@ class TestTestRequire(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(2, len(deps_graph.nodes))
         app = deps_graph.root
-        gtest = app.dependencies[0].dst
+        gtest = app.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[gtest], dependents=[])
         self._check_node(gtest, "gtest/0.1#123", deps=[], dependents=[app])
@@ -305,8 +305,8 @@ class TestTestRequire(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        gtest = lib.dependencies[0].dst
+        lib = app.edges[0].dst
+        gtest = lib.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[lib], dependents=[])
         self._check_node(lib, "lib/0.1#123", deps=[gtest], dependents=[app])
@@ -324,8 +324,8 @@ class TestTestRequire(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        gtest = lib.dependencies[0].dst
+        lib = app.edges[0].dst
+        gtest = lib.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[lib], dependents=[])
         self._check_node(lib, "lib/0.1#123", deps=[gtest], dependents=[app])
@@ -356,9 +356,9 @@ class TestTestRequire(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        gtest = lib.dependencies[0].dst
-        gtestlib = gtest.dependencies[0].dst
+        lib = app.edges[0].dst
+        gtest = lib.edges[0].dst
+        gtestlib = gtest.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[lib], dependents=[], settings={"os": "Linux"})
         self._check_node(lib, "lib/0.1#123", deps=[gtest], dependents=[app],
@@ -393,10 +393,10 @@ class TestTestRequire(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        gtest = lib.dependencies[1].dst
-        zlib = gtest.dependencies[0].dst
-        zlib2 = lib.dependencies[0].dst
+        lib = app.edges[0].dst
+        gtest = lib.edges[1].dst
+        zlib = gtest.edges[0].dst
+        zlib2 = lib.edges[0].dst
         assert zlib is zlib2
 
         self._check_node(app, "app/0.1@", deps=[lib], dependents=[])
@@ -419,9 +419,9 @@ class TestTestRequire(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         opencv = deps_graph.root
-        gtest14 = opencv.dependencies[0].dst
-        abseil = gtest14.dependencies[0].dst
-        gtest11 = abseil.dependencies[0].dst
+        gtest14 = opencv.edges[0].dst
+        abseil = gtest14.edges[0].dst
+        gtest11 = abseil.edges[0].dst
 
         self._check_node(opencv, "opencv/1.0@", deps=[gtest14], dependents=[])
         self._check_node(gtest14, "gtest/1.14#123", deps=[abseil], dependents=[opencv])
@@ -435,13 +435,13 @@ class TestTestRequiresProblemsShared(GraphManagerTest):
         self.assertEqual(3, len(deps_graph.nodes))
         lib_c = deps_graph.root
         if not reverse:
-            lib_a = lib_c.dependencies[0].dst
-            util = lib_a.dependencies[0].dst
-            util2 = lib_c.dependencies[1].dst
+            lib_a = lib_c.edges[0].dst
+            util = lib_a.edges[0].dst
+            util2 = lib_c.edges[1].dst
         else:
-            util = lib_c.dependencies[0].dst
-            lib_a = lib_c.dependencies[1].dst
-            util2 = lib_a.dependencies[0].dst
+            util = lib_c.edges[0].dst
+            lib_a = lib_c.edges[1].dst
+            util2 = lib_a.edges[0].dst
         assert util is util2
 
         self._check_node(lib_c, "lib_c/0.1@", deps=[lib_a, util], dependents=[])
@@ -582,8 +582,8 @@ class BuildRequiresPackageIDTest(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        cmake = lib.dependencies[0].dst
+        lib = app.edges[0].dst
+        cmake = lib.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[lib], dependents=[])
         self._check_node(lib, "lib/0.1#123", deps=[cmake], dependents=[app])
@@ -600,8 +600,8 @@ class BuildRequiresPackageIDTest(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        cmake = lib.dependencies[0].dst
+        lib = app.edges[0].dst
+        cmake = lib.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[lib], dependents=[])
         self._check_node(lib, "lib/0.1#123", deps=[cmake], dependents=[app])
@@ -616,7 +616,7 @@ class BuildRequiresPackageIDTest(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
+        lib = app.edges[0].dst
         assert lib.package_id == "2813db72897dd13aca2af071efe8ecb116f679ed"
         assert lib.package_id != NO_SETTINGS_PACKAGE_ID
 
@@ -634,8 +634,8 @@ class PublicBuildRequiresTest(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        cmake = lib.dependencies[0].dst
+        lib = app.edges[0].dst
+        cmake = lib.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[lib], dependents=[])
         self._check_node(lib, "lib/0.1#123", deps=[cmake], dependents=[app])
@@ -663,17 +663,17 @@ class PublicBuildRequiresTest(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(8 + 4, len(deps_graph.nodes))
         app = deps_graph.root
-        liba = app.dependencies[0].dst
-        libb = liba.dependencies[0].dst
-        libsfun = libb.dependencies[0].dst
-        libx = libsfun.dependencies[0].dst
-        liby = libx.dependencies[0].dst
-        libz = liby.dependencies[0].dst
-        sfun = libb.dependencies[1].dst
-        libsfun_build = sfun.dependencies[0].dst
-        libx_build = libsfun_build.dependencies[0].dst
-        liby_build = libx_build.dependencies[0].dst
-        libz_build = liby_build.dependencies[0].dst
+        liba = app.edges[0].dst
+        libb = liba.edges[0].dst
+        libsfun = libb.edges[0].dst
+        libx = libsfun.edges[0].dst
+        liby = libx.edges[0].dst
+        libz = liby.edges[0].dst
+        sfun = libb.edges[1].dst
+        libsfun_build = sfun.edges[0].dst
+        libx_build = libsfun_build.edges[0].dst
+        liby_build = libx_build.edges[0].dst
+        libz_build = liby_build.edges[0].dst
 
         # TODO non-build-requires
 
@@ -747,9 +747,9 @@ class PublicBuildRequiresTest(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        cmake1 = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        cmake1 = libb.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[libb, libc], dependents=[])
         self._check_node(libb, "libb/0.1#123", deps=[cmake1], dependents=[app])
@@ -776,11 +776,11 @@ class PublicBuildRequiresTest(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(6, len(deps_graph.nodes))
         app = deps_graph.root
-        libd = app.dependencies[0].dst
-        libe = app.dependencies[1].dst
-        libb = libd.dependencies[0].dst
-        libc = libe.dependencies[0].dst
-        cmake1 = libb.dependencies[0].dst
+        libd = app.edges[0].dst
+        libe = app.edges[1].dst
+        libb = libd.edges[0].dst
+        libc = libe.edges[0].dst
+        cmake1 = libb.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[libd, libe], dependents=[])
         self._check_node(libd, "libd/0.1#123", deps=[libb], dependents=[app])
@@ -803,9 +803,9 @@ class PublicBuildRequiresTest(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        protobuf_host = libb.dependencies[0].dst
-        protobuf_build = libb.dependencies[1].dst
+        libb = app.edges[0].dst
+        protobuf_host = libb.edges[0].dst
+        protobuf_build = libb.edges[1].dst
 
         self._check_node(app, "app/0.1@", deps=[libb], dependents=[])
         self._check_node(libb, "libb/0.1#123", deps=[protobuf_host, protobuf_build],
@@ -834,9 +834,9 @@ class PublicBuildRequiresTest(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        protobuf_host = libb.dependencies[0].dst
-        protobuf_build = libb.dependencies[1].dst
+        libb = app.edges[0].dst
+        protobuf_host = libb.edges[0].dst
+        protobuf_build = libb.edges[1].dst
 
         self._check_node(app, "app/0.1@", deps=[libb], dependents=[])
         self._check_node(libb, "libb/0.1#123", deps=[protobuf_host, protobuf_build],
@@ -870,7 +870,7 @@ class PublicBuildRequiresTest(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(2, len(deps_graph.nodes))
         app = deps_graph.root
-        tool = app.dependencies[0].dst
+        tool = app.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[tool], dependents=[])
         self._check_node(tool, "gtest/0.1#123", deps=[], dependents=[app])
@@ -890,9 +890,9 @@ class TestLoops(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        tool = app.dependencies[0].dst
-        tool2 = tool.dependencies[0].dst
-        tool3 = tool2.dependencies[0].dst
+        tool = app.edges[0].dst
+        tool2 = tool.edges[0].dst
+        tool3 = tool2.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[tool], dependents=[])
         self._check_node(tool, "cmake/0.1#123", deps=[tool2], dependents=[app])
@@ -912,11 +912,11 @@ class TestLoops(GraphManagerTest):
         # Build requires always apply to the consumer
         self.assertEqual(6, len(deps_graph.nodes))
         app = deps_graph.root
-        cmake = app.dependencies[0].dst
-        gtest = cmake.dependencies[0].dst
-        cmake2 = gtest.dependencies[0].dst
-        gtest2 = cmake2.dependencies[0].dst
-        cmake3 = gtest2.dependencies[0].dst
+        cmake = app.edges[0].dst
+        gtest = cmake.edges[0].dst
+        cmake2 = gtest.edges[0].dst
+        gtest2 = cmake2.edges[0].dst
+        cmake3 = gtest2.edges[0].dst
 
         assert deps_graph.error.ancestor == cmake
         assert deps_graph.error.node == cmake3
diff --git a/test/integration/graph/core/test_options.py b/test/integration/graph/core/test_options.py
index 086e53be2..04be8d51d 100644
--- a/test/integration/graph/core/test_options.py
+++ b/test/integration/graph/core/test_options.py
@@ -17,8 +17,8 @@ class TestOptions(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -42,8 +42,8 @@ class TestOptions(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -68,10 +68,10 @@ class TestOptions(GraphManagerTest):
         deps_graph = self.build_consumer(consumer)
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
-        liba2 = libc.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba = libb.edges[0].dst
+        liba2 = libc.edges[0].dst
         assert liba is liba2
         self._check_node(app, "app/0.1", deps=[libc, libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -82,10 +82,10 @@ class TestOptions(GraphManagerTest):
         deps_graph = self.build_consumer(consumer)
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
-        liba2 = libc.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = app.edges[1].dst
+        liba = libb.edges[0].dst
+        liba2 = libc.edges[0].dst
         assert liba is liba2
         self._check_node(app, "app/0.1", deps=[libc, libb])
         self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -107,10 +107,10 @@ class TestOptions(GraphManagerTest):
 
             self.assertEqual(4, len(deps_graph.nodes))
             app = deps_graph.root
-            libb = app.dependencies[0].dst
-            libc = app.dependencies[1].dst
-            liba = libb.dependencies[0].dst
-            liba2 = libc.dependencies[0].dst
+            libb = app.edges[0].dst
+            libc = app.edges[1].dst
+            liba = libb.edges[0].dst
+            liba2 = libc.edges[0].dst
             assert liba is liba2
             self._check_node(app, "app/0.1", deps=[libc, libb])
             self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -125,10 +125,10 @@ class TestOptions(GraphManagerTest):
 
             self.assertEqual(4, len(deps_graph.nodes))
             app = deps_graph.root
-            libc = app.dependencies[0].dst
-            libb = app.dependencies[1].dst
-            liba = libb.dependencies[0].dst
-            liba2 = libc.dependencies[0].dst
+            libc = app.edges[0].dst
+            libb = app.edges[1].dst
+            liba = libb.edges[0].dst
+            liba2 = libc.edges[0].dst
             assert liba is liba2
             self._check_node(app, "app/0.1", deps=[libc, libb])
             self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
@@ -149,11 +149,11 @@ class TestBuildRequireOptions(GraphManagerTest):
 
         self.assertEqual(6, len(deps_graph.nodes))
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        protobuf_host = lib.dependencies[0].dst
-        protobuf_build = lib.dependencies[1].dst
-        zlib_shared = protobuf_host.dependencies[0].dst
-        zlib_static = protobuf_build.dependencies[0].dst
+        lib = app.edges[0].dst
+        protobuf_host = lib.edges[0].dst
+        protobuf_build = lib.edges[1].dst
+        zlib_shared = protobuf_host.edges[0].dst
+        zlib_static = protobuf_build.edges[0].dst
 
         self._check_node(app, "app/0.1@", deps=[lib], dependents=[])
         self._check_node(lib, "lib/0.1#123", deps=[protobuf_host, protobuf_build], dependents=[app])
diff --git a/test/integration/graph/core/test_provides.py b/test/integration/graph/core/test_provides.py
index adf2b9bb8..3da1c1d9a 100644
--- a/test/integration/graph/core/test_provides.py
+++ b/test/integration/graph/core/test_provides.py
@@ -21,7 +21,7 @@ class TestProvidesTest(GraphManagerTest):
 
         self.assertEqual(2, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
+        libb = app.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[], dependents=[app])
@@ -38,8 +38,8 @@ class TestProvidesTest(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libb])
         self._check_node(libb, "libb/0.1#123", deps=[libc], dependents=[app])
@@ -64,8 +64,8 @@ class TestProvidesTest(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
 
         self._check_node(app, "app/0.1", deps=[libb, libc])
         self._check_node(libb, "libb/0.1#123", deps=[], dependents=[app])
@@ -83,8 +83,8 @@ class TestProvidesTest(GraphManagerTest):
         self.assertEqual(3, len(deps_graph.nodes))
 
         app = deps_graph.root
-        br = app.dependencies[0].dst
-        br_lib = br.dependencies[0].dst
+        br = app.edges[0].dst
+        br_lib = br.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[br])
         self._check_node(br, "br/0.1#123", deps=[br_lib], dependents=[app])
@@ -109,10 +109,10 @@ class TestProvidesTest(GraphManagerTest):
 
         self.assertEqual(5, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba1 = libb.dependencies[0].dst
-        libd2 = libc.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba1 = libb.edges[0].dst
+        libd2 = libc.edges[0].dst
         self._check_node(app, "app/0.1", deps=[libb, libc])
         self._check_node(libb, "libb/0.1#123", deps=[liba1], dependents=[app])
         self._check_node(libc, "libc/0.1#123", deps=[libd2], dependents=[app])
@@ -136,9 +136,9 @@ class TestProvidesTest(GraphManagerTest):
         self.assertEqual(4, len(deps_graph.nodes))
 
         app = deps_graph.root
-        libc = app.dependencies[0].dst
-        libb = libc.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libc = app.edges[0].dst
+        libb = libc.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[libc])
         self._check_node(libc, "libc/0.1#123", deps=[libb], dependents=[app])
@@ -158,8 +158,8 @@ class ProvidesBuildRequireTest(GraphManagerTest):
         self.assertEqual(3, len(deps_graph.nodes))
 
         app = deps_graph.root
-        br = app.dependencies[0].dst
-        br_lib = br.dependencies[0].dst
+        br = app.edges[0].dst
+        br_lib = br.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[br])
         self._check_node(br, "br/0.1#123", deps=[br_lib], dependents=[app])
@@ -180,8 +180,8 @@ class ProvidesBuildRequireTest(GraphManagerTest):
         self.assertEqual(3, len(deps_graph.nodes))
 
         app = deps_graph.root
-        lib = app.dependencies[0].dst
-        br = lib.dependencies[0].dst
+        lib = app.edges[0].dst
+        br = lib.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[lib])
         self._check_node(lib, "lib/0.1#123", deps=[br], dependents=[app])
@@ -205,8 +205,8 @@ class ProvidesBuildRequireTest(GraphManagerTest):
         self.assertEqual(3, len(deps_graph.nodes))
 
         app = deps_graph.root
-        br = app.dependencies[0].dst
-        br_lib = br.dependencies[0].dst
+        br = app.edges[0].dst
+        br_lib = br.edges[0].dst
 
         self._check_node(app, "app/0.1", deps=[br])
         self._check_node(br, "br/0.1#123", deps=[br_lib], dependents=[app])
@@ -226,8 +226,8 @@ class ProvidesBuildRequireTest(GraphManagerTest):
         self.assertEqual(3, len(deps_graph.nodes))
 
         app = deps_graph.root
-        br1 = app.dependencies[0].dst
-        br2 = app.dependencies[1].dst
+        br1 = app.edges[0].dst
+        br2 = app.edges[1].dst
 
         self._check_node(app, "app/0.1", deps=[br1, br2])
         self._check_node(br1, "br1/0.1#123", deps=[], dependents=[app])
diff --git a/test/integration/graph/core/test_version_ranges.py b/test/integration/graph/core/test_version_ranges.py
index 7e4a9ec04..c49cfdb7d 100644
--- a/test/integration/graph/core/test_version_ranges.py
+++ b/test/integration/graph/core/test_version_ranges.py
@@ -19,7 +19,7 @@ class TestVersionRanges(GraphManagerTest):
 
         self.assertEqual(2, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
+        libb = app.edges[0].dst
 
         self._check_node(libb, "libb/0.2#123", dependents=[app])
         self._check_node(app, "app/0.1", deps=[libb])
@@ -42,7 +42,7 @@ class TestVersionRanges(GraphManagerTest):
             deps_graph = self.build_consumer(consumer)
             self.assertEqual(2, len(deps_graph.nodes))
             app = deps_graph.root
-            libb = app.dependencies[0].dst
+            libb = app.edges[0].dst
 
             self._check_node(libb, f"libb/{solution}#123", dependents=[app])
             self._check_node(app, "app/0.1", deps=[libb])
@@ -115,9 +115,9 @@ class TestVersionRangesDiamond(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba = libb.edges[0].dst
 
         self._check_node(liba, "liba/0.2#123", dependents=[libb, libc], deps=[])
         self._check_node(libb, "libb/0.1#123", dependents=[app], deps=[liba])
@@ -137,9 +137,9 @@ class TestVersionRangesDiamond(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba = libb.edges[0].dst
 
         self._check_node(liba, "liba/0.2#123", dependents=[libb, libc], deps=[])
         self._check_node(libb, "libb/0.1#123", dependents=[app], deps=[liba])
@@ -158,9 +158,9 @@ class TestVersionRangesDiamond(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba = libb.edges[0].dst
 
         self._check_node(liba, "liba/0.1#123", dependents=[libb, libc], deps=[])
         self._check_node(libb, "libb/0.1#123", dependents=[app], deps=[liba])
@@ -181,9 +181,9 @@ class TestVersionRangesDiamond(GraphManagerTest):
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
         app.enabled_remotes = [Remote("foo", None)]
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba = libb.edges[0].dst
 
         self._check_node(libb, "libb/0.1#123", dependents=[app], deps=[liba])
         self._check_node(libb, "libb/0.1#123", dependents=[app], deps=[liba])
@@ -203,9 +203,9 @@ class TestVersionRangesDiamond(GraphManagerTest):
 
         self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        libc = app.dependencies[1].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        libc = app.edges[1].dst
+        liba = libb.edges[0].dst
 
         self._check_node(libb, "libb/0.1#123", dependents=[app], deps=[liba])
         self._check_node(libb, "libb/0.1#123", dependents=[app], deps=[liba])
@@ -225,8 +225,8 @@ class TestVersionRangesOverridesDiamond(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(liba, "liba/0.2#123", dependents=[libb, app], deps=[])
         self._check_node(libb, "libb/0.1#123", dependents=[app], deps=[liba])
@@ -243,8 +243,8 @@ class TestVersionRangesOverridesDiamond(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(liba, "liba/0.1#123", dependents=[libb, app], deps=[])
         self._check_node(libb, "libb/0.1#123", dependents=[app], deps=[liba])
@@ -261,8 +261,8 @@ class TestVersionRangesOverridesDiamond(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(liba, "liba/0.1#123", dependents=[libb, app], deps=[])
         self._check_node(libb, "libb/0.1#123", dependents=[app], deps=[liba])
@@ -281,7 +281,7 @@ class TestVersionRangesOverridesDiamond(GraphManagerTest):
 
         self.assertEqual(2, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
+        libb = app.edges[0].dst
 
     def test_transitive_fixed_conflict_forced(self):
         # app ---> libb/0.1 -----------> liba/1.2
@@ -295,8 +295,8 @@ class TestVersionRangesOverridesDiamond(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(liba, "liba/1.2#123", dependents=[libb, app], deps=[])
         self._check_node(libb, "libb/0.1#123", dependents=[app], deps=[liba])
@@ -315,8 +315,8 @@ class TestVersionRangesOverridesDiamond(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(liba, "liba/0.3#123", dependents=[libb, app], deps=[])
         self._check_node(libb, "libb/0.1#123", dependents=[app], deps=[liba])
@@ -338,8 +338,8 @@ class TestVersionRangesOverridesDiamond(GraphManagerTest):
 
         self.assertEqual(3, len(deps_graph.nodes))
         app = deps_graph.root
-        libb = app.dependencies[0].dst
-        liba = libb.dependencies[0].dst
+        libb = app.edges[0].dst
+        liba = libb.edges[0].dst
 
         self._check_node(liba, "liba/0.2#123", dependents=[libb, app], deps=[])
         self._check_node(libb, "libb/0.1#123", dependents=[app], deps=[liba])
diff --git a/test/integration/remote/test_local_recipes_index.py b/test/integration/remote/test_local_recipes_index.py
index b843c9a8d..96d803e7a 100644
--- a/test/integration/remote/test_local_recipes_index.py
+++ b/test/integration/remote/test_local_recipes_index.py
@@ -391,6 +391,16 @@ class TestErrorsUx:
         c.run("install --requires=zlib/0.1@myuser/mychannel")
         c.assert_listed_require({"zlib/0.1@myuser/mychannel": "Downloaded (default)"})
 
+    def test_errors_missing_folder(self):
+        folder = temp_folder()
+        repo = os.path.join(folder, "repo")
+        mkdir(repo)
+        c = TestClient(light=True)
+        c.run(f"remote add local '{repo}'")
+        # shutil.rmtree(repo)
+        c.run("install --requires=zlib/[*] --build missing", assert_error=True)
+        assert "Cannot connect to 'local-recipes-index' repository, missing 'recipes'" in c.out
+
 
 class TestPythonRequires:
     @pytest.fixture(scope="class")
diff --git a/test/integration/test_package_vendor.py b/test/integration/test_package_vendor.py
index 08c3e4c35..b0cfe7b34 100644
--- a/test/integration/test_package_vendor.py
+++ b/test/integration/test_package_vendor.py
@@ -109,7 +109,7 @@ def test_package_vendor_editable():
             })
     c.run("create pkga")
     c.run("editable add pkgb")
-    c.run("install app -s os=Linux")
+    c.run("install app -s os=Linux --build=editable")
     assert "pkga" in c.out
     # The environment file of "app" doesn't have any visibility of the "pkga" paths
     envfile_app = c.load("app/conanrunenv.sh")
diff --git a/test/integration/toolchains/cmake/test_cmaketoolchain.py b/test/integration/toolchains/cmake/test_cmaketoolchain.py
index 70b27bf9a..bbd6931d5 100644
--- a/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -887,24 +887,25 @@ def test_android_legacy_toolchain_with_compileflags(cmake_legacy_toolchain):
 @pytest.mark.skipif(platform.system() != "Windows", reason="Only Windows")
 def test_presets_paths_normalization():
     # https://github.com/conan-io/conan/issues/11795
+    # But then also https://github.com/conan-io/conan/issues/18434
     client = TestClient()
     conanfile = textwrap.dedent("""
-            from conan import ConanFile
-            from conan.tools.cmake import cmake_layout
+        from conan import ConanFile
+        from conan.tools.cmake import cmake_layout
 
-            class Conan(ConanFile):
-                settings = "os", "arch", "compiler", "build_type"
-                generators = "CMakeToolchain"
+        class Conan(ConanFile):
+            settings = "os", "arch", "compiler", "build_type"
+            generators = "CMakeToolchain"
 
-                def layout(self):
-                    cmake_layout(self)
-            """)
+            def layout(self):
+                cmake_layout(self)
+        """)
     client.save({"conanfile.py": conanfile, "CMakeLists.txt": "foo"})
     client.run("install .")
 
     presets = json.loads(client.load("CMakeUserPresets.json"))
-
-    assert "/" not in presets["include"]
+    assert "/" in presets["include"][0]
+    assert "\\" not in presets["include"][0]
 
 
 @pytest.mark.parametrize("arch, arch_toolset", [("x86", "x86_64"), ("x86_64", "x86_64"),
@@ -1115,7 +1116,7 @@ def test_build_folder_vars_editables():
     c.run("editable add dep")
     conf = "tools.cmake.cmake_layout:build_folder_vars='[\"settings.os\", \"settings.build_type\"]'"
     settings = " -s os=FreeBSD -s arch=armv8 -s build_type=Debug"
-    c.run("install app -c={} {}".format(conf, settings))
+    c.run("install app -c={} {} --build=editable".format(conf, settings))
     assert os.path.exists(os.path.join(c.current_folder, "dep", "build", "freebsd-debug"))
 
 
diff --git a/test/unittests/client/build/compiler_flags_test.py b/test/unittests/client/build/compiler_flags_test.py
index fee6ff32c..4c82661f5 100644
--- a/test/unittests/client/build/compiler_flags_test.py
+++ b/test/unittests/client/build/compiler_flags_test.py
@@ -127,10 +127,10 @@ class CompilerFlagsTest(unittest.TestCase):
         conanfile.settings = settings
         self.assertEqual(' '.join(build_type_flags(conanfile)), flags)
 
-    @parameterized.expand([("clang", "Debug", "-gline-tables-only -fno-inline -O0"),
-                           ("clang", "Release", "-O2"),
-                           ("clang", "RelWithDebInfo", "-gline-tables-only -O2 -fno-inline"),
-                           ("clang", "MinSizeRel", ""),
+    @parameterized.expand([("clang", "Debug", "-Zi -Ob0 -Od"),
+                           ("clang", "Release", "-O2 -Ob2"),
+                           ("clang", "RelWithDebInfo", "-Zi -O2 -Ob1"),
+                           ("clang", "MinSizeRel", "-O1 -Ob1"),
                            ])
     def test_build_type_flags_clangcl(self, compiler, build_type, flags):
         settings = MockSettings({"compiler": compiler,
diff --git a/test/unittests/tools/gnu/test_triplets.py b/test/unittests/tools/gnu/test_triplets.py
index 626479576..5a8998ea5 100644
--- a/test/unittests/tools/gnu/test_triplets.py
+++ b/test/unittests/tools/gnu/test_triplets.py
@@ -51,6 +51,7 @@ from conan.errors import ConanException
     ["tvOS", "x86_64", None, "x86_64-apple-tvos"],
     ["Emscripten", "asm.js", None, "asmjs-local-emscripten"],
     ["Emscripten", "wasm", None, "wasm32-local-emscripten"],
+    ["Emscripten", "wasm64", None, "wasm64-local-emscripten"],
     ["AIX", "ppc32", None, "rs6000-ibm-aix"],
     ["AIX", "ppc64", None, "powerpc-ibm-aix"],
     ["Neutrino", "armv7", None, "arm-nto-qnx"],