Conan 1.32.0 引入了一种新的策略来定义、检测和处理无效配置。这包括一个名为 validate() 的新方法,以及一个名为 BINARY_INVALID 的新 package_id 模式。它还包括对我们未来工具链和生成器策略的大规模重构,一些生成器的一般新功能,以及两个新的 Meson 类(一个工具链和一个构建辅助类)。我们还添加了一些子命令来处理没有关联远程的配方和包,并使其能够处理在图中多次包含相同包的 lockfile(通过私有依赖项实现)。最后,我们很高兴展示一个经过验证的变通方法,它满足了一个长期存在的、高度优先级的请求,即可以选择将 build_requires 包含到 package_id 中。需要注意的是,有一些重要的副作用和注意事项,因此请继续阅读以了解详细信息。

无效配置的新策略

近几个月来,社区中关于定义、检测和处理无效或不可能的配置的一些棘手挑战的讨论越来越多。从历史上看,一般的建议是在 configure() 方法期间引发 ConanInvalidConfiguration 错误来处理这些情况,但在实践中,由于依赖关系图的状态,有些检查根本无法在那里运行。此外,使用 compatible_packages 功能时,某些配方可能无法构建,但可能配置为提供兼容的包。

这些情况的现有解决方法并不理想,因此我们现在添加了一些一流的功能来解决它们。首先,添加了一个名为 validate()Conanfile 新方法,该方法在图完全评估后执行,从而解决了之前的问题。其次,添加了一个名为 BINARY_INVALID 的新 package_id 模式。展望未来,configure() 方法仍应用于实际设置配置,但 validate() 方法应承担引发 ConanInvalidConfiguration 错误的所有责任。在这些情况下,BINARY_INVALID 现在作为 conan info 命令输出中的状态报告,而不是之前该命令以返回码 6 失败的行为。这是一个重大改进,因为某些自动化情况(例如 ConanCenter)使用 conan info 来决定要构建什么,并且解析 json 输出比解释返回码更可取。

这是一个 validate() 的示例及其输出

def validate(self):
    if self.settings.os == "Windows":
        raise ConanInvalidConfiguration("Windows not supported")


$ conan create . pkg/0.1@ -s os=Windows
...
Packages
    pkg/0.1:INVALID - Invalid
...
> ERROR: There are invalid packages (packages that cannot exist for this configuration):
> pkg/0.1: Invalid ID: Windows not supported

重构工具链和生成器(以及命名空间)

需要强调的是,此版本中许多实验性类已移动到不同的命名空间中。请参阅更改日志和文档以了解所有详细信息,但这里有一个简单的总结。

首先,我们现在公开了一个名为 conan 的新的顶级命名空间。经验丰富的用户会记得,以前,几乎所有 Conan 类和函数都在 conanfile.py 中以 conans 命名空间公开。总而言之,不再需要这个字母 s,许多人发现它很烦人。在为 Conan 2.0 做准备时,我们计划在 conan 命名空间下提供所有现有类和函数,并最终弃用 conans。在此版本中,我们保守地开始只是在 tools 子命名空间下公开了一些全新的类和函数。如果一切顺利,我们将随着时间的推移为现有类和函数添加别名。

因此,在 conan.tools 下,我们创建了四个子命名空间

  • cmake
  • gnu
  • meson
  • microsoft

我们将在未来添加更多内容到此结构中,并且决定按“供应商”组织这些集成工具比尝试按“目的”或“功能”组织它们更直观。这种策略在过去已被证明很笨拙,因为许多工具和实用程序函数都是多用途的。

我们已经开始使用这些目录将所有相应的工具链类放入其中。我们还开始推进生成器的新命名约定,从实验性的 msbuild 生成器开始。它已被重命名为 MSBuildDeps,我们稍后将详细说明我们这样做的原因。我们还将在 1.33 中创建一个名为 CMakeDeps 的新生成器,位于 cmake 目录中,最初它将是现有 cmake_find_package_multi 生成器的副本。我们已经将其作为大多数 CMake 生成器用例的首选推荐,并且我们将推广人们在它可用时从这个新的导入和新名称(CMakeDeps)使用它。此外,我们添加了一个名为 MesonToolchain 的新工具链。

工具链如何帮助生成器发展

您可能还注意到,列出的新生成器名称中包含大写字母。实际上,这代表了一个非常重要的约定更改的开始。从历史上看,生成器在 class 名称中包含大写字母,然后有一个小写的 name 字段,该字段是在配方、配置文件和 CLI 中使用的别名。我们现在开始弃用使用小写生成器别名的过程,并迁移到在所有引用生成器的地方使用文字 class 名称作为标识符。这种变化可能难以适应,这似乎仅仅是外观上的变化,所以我认为需要一些解释。

我们对工具链的工作对我们未来对生成器的计划产生了重大影响。我们创建了“工具链”类的抽象以及 conanfile.py 中相应的方法,以区分两个不同的关注点。“生成器”现在旨在专注于“与依赖项相关”的信息,“工具链”旨在专注于与“所有其他内容”相关的构建变量(这意味着主要是平台、编译器和链接器设置)。当我们开始将工具链作为简单的类和函数调用在专用配方方法中使用时,很明显地为这些工具链的构造函数和方法提供参数。这使得工具链灵活且可配置,并允许我们开始使用它们做一些很酷的事情。然后我们意识到,如果我们对生成器做同样的事情(创建一些采用参数且可配置的新生成器),那么我们可能可以相对轻松地解决许多长期存在的与生成器相关的功能请求。

