Workspaces

警告

此功能是新的孵化功能的一部分。这意味着它尚在开发中,并且正在寻求用户测试和反馈。有关更多信息,请参阅 孵化部分

在上一个章节中,我们讨论了可编辑包以及如何定义自定义布局。现在,让我们介绍工作区的概念以及如何使用它。

介绍

重要

可以通过定义环境变量 CONAN_WORKSPACE_ENABLE=will_break_next 来启用工作区功能。值 will_break_next 用来强调它将在后续版本中发生变化,并且此功能仅供测试,不能用于生产环境。

Conan 工作区 允许您以协调单体(也称为超级构建)的方式管理多个包的 editable 模式。

  • 协调模式下,我们指的是 Conan 逐个构建可编辑包,从应用程序/消费者开始(如果存在)。

  • 单体模式下,我们指的是将可编辑包作为一个整体进行构建,为整个工作区生成单一的输出(生成器等)。

请注意,添加到工作区的包会自动解析为 editable 模式。这些可编辑包被命名为工作区的 packages

如何定义工作区

工作区通过 conanws.yml 和/或 conanws.py 文件来定义。任何 Conan 工作区命令都会从当前工作目录向上遍历文件系统到根目录,直到找到这些文件之一。这将定义“根”工作区文件夹。 conanws 文件中的路径旨在是相对路径,以便在必要时可重定位,或者可以提交到 Git 中,用于类似 monorepo 的项目。

通过 conan workspace 命令,我们可以从当前工作区打开、添加和/或移除 packages

另请参阅

阅读 工作区文件 部分。阅读 conan workspace 命令 部分。

单体构建

Conan 工作区可以被构建成一个单一的单体项目(超级项目),这会非常方便。让我们通过一个例子来了解。

$ conan new workspace
$ conan workspace super-install
$ cmake --preset conan-release # use conan-default in Win
$ cmake --build --preset conan-release

让我们稍微解释一下发生了什么。首先,conan new workspace 创建了一个模板项目,包含一些相关文件以及以下结构:

.
├── CMakeLists.txt
├── app1
│    ├── CMakeLists.txt
│    ├── conanfile.py
│    ├── src
│    │    ├── app1.cpp
│    │    ├── app1.h
│    │    └── main.cpp
│    └── test_package
│        └── conanfile.py
├── conanws.py
├── conanws.yml
├── liba
│    ├── CMakeLists.txt
│    ├── conanfile.py
│    ├── include
│    │    └── liba.h
│    ├── src
│    │    └── liba.cpp
│    └── test_package
│        ├── CMakeLists.txt
│        ├── conanfile.py
│        └── src
│            └── example.cpp
└── libb
    ├── CMakeLists.txt
    ├── conanfile.py
    ├── include
    │    └── libb.h
    ├── src
    │    └── libb.cpp
    └── test_package
        ├── CMakeLists.txt
        ├── conanfile.py
        └── src
            └── example.cpp

根目录下的 CMakeLists.txt 定义了超级项目:

CMakeLists.txt
cmake_minimum_required(VERSION 3.25)
project(monorepo CXX)

include(FetchContent)

function(add_project PACKAGE_NAME SUBFOLDER)
    message(STATUS "Adding project: ${PACKAGE_NAME}. Folder: ${SUBFOLDER}")
    FetchContent_Declare(
        ${PACKAGE_NAME}
        SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/${SUBFOLDER}
        SYSTEM
        OVERRIDE_FIND_PACKAGE
    )
    FetchContent_MakeAvailable(${PACKAGE_NAME})
endfunction()

include(build/conanws_build_order.cmake)

foreach(pair ${CONAN_WS_BUILD_ORDER})
    string(FIND "${pair}" ":" pos)
    string(SUBSTRING "${pair}" 0 "${pos}" pkg)
    math(EXPR pos "${pos} + 1")  # Skip the separator
    string(SUBSTRING "${pair}" "${pos}" -1 folder)

    add_project(${pkg} ${folder})
    # This target should be defined in the liba/CMakeLists.txt, but we can fix it here
    get_target_property(target_type ${pkg} TYPE)
    if (NOT target_type STREQUAL "EXECUTABLE")
        add_library(${pkg}::${pkg} ALIAS ${pkg})
    endif()
endforeach()

所以基本上,超级项目使用 FetchContent 来添加子文件夹的子项目。为了正确工作,子项目必须是基于 CMake 的子项目,并包含 CMakeLists.txt。此外,子项目必须像 find_package() 脚本所定义的那样定义正确的 targets,例如 liba::liba。如果不是这种情况,可以始终定义一些本地的 ALIAS targets。

