自定义命令:清理旧的配方和包修订版本

注意

这主要是一个示例命令。内置的 conan remove *#!latest 语法,表示“除最新版本外的所有版本”,可能已经足够满足此用例,而无需此自定义命令。

警告

使用此命令需要 Conan 2.21.0 或更高版本。

请首先克隆源代码以重新创建此项目。您可以在 GitHub 的 examples2 仓库中找到它们。

$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/examples/extensions/commands/clean

在此示例中,我们将了解如何创建/使用自定义命令:conan clean。它会从本地缓存或远程仓库中删除每个配方及其包修订版本,但保留最新的配方版本中的最新包修订版本。

注意

为了更好地理解此示例,强烈建议您先阅读 自定义命令参考

定位命令

将命令文件 cmd_clean.py 复制到您的 [YOUR_CONAN_HOME]/extensions/commands/ 文件夹(如果不存在则创建)。如果您不知道 [YOUR_CONAN_HOME] 的位置,可以运行 conan config home 进行查看。

运行它

现在,您应该能在命令提示符中看到新命令。

$ conan -h
...
Custom commands
clean        Deletes (from local cache or remotes) all recipe and package revisions but the
               latest package revision from the latest recipe revision.

$ conan clean -h
usage: conan clean [-h] [-r REMOTE] [--force]

Deletes (from local cache or remotes) all recipe and package revisions but
the latest package revision from the latest recipe revision.

optional arguments:
  -h, --help            show this help message and exit
  -r REMOTE, --remote REMOTE
                        Will remove from the specified remote
  --force               Remove without requesting a confirmation

最后,如果您执行 conan clean

$ conan clean
Found 4 pkg/version recipes matching */* in local cache
Do you want to remove all the recipes revisions and their packages ones, except the latest package revision from the latest recipe one? (yes/no): yes
Keeping recipe revision: other/1.0#31da245c3399e4124e39bd4f77b5261f and its latest package revisions [Local cache]
Removed package revision: other/1.0#31da245c3399e4124e39bd4f77b5261f:da39a3ee5e6b4b0d3255bfef95601890afd80709#a16985deb2e1aa73a8480faad22b722c [Local cache]
Removed recipe revision: other/1.0#721995a35b1a8d840ce634ea1ac71161 and all its package revisions [Local cache]
Keeping recipe revision: hello/1.0#9a77cdcff3a539b5b077dd811b2ae3b0 and its latest package revisions [Local cache]
Removed package revision: hello/1.0#9a77cdcff3a539b5b077dd811b2ae3b0:da39a3ee5e6b4b0d3255bfef95601890afd80709#cee90a74944125e7e9b4f74210bfec3f [Local cache]
Removed package revision: hello/1.0#9a77cdcff3a539b5b077dd811b2ae3b0:da39a3ee5e6b4b0d3255bfef95601890afd80709#7cddd50952de9935d6c3b5b676a34c48 [Local cache]
Keeping recipe revision: libcxx/0.1#abcdef1234567890abcdef1234567890 and its latest package revisions [Local cache]

如果再次运行,应该不会发生任何事情。

$ conan clean
Do you want to remove all the recipes revisions and their packages ones, except the latest package revision from the latest recipe one? (yes/no): yes
Keeping recipe revision: other/1.0#31da245c3399e4124e39bd4f77b5261f and its latest package revisions [Local cache]
Keeping recipe revision: hello/1.0#9a77cdcff3a539b5b077dd811b2ae3b0 and its latest package revisions [Local cache]
Keeping recipe revision: libcxx/0.1#abcdef1234567890abcdef1234567890 and its latest package revisions [Local cache]

代码解析

命令 conan clean 具有以下代码:

cmd_clean.py
from conan.api.conan_api import ConanAPI
from conan.api.model import PackagesList, ListPattern
from conan.api.input import UserInput
from conan.api.output import ConanOutput, Color
from conan.cli.command import OnceArgument, conan_command

recipe_color = Color.BRIGHT_BLUE
removed_color = Color.BRIGHT_YELLOW

@conan_command(group="Custom commands")
def clean(conan_api: ConanAPI, parser, *args):
    """
    Deletes (from local cache or remotes) all recipe and package revisions but
    the latest package revision from the latest recipe revision.
    """
    parser.add_argument('-r', '--remote', action=OnceArgument,
                        help='Will remove from the specified remote')
    parser.add_argument('--force', default=False, action='store_true',
                        help='Remove without requesting a confirmation')
    args = parser.parse_args(*args)

    def confirmation(message):
        return args.force or ui.request_boolean(message)

    ui = UserInput(non_interactive=False)
    out = ConanOutput()
    remote = conan_api.remotes.get(args.remote) if args.remote else None
    output_remote = remote or "Local cache"

    # List all recipes revisions and all their packages revisions as well
    pkg_list = conan_api.list.select(ListPattern("*/*#*:*#*", rrev=None, prev=None), remote=remote)
    if pkg_list and not confirmation("Do you want to remove all the recipes revisions and their packages ones, "
                                    "except the latest package revision from the latest recipe one?"):
        out.writeln("Aborted")
        return

    # Split the package list into based on their recipe reference
    for sub_pkg_list in pkg_list.split():
        latest = max(sub_pkg_list.items(), key=lambda item: item[0])[0]
        out.writeln(f"Keeping recipe revision: {latest.repr_notime()} "
                    f"and its latest package revisions [{output_remote}]", fg=recipe_color)
        for rref, packages in sub_pkg_list.items():
            # For the latest recipe revision, keep the latest package revision only
            if latest == rref:
                # Get the latest package timestamp for each package_id
                latest_pref_list = [max([p for p in packages if p.package_id == pkg_id], key=lambda p: p.timestamp)
                                    for pkg_id in {p.package_id for p in packages}]
                for pref in packages:
                    if pref not in latest_pref_list:
                        conan_api.remove.package(pref, remote=remote)
                        out.writeln(f"Removed package revision: {pref.repr_notime()} [{output_remote}]", fg=removed_color)
            else:
                # Otherwise, remove all outdated recipe revisions and their packages
                conan_api.remove.recipe(rref, remote=remote)
                out.writeln(f"Removed recipe revision: {rref.repr_notime()} "
                            f"and all its package revisions [{output_remote}]", fg=removed_color)

