Conan 1.36 带来了多项重要增强,包括对 CMake 集成的多项改进,cpp_info 数据结构中特定值的新的属性策略,用于测试用作 build_requires 的包的一流机制,以及排除 Conan 构建中特定包的功能。

新的 CMake 集成增强

此版本中包含许多与 CMake 相关的功能,但变更日志中说明的顺序有些随机。因此,这里总结了所有与 CMake 集成相关的改进和新增功能。

CMakeDeps

  • 通过打印每个声明的目标来改进跟踪。

CMakeToolchain

  • 新的“块”扩展模型取代了继承。
  • 支持使用任何 MSVC 版本设置任何 msvc 工具集。
  • CMake 生成器现在自动从编译器设置中推断。
  • 支持选择 Ninja CMake 生成器。

CMakeToolchain 中使用的新的“块”扩展模型可能是这些更改中最重要的一个,因此我们将在本文中重点介绍它。

自从 Conan 创建以来,用户一直有很多关于自定义生成器输出的突出请求。已经有一些使用各种策略来提供此功能的建议和 POC,但直到现在还没有任何一个通过审查。在此版本中,使用 CMakeToolchain 生成器,我们推出了一个模型,其中生成器在内部将其输出构建为一个类似字典的数据结构,其中包含名为“块”的模板化内容。

此功能解决的第一组突出功能请求是普遍希望使用内置生成器,但删除生成内容的某些部分。以前,人们使用 tools.replace_in_file 或类似方法从生成的输出中手术式地删除不需要的内容,然后再进行构建。这种方法有很多不希望的后果。现在,CMakeToolchain 类提供了一种一流的方法来在将生成的输出写入磁盘 **之前** 删除这些块。

    tc = CMakeToolchain(self)
    tc.pre_blocks.remove("generic_system")
    tc.generate()

此方法解决的另一组主要突出功能请求是希望为现有构建系统编写更多自定义的生成器,同时借用现有生成器的一些有价值的逻辑。对于 CMake(比任何其他构建系统都更甚),现有的生成器经过多年的改进和增强,并且社区在其构建中融入了领域知识,并且它们将继续改进。例如,Conan 设置 compiler.libcxx 会影响在某些情况下发送到编译器的两个不同的值。首先,它会影响必须传递给某些编译器的 -stdlib 标志(例如:-stdlib=libstdc++)。其次,它会影响必须传递给某些编译器的 GLIBCXX_USE_CXX11_ABI 预处理器定义(例如:-DGLIBCXX_USE_CXX11_ABI=1)。为了让 Conan 正确处理所有编译器,现有的 CMakeToolchain 生成器使用了以下非常复杂的逻辑

def context(self):
    libcxx = self._conanfile.settings.get_safe("compiler.libcxx")
    if not libcxx:
        return None
    compiler = self._conanfile.settings.compiler
    lib = glib = None
    if compiler == "apple-clang":
        # In apple-clang 2 only values atm are "libc++" and "libstdc++"
        lib = "-stdlib={}".format(libcxx)
    elif compiler == "clang":
        if libcxx == "libc++":
            lib = "-stdlib=libc++"
        elif libcxx == "libstdc++" or libcxx == "libstdc++11":
            lib = "-stdlib=libstdc++"
        # FIXME, something to do with the other values? Android c++_shared?
    elif compiler == "sun-cc":
        lib = {"libCstd": "Cstd",
               "libstdcxx": "stdcxx4",
               "libstlport": "stlport4",
               "libstdc++": "stdcpp"
               }.get(libcxx)
        if lib:
            lib = "-library={}".format(lib)
    elif compiler == "gcc":
        if libcxx == "libstdc++11":
            glib = "1"
        elif libcxx == "libstdc++":
            glib = "0"
    return {"set_libcxx": lib, "glibcxx": glib}

我们知道企业团队创建自己的 CMake 生成器(与内置生成器非常不同)是非常常见的,但是,他们很可能仍然希望处理 compiler.libcxx 设置。对于这些情况,我们不希望其他人不得不复制、维护、测试和/或更新上述逻辑。因此,新的块策略使企业团队能够在 Conan 中仅使用 GLibCXXBlock 类并仅获取此块,而无需处理现有的生成器。

以下是截至本文撰写时可用的 CMakeToolchain 的完整块列表。

  • generic_system
  • android_system
  • ios_system
  • find_paths
  • fpic
  • rpath
  • arch_flags
  • libcxx
  • vs_runtime
  • cppstd
  • shared
  • parallel

有关每个块包含的内容的更多详细信息,请参阅 CMakeToolchain 文档

这种“块”范式以及许多块名称可能会在不久的将来传播到其他生成器。

新的 cpp_info 属性系统

此版本中的第二个最重要的功能肯定是新的 cpp_info 属性系统。需要注意的是,这个新的属性系统不会影响 cpp_info 的核心成员,例如 includedirslibdirslibs 等。它只影响一些值,因此请参阅 cpp_info 属性的文档 以了解此功能的范围。

conanfile.pycpp_info 属性是 Conan 的基石,最初表示为一个简单的二维类似字典的对象。它具有声明性的特性,并且限制了 Conan 生成器中遍历它的代码的复杂性。但是,随着时间的推移,一如既往,决定问题域复杂性的不是我们的编程模型……而是域本身。并且,C 和 C++ 中依赖项信息的复杂性逐渐克服了字典的简单性。