这似乎是一个非常惊人的发现……好得令人难以置信。巨大的成功很少来得容易,因此我们回顾过去以找出为什么在尝试解决其中一些功能请求的过程中我们没有想到这一点。此外,当前生成器的实现有何独特之处,以及为什么再次以这种方式设计。我们发现了一些原因,我将总结一下,但简单地说,这不足以阻止我们继续推进可配置生成器。我们仍在内部努力验证基础知识,并希望很快能分享一些重要的内容。

Conan 中生成器的主要“魔力”在于它们在配方中声明的方式以及隐式“依赖”的方式,并在适当的时间调用。好处实际上归结为超级简洁的声明式语法。但是,这一个小小的魔力与便利实际上带来了巨大的代价。其实现存在根本的内部挑战,如果不进行重大更改就无法解决。回想起来,这种魔力可能不值得付出代价。现在正在探索的方法没有这些重大后果,有望提供“可配置生成器”,并且可能甚至不会比我们今天拥有的更冗长。

现在实施可配置生成器的主要障碍是生成器可以在两种上下文中调用:在配方中和按需(使用 -g generator 或在配置文件中)。从历史上看,人们要求的大多数“可配置性”都围绕着在配方中使用生成器,并且使它们成为具有方法的普通 Python 类似乎将具有人们可能期望的所有好处。但是,很难想象可配置生成器在按需使用时是否有意义。目前,我们不打算为此上下文中的生成器传递参数支持额外的命令行语法(尽管我们已经考虑过了)。因此,我们将不得不看看大多数生成器是否仅使用默认值在按需上下文中才有用。它们可能会有用,并且不应该比没有任何配置的现有生成器更糟糕。

上面描述的启示过程也使我们能够退一步并认识到可以进行的简化。虽然从构建的角度来看,工具链和生成器绝对是不同的信息类别,但我们认为就其在 Conan 中的本质而言,它们并没有实质性的差异。因此,我们决定重构围绕它们的实现,以类似的方式对待它们。这极大地降低了内部复杂性,并在多个方面提高了一致性,但简单地说,学习的东西更少了。

这里的总体思路是,这两件事的过程仍然相同

  • 分析 Conan 收集的配置和依赖项数据
  • 生成文件

它们只是同一事物的两个子类别。因此,在那个时候,将conanfile.py中仍处于实验阶段的def toolchain()方法重命名为def generate()似乎相当明显。出于同样的原因,我们决定触发工具链和生成器上文件生成的“主要接口方法”在未来也应该只是generate()。因此,对于工具链,旧方法write_toolchain_files()已重命名为generate()。它简单、一致且描述性强。以下是如何一起使用这两个方法的示例。

from conans import ConanFile
from conan.tools.microsoft import MSBuildDeps, MSBuildToolchain

class Pkg(ConanFile):
    settings = "os", "compiler", "build_type", "arch"
    requires = "boost/1.72.0", "poco/1.9.4"

    # The generate method creates files that the build can use
    def generate(self):
        # generates conan_boost.props, conan_poco.props for dependencies
        # and conandeps.props that aggregates them
        deps= MSBuildDeps(self)
        deps.generate()
        # generates conantoolchain.props from current settings
        tc= MSBuildToolchain(self)
        tc.generate()

此外,回到弃用生成器的小写name字段/别名的主题,我们意识到我们在工具链中没有实现等效的功能。这主要是因为它实际上只代表了一个抽象和重定向层,这比它有价值更令人困惑。回顾生成器,我们也有同样的感受。有时您希望将class名称与事物的“使用名称”分离,但这不再像那些情况之一。但这不仅仅是外观问题,在实现可配置生成器时,它产生了一些尴尬的设计问题,如果我们选择使用class名称,这些问题就会消失。在这种情况下,简单在多个方面都是更好的选择。

将build_requires包含在package_id中(实验性)

长期以来一直存在一个未解决的功能请求,在当前版本的Conan中,如果不进行重大更改,它似乎几乎无法解决。该功能请求是使build_requires能够影响package_id成为可能。用户长期以来一直希望拥有此功能,并且在许多情况下,现有的解决方法都不可接受。遗憾的是,一流的修复可能仍只会在Conan 2.0中出现。但是,我们发现了一种新的解决方法,它可能会在某些用例中实现目标,并且实际上已经存在了很长时间,并且与以前已知的解决方法有很大不同。需要注意的是,在我们为受影响的用户制造太多兴奋之前,此解决方法有一些注意事项和挑战,可能会否定其使用。

尽管如此,以下是策略。如果您在配方中声明了build_requires,并且希望它影响包的package_id,只需将其也添加为python_requires即可。就是这样,这就是策略。它之所以有效,是因为Conan已经将python_requires包含在package_id计算中,并且不会导致任何问题,因为它只是丢弃并且不使用build_requiresconanfile.py。再说一遍,它似乎好得令人难以置信,所以让我们重点介绍最大的问题。如果您通过配置文件利用您的build_requires,那么这根本无法为您工作。这很不幸,因为这描述了许多build_requires的用例,但并非所有用例。无论如何,我们认为一些用户将能够使用此策略,因此我们希望在此处宣布这一发现。我们还在文档中包含了一个示例。



除了上面列出的项目之外,还有一长串您可能希望阅读的相当有影响力的错误修复。如果是这样,请参阅更改日志以获取完整列表。

我们希望您喜欢此版本,并期待您的反馈