让我们分析最重要的部分。

解析器

参数 parser 是 Python 命令行解析库 argparse.ArgumentParser 的一个实例,如果您想了解更多关于其 API 的信息,请访问 其官方网站

用户输出

ConanOutput():用于管理用户输出的类。在此示例中,我们仅使用 out.writeln(message, fg=None, bg=None),其中 fg 是字体前景色,bg 是字体背景色。此外,您还拥有一些预定义的方法,如 out.info()out.success()out.error() 等。

Conan 公共 API

警告

此功能是实验性的,可能会发生破坏性更改。有关更多信息,请参阅 Conan 稳定性 部分。

此示例中最重要的一部分是通过 conan_api 参数使用 Conan API。以下是一些在此自定义命令中使用的示例:

conan_api.remotes.get(args.remote)
conan_api.list.select(ListPattern("*/*#*:*#*", rrev=None, prev=None), remote=remote)
conan_api.remove.recipe(rrev, remote=remote)
conan_api.remove.package(prev, remote=remote)
  • conan_api.remotes.get(...)[RemotesAPI] 根据远程名称返回一个 RemoteRegistry。

  • conan_api.list.select(...)[ListAPI] 返回一个包含所有与给定模式匹配的配方的列表。

  • conan_api.remove.recipe(...)[RemoveAPI] 删除给定的配方修订版本及其所有包修订版本。

  • conan_api.remove.package(...)[RemoveAPI] 删除给定的包修订版本。

此外,以下几行值得特别关注:

for sub_pkg_list in pkg_list.split():
    latest = max(sub_pkg_list.items(), key=lambda item: item[0])[0]

...

latest_pref_list = [max([p for p in packages if p.package_id == pkg_id], key=lambda p: p.timestamp)
                                for pkg_id in {p.package_id for p in packages}]

基本上,pkg_list.split() 为相同的配方引用返回一个列表。然后,sub_pkg_list.items() 返回一个元组列表 (Recipe Reference, Packages References),因此最后,使用 max(..., key=...) 根据时间戳获取最新的配方引用。之后,创建 latest_pref_list 以仅保留每个包 ID 的最新包修订版本。它会遍历包 ID 集合以根据时间戳获取最新的包修订版本。

如果您想了解更多关于 Conan API 的信息,请访问 ConanAPI 部分