From nobody Thu May 16 09:06:59 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) client-ip=66.175.222.108; envelope-from=bounce+27952+67644+1787277+3901457@groups.io; helo=mail02.groups.io; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) smtp.mailfrom=bounce+27952+67644+1787277+3901457@groups.io; dmarc=fail(p=none dis=none) header.from=intel.com ARC-Seal: i=1; a=rsa-sha256; t=1605588611; cv=none; d=zohomail.com; s=zohoarc; b=PmqH3meTX2h4N3IKUyPHO9pDETyKBqWU5qI79hYuDudHBIKiNZLdT6s1lAXmoNthtn8aKr35ou+dLvfn5hqm85QbasDsVdY/5fEMjqWte4Q6JNYLEMYHi0Td8ZGdqcHZV7E3+5vlvAQA9ASnAvOmeQ+uTFgn6GYdVgFRzm4FkVM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1605588611; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Id:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:References:Sender:Subject:To; bh=sCK1rg30y/v7hIn5wE1bVPESOtzjSeZsipu975OdOws=; b=R5YustDp/OVvL+bp41R9ubAavi9xNa2qgKtdIVNaMFSs72Lu9kuAc2irQ89l1MuJ4hxdVvkJwyjwwcu+fpdWmJa0OMzgYtunKI1sHutwCqEvcvtq9VwZMUrmPE1wRGfbCcBvSFkPDhRpAG5UoUdGVROKFW2p3mXZeDVLQE0TtOk= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) smtp.mailfrom=bounce+27952+67644+1787277+3901457@groups.io; dmarc=fail header.from= (p=none dis=none) header.from= Received: from mail02.groups.io (mail02.groups.io [66.175.222.108]) by mx.zohomail.com with SMTPS id 1605588611865348.66514812401556; Mon, 16 Nov 2020 20:50:11 -0800 (PST) Return-Path: X-Received: by 127.0.0.2 with SMTP id c3hdYY1788612xN7APpg9Fnp; Mon, 16 Nov 2020 20:50:11 -0800 X-Received: from mga05.intel.com (mga05.intel.com []) by mx.groups.io with SMTP id smtpd.web12.3770.1605588604161105067 for ; Mon, 16 Nov 2020 20:50:06 -0800 IronPort-SDR: QxKnFfQjZuoZjhGONrjlDs+BO9t2icif5eXgGf01faeRNS1x0wCssDn9Rz6Kg6185nCHuD3GJK nrTfAP8LmRWA== X-IronPort-AV: E=McAfee;i="6000,8403,9807"; a="255574588" X-IronPort-AV: E=Sophos;i="5.77,484,1596524400"; d="scan'208";a="255574588" X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False X-Received: from orsmga004.jf.intel.com ([10.7.209.38]) by fmsmga105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 16 Nov 2020 20:50:05 -0800 IronPort-SDR: cCoddN3FemZo6Y7QXTFbrrKlN2P86Yu4MOE5Sp63I56dNKxE2ni1sc1SwP2L2oh64LlkhobOUs 3ZtvG2eaA7+A== X-IronPort-AV: E=Sophos;i="5.77,484,1596524400"; d="scan'208";a="475783062" X-Received: from ecbjorge-mobl1.amr.corp.intel.com ([10.251.18.231]) by orsmga004-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 16 Nov 2020 20:50:05 -0800 From: "Bjorge, Erik C" To: devel@edk2.groups.io Cc: Ashley E Desimone , Nate DeSimone , Puja Pandya , Bret Barkelew , Prince Agyeman Subject: [edk2-devel] [edk2-staging/EdkRepo] [PATCH v2 1/2] EdkRepo: Add cache command Date: Mon, 16 Nov 2020 20:49:44 -0800 Message-Id: <737e00b25148bcf2faede39d38b352dd41843090.1605588263.git.erik.c.bjorge@intel.com> In-Reply-To: References: MIME-Version: 1.0 Precedence: Bulk List-Unsubscribe: Sender: devel@edk2.groups.io List-Id: Mailing-List: list devel@edk2.groups.io; contact devel+owner@edk2.groups.io Reply-To: devel@edk2.groups.io,erik.c.bjorge@intel.com X-Gm-Message-State: PGxOgeTMLGWrhSLN5TQ7jmU4x1787277AA= Content-Transfer-Encoding: quoted-printable DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=groups.io; q=dns/txt; s=20140610; t=1605588611; bh=ZfqWXx4v4qmkJLE2aHmFfsvuNjfiS7duvOioEgm9dIk=; h=Cc:Date:From:Reply-To:Subject:To; b=Dmx207G9PckeldPpagpC8PoNforKJregeHb29E3lbZeh4ZsxW1jfI9TdO447QAj2hMC 13dBb3TckcvqqSJmQk610M/ecPfFOAj1SA9ASb8bWNLBvYFkLyI//80kc0FQikvv2vo3+ IEF1MIEVefmz7Ovr2gHF26qc4WapZO1jUtg= X-ZohoMail-DKIM: pass (identity @groups.io) Content-Type: text/plain; charset="utf-8" Adds a module to add a repo cache and mange it. Also adds a command to manage the repo cache from EdkRepo. No other commands use the functionality at this point. Cc: Ashley E Desimone Cc: Nate DeSimone Cc: Puja Pandya Cc: Bret Barkelew Cc: Prince Agyeman Cc: Erik Bjorge Signed-off-by: Erik Bjorge Reviewed-by: Ashley DeSimone --- edkrepo/commands/arguments/cache_args.py | 19 ++ edkrepo/commands/cache_command.py | 118 ++++++++++++ edkrepo/commands/humble/cache_humble.py | 17 ++ edkrepo/common/common_cache_functions.py | 41 +++++ edkrepo/common/edkrepo_exception.py | 3 + edkrepo/config/config_factory.py | 14 +- edkrepo/config/tool_config.py | 5 +- project_utils/cache.py | 224 +++++++++++++++++++++++ project_utils/project_utils_strings.py | 11 ++ 9 files changed, 448 insertions(+), 4 deletions(-) create mode 100644 edkrepo/commands/arguments/cache_args.py create mode 100644 edkrepo/commands/cache_command.py create mode 100644 edkrepo/commands/humble/cache_humble.py create mode 100644 edkrepo/common/common_cache_functions.py create mode 100644 project_utils/cache.py diff --git a/edkrepo/commands/arguments/cache_args.py b/edkrepo/commands/ar= guments/cache_args.py new file mode 100644 index 0000000..0080536 --- /dev/null +++ b/edkrepo/commands/arguments/cache_args.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# +## @file +# cache_args.py +# +# Copyright (c) 2020, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# + +''' Contains the help and description strings for arguments in the +cache command meta data. +''' +COMMAND_DESCRIPTION =3D ('Manages local caching support for project repos.= The goal of this feature ' + 'is to improve clone performance') +COMMAND_ENABLE_HELP =3D 'Enables caching support on the system.' +COMMAND_DISABLE_HELP =3D 'Disables caching support on the system.' +COMMAND_UPDATE_HELP =3D 'Update the repo cache for all cached projects.' +COMMAND_INFO_HELP =3D 'Display the current cache information.' +COMMAND_PROJECT_HELP =3D 'Project to add to the cache.' diff --git a/edkrepo/commands/cache_command.py b/edkrepo/commands/cache_com= mand.py new file mode 100644 index 0000000..9f0d6e9 --- /dev/null +++ b/edkrepo/commands/cache_command.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# +## @file +# cache_command.py +# +# Copyright (c) 2020, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# + +import edkrepo.commands.arguments.cache_args as arguments +from edkrepo.commands.edkrepo_command import EdkrepoCommand +from edkrepo.commands.edkrepo_command import SourceManifestRepoArgument +from edkrepo.commands.humble.cache_humble import CACHE_ENABLED, CACHE_FETC= H, CACHE_INFO +from edkrepo.commands.humble.cache_humble import CACHE_INFO_LINE, PROJECT_= NOT_FOUND, NO_INSTANCE +from edkrepo.commands.humble.cache_humble import UNABLE_TO_LOAD_MANIFEST, = UNABLE_TO_PARSE_MANIFEST +from edkrepo.common.common_cache_functions import add_missing_cache_repos +from edkrepo.common.common_cache_functions import get_repo_cache_obj +from edkrepo.common.edkrepo_exception import EdkrepoCacheException +from edkrepo.common.workspace_maintenance.manifest_repos_maintenance impor= t find_project_in_all_indices +from edkrepo.config.config_factory import get_workspace_manifest +from edkrepo_manifest_parser.edk_manifest import ManifestXml + + +class CacheCommand(EdkrepoCommand): + def __init__(self): + super().__init__() + + def get_metadata(self): + metadata =3D {} + metadata['name'] =3D 'cache' + metadata['help-text'] =3D arguments.COMMAND_DESCRIPTION + args =3D [] + metadata['arguments'] =3D args + args.append({'name': 'enable', + 'positional': False, + 'required': False, + 'help-text': arguments.COMMAND_ENABLE_HELP}) + args.append({'name': 'disable', + 'positional': False, + 'required': False, + 'help-text': arguments.COMMAND_DISABLE_HELP}) + args.append({'name': 'update', + 'positional': False, + 'required': False, + 'help-text': arguments.COMMAND_UPDATE_HELP}) + args.append({'name': 'info', + 'positional': False, + 'required': False, + 'help-text': arguments.COMMAND_INFO_HELP}) + args.append({'name': 'project', + 'positional': True, + 'required': False, + 'help-text': arguments.COMMAND_PROJECT_HELP}) + args.append(SourceManifestRepoArgument) + return metadata + + def run_command(self, args, config): + # Process enable disable requests + if args.disable: + config['user_cfg_file'].set_caching_state(False) + elif args.enable: + config['user_cfg_file'].set_caching_state(True) + + # Get the current state now that we have processed enable/disable + cache_state =3D config['user_cfg_file'].caching_state + print(CACHE_ENABLED.format(cache_state)) + if not cache_state: + return + + # State is enabled so make sure cache directory exists + cache_obj =3D get_repo_cache_obj(config) + + # Check to see if a manifest was provided and add any missing remo= tes + manifest =3D None + if args.project is not None: + manifest =3D _get_manifest(args.project, config, args.source_m= anifest_repo) + else: + try: + manifest =3D get_workspace_manifest() + except Exception: + pass + + # If manifest is provided attempt to add any remotes that do not e= xist + if manifest is not None: + add_missing_cache_repos(cache_obj, manifest, True) + + # Display all the cache information + if args.info: + print(CACHE_INFO) + info =3D cache_obj.get_cache_info(args.verbose) + for item in info: + print(CACHE_INFO_LINE.format(item.path, item.remote, item.= url)) + + # Do an update if requested + if args.update: + print(CACHE_FETCH) + cache_obj.update_cache(verbose=3DTrue) + + # Close the cache repos + cache_obj.close(args.verbose) + + +def _get_manifest(project, config, source_manifest_repo=3DNone): + try: + manifest_repo, source_cfg, manifest_path =3D find_project_in_all_i= ndices( + project, + config['cfg_file'], + config['user_cfg_file'], + PROJECT_NOT_FOUND.format(project), + NO_INSTANCE.format(project), + source_manifest_repo) + except Exception: + raise EdkrepoCacheException(UNABLE_TO_LOAD_MANIFEST) + try: + manifest =3D ManifestXml(manifest_path) + except Exception: + raise EdkrepoCacheException(UNABLE_TO_PARSE_MANIFEST) + return manifest diff --git a/edkrepo/commands/humble/cache_humble.py b/edkrepo/commands/hum= ble/cache_humble.py new file mode 100644 index 0000000..4f318ac --- /dev/null +++ b/edkrepo/commands/humble/cache_humble.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# +## @file +# cache_humble.py +# +# Copyright (c) 2020, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# + +CACHE_ENABLED =3D 'Caching Enabled: {}' +CACHE_INFO =3D 'Cache Information:' +CACHE_INFO_LINE =3D '+ {}\n {} ({})' +CACHE_FETCH =3D 'Fetching all remotes... (this could take a while)' +PROJECT_NOT_FOUND =3D 'Project {} does not exist' +NO_INSTANCE =3D 'Unable to determine instance to use for {}' +UNABLE_TO_LOAD_MANIFEST =3D 'Unable to load manifest file.' +UNABLE_TO_PARSE_MANIFEST =3D 'Failed to parse manifest file.' diff --git a/edkrepo/common/common_cache_functions.py b/edkrepo/common/comm= on_cache_functions.py new file mode 100644 index 0000000..84bd3ed --- /dev/null +++ b/edkrepo/common/common_cache_functions.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# +## @file +# common_cache_functions.py +# +# Copyright (c) 2020, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# + +import os + +from edkrepo.config.config_factory import get_edkrepo_global_data_directory +from edkrepo.config.tool_config import SUBMODULE_CACHE_REPO_NAME +from project_utils.cache import RepoCache + + +def get_global_cache_directory(config): + if config['user_cfg_file'].caching_state: + return os.path.join(get_edkrepo_global_data_directory(), '.cache') + return None + + +def get_repo_cache_obj(config): + cache_obj =3D None + cache_directory =3D get_global_cache_directory(config) + if cache_directory is not None: + cache_obj =3D RepoCache(cache_directory) + cache_obj.open() + return cache_obj + + +def add_missing_cache_repos(cache_obj, manifest, verbose=3DFalse): + print('Adding and fetching new remotes... (this could take a while)') + for remote in manifest.remotes: + cache_obj.add_repo(url=3Dremote.url, verbose=3Dverbose) + alt_submodules =3D manifest.submodule_alternate_remotes + if alt_submodules: + print('Adding and fetching new submodule remotes... (this could al= so take a while)') + cache_obj.add_repo(name=3DSUBMODULE_CACHE_REPO_NAME, verbose=3Dver= bose) + for alt in alt_submodules: + cache_obj.add_remote(alt.alternate_url, SUBMODULE_CACHE_REPO_N= AME, verbose) diff --git a/edkrepo/common/edkrepo_exception.py b/edkrepo/common/edkrepo_e= xception.py index a56e709..b3f2300 100644 --- a/edkrepo/common/edkrepo_exception.py +++ b/edkrepo/common/edkrepo_exception.py @@ -98,3 +98,6 @@ class EdkrepoGitConfigSetupException(EdkrepoException): def __init__(self, message): super().__init__(message, 131) =20 +class EdkrepoCacheException(EdkrepoException): + def __init__(self, message): + super().__init__(message, 132) diff --git a/edkrepo/config/config_factory.py b/edkrepo/config/config_facto= ry.py index fe69460..3680c0b 100644 --- a/edkrepo/config/config_factory.py +++ b/edkrepo/config/config_factory.py @@ -225,10 +225,20 @@ class GlobalUserConfig(BaseConfig): self.filename =3D os.path.join(get_edkrepo_global_data_directory()= , "edkrepo_user.cfg") self.prop_list =3D [ CfgProp('scm', 'mirror_geo', 'geo', 'none', False), - CfgProp('send-review', 'max-patch-set', 'max_patch_set', '10',= False) - ] + CfgProp('send-review', 'max-patch-set', 'max_patch_set', '10',= False), + CfgProp('caching', 'enable-caching', 'enable_caching_text', 'f= alse', False)] super().__init__(self.filename, get_edkrepo_global_data_directory(= ), False) =20 + @property + def caching_state(self): + return self.enable_caching_text.lower() =3D=3D 'true' + + def set_caching_state(self, enable): + if enable: + self.enable_caching_text =3D 'true' + else: + self.enable_caching_text =3D 'false' + @property def max_patch_set_int(self): try: diff --git a/edkrepo/config/tool_config.py b/edkrepo/config/tool_config.py index eee1326..81f4ddf 100644 --- a/edkrepo/config/tool_config.py +++ b/edkrepo/config/tool_config.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 # ## @file -# tool)config.py +# tool_config.py # # Copyright (c) 2020, Intel Corporation. All rights reserved.
# SPDX-License-Identifier: BSD-2-Clause-Patent # =20 -CI_INDEX_FILE_NAME =3D 'CiIndex.xml' \ No newline at end of file +CI_INDEX_FILE_NAME =3D 'CiIndex.xml' +SUBMODULE_CACHE_REPO_NAME =3D 'submodule-cache' diff --git a/project_utils/cache.py b/project_utils/cache.py new file mode 100644 index 0000000..8efd411 --- /dev/null +++ b/project_utils/cache.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +# +## @file +# cache.py +# +# Copyright (c) 2020, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# +from collections import namedtuple +import os +import shutil + +from git import Repo + +from edkrepo.common.progress_handler import GitProgressHandler +from project_utils.project_utils_strings import CACHE_ADD_REMOTE, CACHE_AD= DING_REPO, CACHE_CHECK_ROOT_DIR +from project_utils.project_utils_strings import CACHE_FAILED_TO_CLOSE, CAC= HE_FAILED_TO_OPEN, CACHE_FETCH_REMOTE +from project_utils.project_utils_strings import CACHE_REMOTE_EXISTS, CACHE= _REMOVE_REPO, CACHE_REPO_EXISTS + +CacheInfo =3D namedtuple('CacheInfo', ['path', 'remote', 'url']) + + +class RepoCache(object): + """ + Provides basic management of a cache repo. + """ + def __init__(self, path): + self._cache_root_path =3D path + self._repos =3D {} + + def _create_name(self, url_or_name): + """ + Used to create consistent repo and remote names + """ + dir_name =3D url_or_name.split('/')[-1] + if not dir_name.endswith('.git'): + dir_name +=3D '.git' + return dir_name + + def _get_repo_path(self, dir_name): + return os.path.join(self._cache_root_path, dir_name) + + def _get_repo(self, dir_name): + """ + Returns the git repo object for the cache repo. + + Raises FileNotFoundError if the cache directory does not exist. + Raises IOError if the repo cannot be opened + """ + repo_path =3D self._get_repo_path(dir_name) + if not os.path.isdir(repo_path): + raise FileNotFoundError + try: + repo =3D Repo(repo_path) + except Exception: + raise IOError + return repo + + def _get_cache_dirs(self): + if not os.path.isdir(self._cache_root_path): + raise FileNotFoundError + return [x for x in os.listdir(self._cache_root_path) if os.path.is= dir(self._get_repo_path(x))] + + def _add_and_fetch_remote(self, repo, remote_name, url, verbose=3DFals= e): + if verbose: + print(CACHE_ADD_REMOTE.format(remote_name, url)) + repo.create_remote(remote_name, url) + if verbose: + print(CACHE_FETCH_REMOTE.format(remote_name, url)) + repo.remotes[remote_name].fetch(progress=3DGitProgressHandler()) + + def open(self, verbose=3DFalse): + """ + Opens all cache repos. + + Raises FileNotFoundError if the cache directory does not exist. + """ + if not self._repos: + if not os.path.isdir(self._cache_root_path): + if verbose: + print(CACHE_CHECK_ROOT_DIR.format(self._cache_root_pat= h)) + os.makedirs(self._cache_root_path) + + for dir_name in self._get_cache_dirs(): + try: + self._repos[dir_name] =3D self._get_repo(dir_name) + except Exception: + if verbose: + print(CACHE_FAILED_TO_OPEN.format(dir_name)) + + def close(self, verbose=3DFalse): + """ + Closes all cache repos. + """ + for dir_name in self._repos: + try: + self._repos[dir_name].close() + except Exception: + if verbose: + print(CACHE_FAILED_TO_CLOSE.format(dir_name)) + self._repos =3D {} + + def get_cache_path(self, url_or_name): + dir_name =3D self._create_name(url_or_name) + if dir_name not in self._repos: + return None + return self._get_repo_path(dir_name) + + def get_cache_info(self, verbose=3DFalse): + """ + Returns a list of remotes currently configured in the cache. + + Raises FileNotFoundError if the cache repo is not open. + """ + ret_val =3D [] + for dir_name in self._repos: + for remote in self._repos[dir_name].remotes: + ret_val.append(CacheInfo(self._get_repo_path(dir_name), re= mote.name, remote.url)) + return ret_val + + def delete_cache_root(self, verbose=3DFalse): + """ + Deletes the cache root directory and all caches. + """ + if os.path.isdir(self._cache_root_path): + if self._repos: + self.close() + shutil.rmtree(self._cache_root_path, ignore_errors=3DTrue) + + def add_repo(self, url=3DNone, name=3DNone, verbose=3DFalse): + """ + Adds a repo to the cache if it does not already exist. + + """ + remote_name =3D None + if url is None and name is None: + raise ValueError + elif name is not None: + dir_name =3D self._create_name(name) + else: + dir_name =3D self._create_name(url) + if url is not None: + remote_name =3D self._create_name(url) + repo_path =3D self._get_repo_path(dir_name) + + if dir_name in self._repos: + if verbose: + print(CACHE_REPO_EXISTS.format(dir_name)) + else: + if verbose: + print(CACHE_ADDING_REPO.format(dir_name)) + os.makedirs(repo_path) + self._repos[dir_name] =3D Repo.init(repo_path, bare=3DTrue) + + if remote_name is not None and remote_name not in self._repos[dir_= name].remotes: + self._add_and_fetch_remote(self._get_repo(dir_name), remote_na= me, url) + return dir_name + + def remove_repo(self, url=3DNone, name=3DNone, verbose=3DFalse): + """ + Removes a remote from the cache repo if it exists + + Raises FileNotFoundError if the cache repo is not open. + """ + if url is None and name is None: + raise ValueError + elif name is not None: + dir_name =3D self._create_name(name) + else: + dir_name =3D self._create_name(url) + if dir_name not in self._repos: + return + if verbose: + print(CACHE_REMOVE_REPO.format(dir_name)) + self._repos.pop(dir_name).close() + shutil.rmtree(os.path.join(self._cache_root_path, dir_name), ignor= e_errors=3DTrue) + + def add_remote(self, url, name, verbose=3DFalse): + remote_name =3D self._create_name(url) + dir_name =3D self._create_name(name) + if dir_name not in self._repos: + raise ValueError + repo =3D self._get_repo(dir_name) + if remote_name in repo.remotes: + if verbose: + print(CACHE_REMOTE_EXISTS.format(remote_name)) + return + self._add_and_fetch_remote(repo, remote_name, url, verbose) + + def remove_remote(self, url, name, verbose=3DFalse): + remote_name =3D self._create_name(url) + dir_name =3D self._create_name(name) + if dir_name not in self._repos: + raise ValueError + repo =3D self._get_repo(dir_name) + if remote_name not in repo.remotes: + raise IndexError + repo.remove_remote(repo.remotes[remote_name]) + + def update_cache(self, url_or_name=3DNone, verbose=3DFalse): + if not self._repos: + raise FileNotFoundError + repo_dirs =3D self._repos.keys() + + if url_or_name is not None: + dir_name =3D self._create_name(url_or_name) + if dir_name in self._repos: + repo_dirs =3D [dir_name] + else: + return + + for dir_name in repo_dirs: + try: + repo =3D self._get_repo(dir_name) + except Exception: + print(CACHE_FAILED_TO_OPEN.format(dir_name)) + continue + for remote in repo.remotes: + if verbose: + print(CACHE_FETCH_REMOTE.format(dir_name, remote.url)) + remote.fetch(progress=3DGitProgressHandler()) + + def clean_cache(self, verbose=3DFalse): + raise NotImplementedError diff --git a/project_utils/project_utils_strings.py b/project_utils/project= _utils_strings.py index 33c22d2..1547978 100644 --- a/project_utils/project_utils_strings.py +++ b/project_utils/project_utils_strings.py @@ -22,3 +22,14 @@ SUBMOD_DEINIT_PATH =3D 'Submodule deinit: {}' SUBMOD_SYNC_PATH =3D 'Submodule sync: {}' SUBMOD_UPDATE_PATH =3D 'Submodule update: {}' SUBMOD_EXCEPTION =3D '- Exception: {}' + +# Caching support strings +CACHE_ADD_REMOTE =3D '+ Adding remote {} ({})' +CACHE_FETCH_REMOTE =3D '+ Fetching data for {} ({})' +CACHE_CHECK_ROOT_DIR =3D '+ Creating cache root directory: {}' +CACHE_FAILED_TO_OPEN =3D '- Failed to open cache: {}' +CACHE_FAILED_TO_CLOSE =3D '- Failed to close cache: {}' +CACHE_REPO_EXISTS =3D '- Repo {} already exists.' +CACHE_ADDING_REPO =3D '+ Adding cache repo {}' +CACHE_REMOVE_REPO =3D '- Removing cache repo: {}' +CACHE_REMOTE_EXISTS =3D '- Remote {} already exists.' --=20 2.21.0.windows.1 -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#67644): https://edk2.groups.io/g/devel/message/67644 Mute This Topic: https://groups.io/mt/78310534/1787277 Group Owner: devel+owner@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org] -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D- From nobody Thu May 16 09:06:59 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) client-ip=66.175.222.108; envelope-from=bounce+27952+67645+1787277+3901457@groups.io; helo=mail02.groups.io; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) smtp.mailfrom=bounce+27952+67645+1787277+3901457@groups.io; dmarc=fail(p=none dis=none) header.from=intel.com ARC-Seal: i=1; a=rsa-sha256; t=1605588609; cv=none; d=zohomail.com; s=zohoarc; b=bcsRt7CQUv4sp8EqD13FXgGIIDj9AiPiEpKbdCimpTP1Xr2bRw/prdUwDTg9TVrDy1QrcawWnPdrHhmxPn82L7SD/0qHTMH+H6r2oDNFFAYd10ymeerqhBNpq6XrGVhLLRqO8FbfNB/KfkaoYWckWSRHk1mueEV0vv228klKJeg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1605588609; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Id:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:References:Sender:Subject:To; bh=G+sz/dJ+3+zIPyuenqu0Vp3Mbh/thhd2WCZEnG0+MSM=; b=g6OnGxtOugSDz8WPjUSuC2PpnCLJTEICSUDfBsWhQdiBWWSxaVlm3J8E+yOFWz5LJ0Htny/PowxbIVBuZ9JNqOvITbPip5C6VaSmhZfW0vHEJy1paUxykqGwCfsWvz03Ppt2MmbHUADxERCMUIehvmNFsNwnOq/Tn6covLu46Xk= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) smtp.mailfrom=bounce+27952+67645+1787277+3901457@groups.io; dmarc=fail header.from= (p=none dis=none) header.from= Received: from mail02.groups.io (mail02.groups.io [66.175.222.108]) by mx.zohomail.com with SMTPS id 1605588609384365.12636200477993; Mon, 16 Nov 2020 20:50:09 -0800 (PST) Return-Path: X-Received: by 127.0.0.2 with SMTP id H5BqYY1788612xdfLj2mhb2z; Mon, 16 Nov 2020 20:50:09 -0800 X-Received: from mga05.intel.com (mga05.intel.com []) by mx.groups.io with SMTP id smtpd.web12.3770.1605588604161105067 for ; Mon, 16 Nov 2020 20:50:08 -0800 IronPort-SDR: P1FDKe8LfWaupNy6l6jga9v8Lkm/I32BtkarasSVJ+AZq3lxUnxShsrOt6+JmKT0JlT0arv8XC /+CnqJvTvpSA== X-IronPort-AV: E=McAfee;i="6000,8403,9807"; a="255574605" X-IronPort-AV: E=Sophos;i="5.77,484,1596524400"; d="scan'208";a="255574605" X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False X-Received: from orsmga004.jf.intel.com ([10.7.209.38]) by fmsmga105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 16 Nov 2020 20:50:08 -0800 IronPort-SDR: GB4kzwTtJbKTAQh6hLtRo+GgT/Ogeq2SjbyfqQPVvZ8S4VurwG/661JYrZKS/FOvfkJ6OuGfE2 Lbbf7Lunn+MA== X-IronPort-AV: E=Sophos;i="5.77,484,1596524400"; d="scan'208";a="475783070" X-Received: from ecbjorge-mobl1.amr.corp.intel.com ([10.251.18.231]) by orsmga004-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 16 Nov 2020 20:50:08 -0800 From: "Bjorge, Erik C" To: devel@edk2.groups.io Cc: Ashley E Desimone , Nate DeSimone , Puja Pandya , Bret Barkelew , Prince Agyeman Subject: [edk2-devel] [edk2-staging/EdkRepo] [PATCH v2 2/2] EdkRepo: Enable use of repo cache support. Date: Mon, 16 Nov 2020 20:49:45 -0800 Message-Id: In-Reply-To: References: MIME-Version: 1.0 Precedence: Bulk List-Unsubscribe: Sender: devel@edk2.groups.io List-Id: Mailing-List: list devel@edk2.groups.io; contact devel+owner@edk2.groups.io Reply-To: devel@edk2.groups.io,erik.c.bjorge@intel.com X-Gm-Message-State: jUqCLwEw3pzWjXgAeO6tzvdVx1787277AA= Content-Transfer-Encoding: quoted-printable DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=groups.io; q=dns/txt; s=20140610; t=1605588609; bh=gFK5FKpAfXrRM0cypy5/ai1OJQTIMe/CJMzHbeSvM+s=; h=Cc:Date:From:Reply-To:Subject:To; b=PA911W21tn9jHqifHg7xHy/SdT0rgPRKxMH9uJpK/RfwukqinAvvgQ/nPHnEuXciLsu TjwM72i7H3wE5I27KVKUDBwKyP7gywrls3bhKXI7Jmqy4Pq/PBtg/wgs9DSY7qsEzage1 qEovw1R5C1uGdToTswEko34AH7VKk5sxS1k= X-ZohoMail-DKIM: pass (identity @groups.io) Content-Type: text/plain; charset="utf-8" This changes enables the local repo cache to be used when cloning and syncing changes. The repo cache applies to submodules as well. Cc: Ashley E Desimone Cc: Nate DeSimone Cc: Puja Pandya Cc: Bret Barkelew Cc: Prince Agyeman Cc: Erik Bjorge Signed-off-by: Erik Bjorge Reviewed-by: Ashley DeSimone --- edkrepo/commands/checkout_command.py | 3 ++- edkrepo/commands/checkout_pin_command.py | 8 +++++++- edkrepo/commands/clone_command.py | 15 ++++++++++++-- edkrepo/commands/sync_command.py | 12 +++++++++--- edkrepo/common/common_repo_functions.py | 25 ++++++++++++++++++++---- project_utils/submodule.py | 13 ++++++++---- 6 files changed, 61 insertions(+), 15 deletions(-) diff --git a/edkrepo/commands/checkout_command.py b/edkrepo/commands/checko= ut_command.py index 0169f30..2ce26c0 100644 --- a/edkrepo/commands/checkout_command.py +++ b/edkrepo/commands/checkout_command.py @@ -16,6 +16,7 @@ import os from edkrepo.commands.edkrepo_command import EdkrepoCommand, OverrideArgum= ent import edkrepo.commands.arguments.checkout_args as arguments import edkrepo.commands.humble.checkout_humble as humble +from edkrepo.common.common_cache_functions import get_repo_cache_obj from edkrepo.common.common_repo_functions import checkout, combination_is_= in_manifest from edkrepo.common.edkrepo_exception import EdkrepoInvalidParametersExcep= tion from edkrepo.config.config_factory import get_workspace_manifest @@ -42,6 +43,6 @@ class CheckoutCommand(EdkrepoCommand): =20 def run_command(self, args, config): if combination_is_in_manifest(args.Combination, get_workspace_mani= fest()): - checkout(args.Combination, args.verbose, args.override) + checkout(args.Combination, args.verbose, args.override, get_re= po_cache_obj(config)) else: raise EdkrepoInvalidParametersException(humble.NO_COMBO.format= (args.Combination)) diff --git a/edkrepo/commands/checkout_pin_command.py b/edkrepo/commands/ch= eckout_pin_command.py index 1c58113..0ad1b48 100644 --- a/edkrepo/commands/checkout_pin_command.py +++ b/edkrepo/commands/checkout_pin_command.py @@ -14,6 +14,7 @@ from git import Repo from edkrepo.commands.edkrepo_command import EdkrepoCommand, OverrideArgum= ent, SourceManifestRepoArgument import edkrepo.commands.arguments.checkout_pin_args as arguments import edkrepo.commands.humble.checkout_pin_humble as humble +from edkrepo.common.common_cache_functions import get_repo_cache_obj from edkrepo.common.common_repo_functions import sparse_checkout_enabled, = reset_sparse_checkout, sparse_checkout from edkrepo.common.common_repo_functions import check_dirty_repos, checko= ut_repos, combinations_in_manifest from edkrepo.common.humble import SPARSE_CHECKOUT, SPARSE_RESET, SUBMODULE= _DEINIT_FAILED @@ -21,6 +22,7 @@ from edkrepo.common.edkrepo_exception import EdkrepoInval= idParametersException, from edkrepo.common.workspace_maintenance.manifest_repos_maintenance impor= t list_available_manifest_repos from edkrepo.common.workspace_maintenance.manifest_repos_maintenance impor= t find_source_manifest_repo from edkrepo.config.config_factory import get_workspace_path, get_workspac= e_manifest +from edkrepo.config.tool_config import SUBMODULE_CACHE_REPO_NAME from edkrepo_manifest_parser.edk_manifest import ManifestXml from project_utils.submodule import deinit_full, maintain_submodules =20 @@ -82,7 +84,11 @@ class CheckoutPinCommand(EdkrepoCommand): checkout_repos(args.verbose, args.override, pin_repo_sources, = workspace_path, manifest) manifest.write_current_combo(humble.PIN_COMBO.format(args.pinf= ile)) finally: - maintain_submodules(workspace_path, pin, submodule_combo, args= .verbose) + cache_path =3D None + cache_obj =3D get_repo_cache_obj(config) + if cache_obj is not None: + cache_path =3D cache_obj.get_cache_path(SUBMODULE_CACHE_RE= PO_NAME) + maintain_submodules(workspace_path, pin, submodule_combo, args= .verbose, cache_path) if sparse_enabled: print(SPARSE_CHECKOUT) sparse_checkout(workspace_path, pin_repo_sources, manifest) diff --git a/edkrepo/commands/clone_command.py b/edkrepo/commands/clone_com= mand.py index 8769102..56c15c9 100644 --- a/edkrepo/commands/clone_command.py +++ b/edkrepo/commands/clone_command.py @@ -14,6 +14,8 @@ import sys from edkrepo.commands.edkrepo_command import EdkrepoCommand from edkrepo.commands.edkrepo_command import SubmoduleSkipArgument, Source= ManifestRepoArgument import edkrepo.commands.arguments.clone_args as arguments +from edkrepo.common.common_cache_functions import get_repo_cache_obj +from edkrepo.common.common_cache_functions import add_missing_cache_repos from edkrepo.common.common_repo_functions import clone_repos, sparse_check= out, verify_single_manifest from edkrepo.common.common_repo_functions import update_editor_config, com= binations_in_manifest from edkrepo.common.common_repo_functions import write_included_config, wr= ite_conditional_include @@ -28,6 +30,7 @@ from edkrepo.common.workspace_maintenance.manifest_repos_= maintenance import list from edkrepo.common.workspace_maintenance.humble.manifest_repos_maintenanc= e_humble import PROJ_NOT_IN_REPO, SOURCE_MANIFEST_REPO_NOT_FOUND from edkrepo_manifest_parser.edk_manifest import CiIndexXml, ManifestXml from project_utils.submodule import maintain_submodules +from edkrepo.config.tool_config import SUBMODULE_CACHE_REPO_NAME =20 =20 class CloneCommand(EdkrepoCommand): @@ -151,11 +154,19 @@ class CloneCommand(EdkrepoCommand): # Set up submodule alt url config settings prior to cloning any re= pos submodule_included_configs =3D write_included_config(manifest.remo= tes, manifest.submodule_alternate_remotes, local_manifest_dir) write_conditional_include(workspace_dir, repo_sources_to_clone, su= bmodule_included_configs) - clone_repos(args, workspace_dir, repo_sources_to_clone, project_cl= ient_side_hooks, config, manifest) + + # Determine if caching is going to be used and then clone + cache_obj =3D get_repo_cache_obj(config) + if cache_obj is not None: + add_missing_cache_repos(cache_obj, manifest, args.verbose) + clone_repos(args, workspace_dir, repo_sources_to_clone, project_cl= ient_side_hooks, config, manifest, cache_obj) =20 # Init submodules if not args.skip_submodule: - maintain_submodules(workspace_dir, manifest, combo_name, args.= verbose) + cache_path =3D None + if cache_obj is not None: + cache_path =3D cache_obj.get_cache_path(SUBMODULE_CACHE_RE= PO_NAME) + maintain_submodules(workspace_dir, manifest, combo_name, args.= verbose, cache_path) =20 # Perform a sparse checkout if requested. use_sparse =3D args.sparse diff --git a/edkrepo/commands/sync_command.py b/edkrepo/commands/sync_comma= nd.py index c4ee330..ff48f50 100644 --- a/edkrepo/commands/sync_command.py +++ b/edkrepo/commands/sync_command.py @@ -32,6 +32,7 @@ from edkrepo.common.humble import MIRROR_BEHIND_PRIMARY_R= EPO, SYNC_NEEDS_REBASE, from edkrepo.common.humble import SYNC_BRANCH_CHANGE_ON_LOCAL, SYNC_INCOMP= ATIBLE_COMBO from edkrepo.common.humble import SYNC_REBASE_CALC_FAIL from edkrepo.common.pathfix import get_actual_path, expanduser +from edkrepo.common.common_cache_functions import get_repo_cache_obj from edkrepo.common.common_repo_functions import clone_repos, sparse_check= out_enabled from edkrepo.common.common_repo_functions import reset_sparse_checkout, sp= arse_checkout, verify_single_manifest from edkrepo.common.common_repo_functions import checkout_repos, check_dir= ty_repos @@ -47,6 +48,7 @@ from edkrepo.common.workspace_maintenance.manifest_repos_= maintenance import list from edkrepo.common.ui_functions import init_color_console from edkrepo.config.config_factory import get_workspace_path, get_workspac= e_manifest, get_edkrepo_global_data_directory from edkrepo.config.config_factory import get_workspace_manifest_file +from edkrepo.config.tool_config import SUBMODULE_CACHE_REPO_NAME from edkrepo_manifest_parser.edk_manifest import CiIndexXml, ManifestXml from project_utils.submodule import deinit_submodules, maintain_submodules =20 @@ -102,7 +104,7 @@ class SyncCommand(EdkrepoCommand): if not args.update_local_manifest: self.__check_for_new_manifest(args, config, initial_manifest, = workspace_path, global_manifest_directory) check_dirty_repos(initial_manifest, workspace_path) - + # Determine if sparse checkout needs to be disabled for this opera= tion sparse_settings =3D initial_manifest.sparse_settings sparse_enabled =3D sparse_checkout_enabled(workspace_path, initial= _sources) @@ -116,7 +118,7 @@ class SyncCommand(EdkrepoCommand): reset_sparse_checkout(workspace_path, initial_sources) =20 # Get the latest manifest if requested - if args.update_local_manifest: # NOTE: hyphens in arg name replac= ed with underscores due to argparse + if args.update_local_manifest: # NOTE: hyphens in arg name replac= ed with underscores due to argparse self.__update_local_manifest(args, config, initial_manifest, w= orkspace_path, global_manifest_directory) manifest =3D get_workspace_manifest() if args.update_local_manifest: @@ -212,7 +214,11 @@ class SyncCommand(EdkrepoCommand): =20 # Initialize submodules if not args.skip_submodule: - maintain_submodules(workspace_path, manifest, current_combo, a= rgs.verbose) + cache_path =3D None + cache_obj =3D get_repo_cache_obj(config) + if cache_obj is not None: + cache_path =3D cache_obj.get_cache_path(SUBMODULE_CACHE_RE= PO_NAME) + maintain_submodules(workspace_path, manifest, current_combo, a= rgs.verbose, cache_path) =20 # Restore sparse checkout state if sparse_enabled: diff --git a/edkrepo/common/common_repo_functions.py b/edkrepo/common/commo= n_repo_functions.py index 2277c1e..336661a 100644 --- a/edkrepo/common/common_repo_functions.py +++ b/edkrepo/common/common_repo_functions.py @@ -56,6 +56,7 @@ from project_utils.sparse import BuildInfo, process_spars= e_checkout from edkrepo.config.config_factory import get_workspace_path from edkrepo.config.config_factory import get_workspace_manifest from edkrepo.config.tool_config import CI_INDEX_FILE_NAME +from edkrepo.config.tool_config import SUBMODULE_CACHE_REPO_NAME from edkrepo.common.edkrepo_exception import EdkrepoInvalidParametersExcep= tion from edkrepo_manifest_parser.edk_manifest import CiIndexXml, ManifestXml from edkrepo.common.edkrepo_exception import EdkrepoNotFoundException, Edk= repoGitException, EdkrepoWarningException @@ -75,12 +76,25 @@ CLEAR_LINE =3D '\x1b[K' DEFAULT_REMOTE_NAME =3D 'origin' PRIMARY_REMOTE_NAME =3D 'primary' =20 -def clone_repos(args, workspace_dir, repos_to_clone, project_client_side_h= ooks, config, manifest): + +def clone_repos(args, workspace_dir, repos_to_clone, project_client_side_h= ooks, config, manifest, cache_obj=3DNone): for repo_to_clone in repos_to_clone: local_repo_path =3D os.path.join(workspace_dir, repo_to_clone.root) local_repo_url =3D repo_to_clone.remote_url + cache_path =3D None + if cache_obj is not None: + cache_path =3D cache_obj.get_cache_path(local_repo_url) print("Cloning from: " + str(local_repo_url)) - repo =3D Repo.clone_from(local_repo_url, local_repo_path, progress= =3DGitProgressHandler(), no_checkout=3DTrue) + if cache_path is not None: + print('+ Using cache at {}'.format(cache_path)) + repo =3D Repo.clone_from(local_repo_url, local_repo_path, + progress=3DGitProgressHandler(), + reference_if_able=3Dcache_path, + no_checkout=3DTrue) + else: + repo =3D Repo.clone_from(local_repo_url, local_repo_path, + progress=3DGitProgressHandler(), + no_checkout=3DTrue) # Fetch notes repo.remotes.origin.fetch("refs/notes/*:refs/notes/*") =20 @@ -427,7 +441,7 @@ def combination_is_in_manifest(combination, manifest): return combination in combination_names =20 =20 -def checkout(combination, verbose=3DFalse, override=3DFalse, log=3DNone): +def checkout(combination, verbose=3DFalse, override=3DFalse, log=3DNone, c= ache_obj=3DNone): workspace_path =3D get_workspace_path() manifest =3D get_workspace_manifest() =20 @@ -493,7 +507,10 @@ def checkout(combination, verbose=3DFalse, override=3D= False, log=3DNone): # Return to the initial combo, since there was an issue with cheki= ng out the selected combo checkout_repos(verbose, override, initial_repo_sources, workspace_= path, manifest) finally: - maintain_submodules(workspace_path, manifest, submodule_combo, ver= bose) + cache_path =3D None + if cache_obj is not None: + cache_path =3D cache_obj.get_cache_path(SUBMODULE_CACHE_REPO_N= AME) + maintain_submodules(workspace_path, manifest, submodule_combo, ver= bose, cache_path) if sparse_enabled or sparse_diff: print(SPARSE_CHECKOUT) sparse_checkout(workspace_path, current_repos, manifest) diff --git a/project_utils/submodule.py b/project_utils/submodule.py index 3d1b620..f735125 100644 --- a/project_utils/submodule.py +++ b/project_utils/submodule.py @@ -61,7 +61,7 @@ def _deinit(repo, submodules=3DNone, verbose=3DFalse): return =20 =20 -def _update(repo, submodules=3DNone, verbose=3DFalse, recursive=3DFalse): +def _update(repo, submodules=3DNone, verbose=3DFalse, recursive=3DFalse, c= ache_path=3DNone): """ Performs the update of submodules. This includes the sync and update = operations. =20 @@ -82,6 +82,8 @@ def _update(repo, submodules=3DNone, verbose=3DFalse, rec= ursive=3DFalse): cmd =3D ['git', 'submodule', 'update', '--init'] if recursive: cmd.append('--recursive') + if cache_path is not None: + cmd.extend(['--reference', cache_path]) output_data =3D repo.git.execute(cmd, with_extended_output=3DTrue,= with_stdout=3DTrue) display_git_output(output_data, verbose) else: @@ -99,6 +101,8 @@ def _update(repo, submodules=3DNone, verbose=3DFalse, re= cursive=3DFalse): cmd =3D ['git', 'submodule', 'update', '--init'] if sub.recursive: cmd.append('--recursive') + if cache_path is not None: + cmd.extend(['--reference', cache_path]) cmd.extend(['--', sub.path]) output_data =3D repo.git.execute(cmd, with_extended_output=3DT= rue, with_stdout=3DTrue) display_git_output(output_data, verbose) @@ -269,7 +273,7 @@ def deinit_submodules(workspace, start_manifest, start_= combo, _deinit(repo, deinit_list, verbose) =20 =20 -def maintain_submodules(workspace, manifest, combo_name, verbose=3DFalse): +def maintain_submodules(workspace, manifest, combo_name, verbose=3DFalse, = cache_path=3DNone): """ Updates the submodules for a specific repo. =20 @@ -277,6 +281,7 @@ def maintain_submodules(workspace, manifest, combo_name= , verbose=3DFalse): manifest - The manifest parser object for the project. combo_name - The combination name to use for submodule maintenance. verbose - Enable verbose messages. + cache_path - Path to the submodule cache repo. A value of None in= dicates that no cache repo exists. """ # Process each repo that may have submodules enabled print(strings.SUBMOD_INIT_UPDATE) @@ -303,9 +308,9 @@ def maintain_submodules(workspace, manifest, combo_name= , verbose=3DFalse): =20 # Perform sync/update if len(repo_subs) =3D=3D 0: - _update(repo, None, verbose) + _update(repo, None, verbose, cache_path=3Dcache_path) else: - _update(repo, repo_subs, verbose) + _update(repo, repo_subs, verbose, cache_path=3Dcache_path) =20 =20 if __name__ =3D=3D '__main__': --=20 2.21.0.windows.1 -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#67645): https://edk2.groups.io/g/devel/message/67645 Mute This Topic: https://groups.io/mt/78310536/1787277 Group Owner: devel+owner@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org] -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-