Allow toplevel project to override mirrors for subprojects

Previously, only the user configuration could override mirrors
diff --git a/src/buildstream/_project.py b/src/buildstream/_project.py
index 8e114c8..fad8351 100644
--- a/src/buildstream/_project.py
+++ b/src/buildstream/_project.py
@@ -59,6 +59,7 @@
         self.source_overrides = {}  # Source specific configurations
         self.mirrors = {}  # Dictionary of SourceMirror objects
         self.default_mirror = None  # The name of the preferred mirror.
+        self.mirror_overrides = {}
         self._aliases = None  # Aliases dictionary
 
 
@@ -136,7 +137,8 @@
         self._shell_command: List[str] = []  # The default interactive shell command
         self._shell_environment: Dict[str, str] = {}  # Statically set environment vars
         self._shell_host_files: List[_HostMount] = []  # A list of HostMount objects
-        self._mirror_override: bool = False  # Whether mirrors have been declared in user configuration
+        self._user_mirror_override: bool = False  # Whether mirrors have been declared in user configuration
+        self._toplevel_mirror_override: bool = False  # Whether mirrors have been declared in the toplevel project
 
         # This is a lookup table of lists indexed by project,
         # the child dictionaries are lists of ScalarNodes indicating
@@ -425,8 +427,10 @@
         uri_list: List[Tuple[Optional[SourceMirror], Optional[str]]] = []
         policy = self._context.track_source if tracking else self._context.fetch_source
 
-        if policy in (_SourceUriPolicy.ALL, _SourceUriPolicy.MIRRORS) or (
-            policy == _SourceUriPolicy.USER and self._mirror_override
+        if (
+            policy in (_SourceUriPolicy.ALL, _SourceUriPolicy.MIRRORS)
+            or (policy == _SourceUriPolicy.USER and self._user_mirror_override)
+            or (policy == _SourceUriPolicy.TOPLEVEL and self._toplevel_mirror_override)
         ):
             for mirror_name, mirror in config.mirrors.items():
                 mirror_uri_list = mirror._get_alias_uris(alias)
@@ -671,6 +675,7 @@
                 "sources",
                 "source-caches",
                 "junctions",
+                "projects",
                 "(@)",
                 "(?)",
             ]
@@ -1041,6 +1046,30 @@
         # Override default_mirror if not set by command-line
         output.default_mirror = self._default_mirror or overrides.get_str("default-mirror", default=None)
 
+        if self == toplevel_project:
+            project_overrides = config.get_mapping("projects", default={})
+
+            for project_name, project_overrides in project_overrides.items():
+                project_overrides.validate_keys(["mirrors"])
+                mirrors_node = project_overrides.get_sequence("mirrors", default=None)
+
+                if mirrors_node is None:
+                    continue
+
+                variables.expand(mirrors_node)
+
+                mirrors = []
+                # Collect SourceMirror objects
+                for mirror_node in mirrors_node:
+                    mirror = self.source_mirror_factory.create(self._context, self, mirror_node)
+                    mirrors.append(mirror)
+                output.mirror_overrides[project_name] = mirrors
+
+        if self == toplevel_project:
+            toplevel_config = output
+        else:
+            toplevel_config = toplevel_project.config
+
         # First try mirrors specified in user configuration, user configuration
         # is allowed to completely disable mirrors by specifying an empty list,
         # so we check for a None value here too.
@@ -1048,19 +1077,27 @@
         mirrors_node = overrides.get_sequence("mirrors", default=None)
         if mirrors_node is None:
             mirrors_node = config.get_sequence("mirrors", default=[])
+            if self.name in toplevel_config.mirror_overrides:
+                self._toplevel_mirror_override = True
         else:
-            self._mirror_override = True
+            self._user_mirror_override = True
 
-        # Perform variable substitutions in source mirror definitions,
-        # even if the mirrors are specified in user configuration.
-        variables.expand(mirrors_node)
+        if self._toplevel_mirror_override:
+            for mirror in toplevel_config.mirror_overrides[self.name]:
+                output.mirrors[mirror.name] = mirror
+                if not output.default_mirror:
+                    output.default_mirror = mirror.name
+        else:
+            # Perform variable substitutions in source mirror definitions,
+            # even if the mirrors are specified in user configuration.
+            variables.expand(mirrors_node)
 
-        # Collect SourceMirror objects
-        for mirror_node in mirrors_node:
-            mirror = self.source_mirror_factory.create(self._context, self, mirror_node)
-            output.mirrors[mirror.name] = mirror
-            if not output.default_mirror:
-                output.default_mirror = mirror.name
+            # Collect SourceMirror objects
+            for mirror_node in mirrors_node:
+                mirror = self.source_mirror_factory.create(self._context, self, mirror_node)
+                output.mirrors[mirror.name] = mirror
+                if not output.default_mirror:
+                    output.default_mirror = mirror.name
 
         # Source url aliases
         output._aliases = config.get_mapping("aliases", default={})
diff --git a/src/buildstream/types.py b/src/buildstream/types.py
index 7789417..eef5885 100644
--- a/src/buildstream/types.py
+++ b/src/buildstream/types.py
@@ -274,6 +274,10 @@
     # configuration has not provided a mirror
     USER = "user"
 
+    # Use only URIs defined in the toplevel project (the project on  which
+    # BuildStream was invoked with as opposed to a junctioned subproject.
+    TOPLEVEL = "toplevel"
+
 
 # _PipelineSelection()
 #