这个超级构建 CMakeLists.txt 动态地定义了正确的子项目顺序。请注意,FetchContent 策略需要按正确的构建顺序定义不同的子项目。虽然这对于只有很少包的工作区很容易,但对于较大的工作区来说可能会变得麻烦。构建顺序的定义在生成的 conanws_build_order.cmake 文件中完成,该文件由 conan workspace super-install 命令调用 conanws.py 工作区的 build_order() 方法创建。工作区的责任是将 build_order() 的信息翻译成构建系统的具体实现。确切的实现,例如 conanws_build_order.cmake 文件,不是一个“内置”的工作区功能,请注意这只是 conan new workspace 默认模板提供的示例方法。用户可以在他们的 conanws.py 文件中实现自己的逻辑。

另一个重要部分是 conanws.py 文件。

conanws.py
from conan import Workspace
from conan import ConanFile
from conan.tools.files import save
from conan.tools.cmake import CMakeDeps, CMakeToolchain, cmake_layout


class MyWs(ConanFile):
    """ This is a special conanfile, used only for workspace definition of layout
    and generators. It shouldn't have requirements, tool_requirements. It shouldn't have
    build() or package() methods
    """
    settings = "os", "compiler", "build_type", "arch"

    def generate(self):
        deps = CMakeDeps(self)
        deps.generate()
        tc = CMakeToolchain(self)
        tc.generate()

    def layout(self):
        cmake_layout(self)


class Ws(Workspace):
    def root_conanfile(self):
        return MyWs  # Note this is the class name

    def build_order(self, order):
        super().build_order(order)  # default behavior prints the build order
        pkglist = " ".join([f'{it["ref"].name}:{it["folder"]}' for level in order for it in level])
        save(self, "build/conanws_build_order.cmake", f"set(CONAN_WS_BUILD_ORDER {pkglist})")

嵌入的 class MyWs(ConanFile) conanfile 的作用很重要,它定义了超级项目所需的生成器和布局。

conan workspace super-install 不会单独安装不同的可编辑包,对于此命令,可编辑包不存在,它们仅被视为依赖图中的一个“节点”,因为它们将成为超级项目构建的一部分。因此,所有超级项目外部依赖项只有一个生成的 conan_toolchain.cmake 和一组通用的依赖项 xxx-config.cmake 文件。

上面的模板在没有外部依赖的情况下也能工作,但是当有外部依赖时,一切都会以相同的方式工作。这可以通过以下方式进行测试:

$ conan new cmake_lib -d name=mymath
$ conan create .
$ conan new workspace -d requires=mymath/0.1
$ conan workspace super-install
$ cmake ...

注意

当前的 conan new workspace 生成一个基于 CMake 的超级项目。但是,也可以使用其他构建系统定义超级项目,例如 MSBuild 解决方案文件,它添加了不同的 .vcxproj 子项目。只要超级项目知道如何聚合和管理子项目,这是可行的。

conanws.pyadd() 方法中,如果存在某种结构,也可以管理将子项目添加到超级项目。

协调构建

Conan 工作区也可以单独构建不同的 packages,并考虑到是否有包被定义为其他包的消费者。

让我们使用另一个结构来更好地理解它是如何工作的。现在,让我们从头开始,使用 conan workspace init . 创建一个几乎为空的 conanws.py/conanws.yml,并使用 conan new cmake_lib/cmake_exe 的基本模板,这些模板创建常规的基于 CMake 的 conan 包。

$ mkdir myproject && cd myproject
$ conan workspace init .
$ conan new cmake_lib -d name=hello -d version=1.0 -o hello
$ conan new cmake_exe -d name=app -d version=1.0 -d requires=hello/1.0 -o app

这些命令创建了一个类似这样的文件结构:

.
├── conanws.py
├── conanws.yml
├── app
│    ├── CMakeLists.txt
│    ├── conanfile.py
│    ├── src
│    │    ├── app.cpp
│    │    ├── app.h
│    │    └── main.cpp
│    └── test_package
│        └── conanfile.py
└── hello
     ├── CMakeLists.txt
     ├── conanfile.py
     ├── include
     │    └── hello.h
     ├── src
     │    └── hello.cpp
     └── test_package
         ├── CMakeLists.txt
         ├── conanfile.py
         └── src
             └── example.cpp

现在,conanws.yml 是空的,而 conanws.py 有一个相当简单的定义。让我们将 app 应用程序(它依赖于 hello)和 hello 库作为新的 packages 添加到工作区。

$ conan workspace add hello
Reference 'hello/1.0' added to workspace
$ conan workspace add app
Reference 'app/1.0' added to workspace

定义了工作区的 packages 后,我们可以构建它们并执行应用程序。

$ conan workspace build
$ app/build/Release/app
hello/1.0: Hello World Release!
...
app/1.0: Hello World Release!
...

如果您有任何反馈,请在新工单中提交至 https://github.com/conan-io/conan/issues