随着时间的推移,出现了更复杂的用例,其中配方作者希望自定义生成器的输出文件名,并且在每个生成器的基础上,因此 cpp_info 添加了一个 filenames 成员,它本身就是一个字典。

self.cpp_info.filenames["cmake_find_package"] = "MyFileName"
self.cpp_info.filenames["cmake_find_package_multi"] = "MyFileName"

此外,配方作者希望支持 CMake 构建系统中 components 的唯一抽象,因此 Conan 扩展了 cpp_info 以支持类似的表示法。

self.cpp_info.components["mycomponent"].names["cmake_find_package"] = "mycomponent-name"
self.cpp_info.components["mycomponent"].names["cmake_find_package_multi"] = "mycomponent-name"

然后出现了对名为 build_modules 的成员的请求,并且该成员也必须支持每个组件的定义,从而导致以下语法。

self.cpp_info.components["mycomponent"].build_modules.append(os.path.join("lib", "mypkg_bm.cmake"))

随着此数据结构的复杂性增加,它开始让人感觉我们已经超出了对字典模型的适当使用,原因有很多。例如,CMake 构建系统有多个生成器,我们知道开发团队也会创建自己的生成器。在大多数情况下(如上例所示),作者希望为“所有 CMake* 生成器”设置一个值。但是,使用上面的字典模型,键名是生成器名称,因此用户必须为每个已知的 CMake 生成器设置一次值,这实际上永远无法扩展或与自定义生成器很好地协同工作。

因此,以下是表达与上述示例相同信息的新方法,但这种方法不会遇到上面描述的问题。

self.cpp_info.set_property("cmake_file_name", "MyFileName")
self.cpp_info.components["mycomponent"].set_property("cmake_target_name", "mycomponent-name")
self.cpp_info.components["mycomponent"].set_property("cmake_build_modules", [os.path.join("lib", "mypkg_bm.cmake")])
self.cpp_info.components["mycomponent"].set_property("custom_name", "mycomponent-name", "custom_generator")

根本性的变化是,组件的字典成员现在可以具有命名属性,并且生成器可以在需要添加对它们的支 持时查询这些属性。因此,我们不是为每个可能的 CMake 生成器定义一次 build_modules,而是定义了一个 cmake_build_modules 属性,并且任意数量的 CMake 生成器可以选择添加对它的支持,或者在适当情况下忽略它。

总而言之,此策略使配方更通用,并且与支持特定生成器的耦合程度降低,并将责任放在配方作者身上以支持已知的属性。一些内置属性已在当前生成器中定义和使用,但此策略也支持完全任意的属性以用于自定义配方和自定义生成器的用例。因此,我们认为此功能将在企业包环境中得到广泛使用。

在 test_package 中支持 build_requires 测试

Conan 社区中另一个长期存在的请求是能够编写 test_package 逻辑,该逻辑可以正确测试 build_requires 的所有独特行为和特征。以前,test_package 功能实际上只设计用于测试普通的 requires。此限制的影响随着时间的推移而增加。例如,在 ConanCenter 中,用作 build_requires 的包数量稳步增加,并且由于我们没有有效的方法以编程方式对其进行测试,因此我们遇到了更多与 build_requires 包相关的错误,并且更难快速找到这些错误。

现在,创建 test_package 来验证包是否可以作为 build_requires 正确工作相对简单。只需添加以下属性,Conan 就会对包执行它对 build_requirements 执行的所有特殊行为,并在此上下文中针对 test_package 配方对其进行测试。

class MyBuildToolTestPackage(ConanFile):
    test_type = "build_requires"

就是这样,只需添加如上所示的 test_type 属性,它应该就可以工作了。

新的 --build 排除语法

今天我们将讨论的最后一个功能是排除 Conan 构建过程中的特定包的新语法。它再次回答了一些长期存在的特性请求,但这一个解释起来相当简单。

Conan 在所有包管理器中最新颖的功能之一是 --build 标志。在调用 conan createconan install 时,调用者可以精确指定他们想要从源代码构建图中的哪些包,以及可以使用预编译二进制文件的哪些包。用户有以下语法选项

  • --build=all--build:所有包
  • --build=package_name:单个指定包(按名称)
  • --build=package_1 --build=package_2:特定包列表
  • --build=missing:没有预编译二进制文件的包子集
  • --build=pack*:与通配符表达式匹配的包子集

但是,尽管从技术上讲能够处理所有可能的案例,但仍然有一种情况很尴尬。也就是说,指定您想要构建 **所有** 包 **除了** 一个(或几个)。要做到这一点,您必须使用 Conan 之外的脚本枚举图中所有包的完整列表,然后过滤掉您想要排除的包(这并非易事),然后将每个包作为 --build=package_name 添加到命令行中。

问题现在已解决。现在可以轻松指定您想要构建除一个之外的所有包(例如,如下例所示的zlib)。您只需在想要排除的模式前加上感叹号,就像在conanfile.py中的exportsexports_sources属性中一样。

  • --build=!zlib --build=*

这可能看起来微不足道,但对于那些需要它的人来说,它将大有裨益,并消除大量在 Conan 之外非常不希望出现的脚本代码。



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

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