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

注意

这主要是一个示例命令。内置的 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() 返回一个元组列表 (配方 引用, 引用),因此最后,max(..., key=...) 用于根据其时间戳获取最新的配方引用。之后,创建 latest_pref_list 以仅保留每个包 ID 的最新包修订版。它遍历包 ID 的集合,以根据其时间戳获取最新的包修订版。

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