工作区

警告

此特性是新的孵化中特性的一部分。这意味着它仍在开发中,并寻求用户测试和反馈。更多信息请参阅 孵化中章节

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

介绍

重要

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

Conan 工作区 让你有机会以 协调整体(也称为 超级项目)的方式将多个包作为 可编辑 模式管理

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

  • 整体,我们指的是可编辑包作为一个整体进行构建,为整个工作区生成一个单一结果(生成器等)。

注意,添加到工作区的包会自动解析为 可编辑 的包。这些可编辑包被命名为工作区的 packages。此外,这些 packages 的根消费者被命名为 products

如何定义工作区

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

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

另请参阅

阅读 工作区文件 章节。阅读 conan workspace 命令 章节。

整体构建

Conan 工作区可以作为一个单一的整体项目(超级项目)来构建,这非常方便。让我们通过一个例子来看一下

$ conan new workspace
$ conan workspace 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 SUBFOLDER)
   FetchContent_Declare(
      ${SUBFOLDER}
      SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/${SUBFOLDER}
      SYSTEM
      OVERRIDE_FIND_PACKAGE
   )
   FetchContent_MakeAvailable(${SUBFOLDER})
endfunction()

add_project(liba)
# They should be defined in the liba/CMakeLists.txt, but we can fix it here
add_library(liba::liba ALIAS liba)
add_project(libb)
add_library(libb::libb ALIAS libb)
add_project(app1)

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

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

conanws.py
from conan import Workspace
from conan import ConanFile
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

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

conan workspace 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 install
$ cmake ...

注意

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

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

协调构建

Conan 工作区也可以单独构建不同的 packages,并考虑是否存在消费它们的 products

让我们使用另一种结构来更好地理解它的工作原理。现在,让我们从头开始创建它,使用 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 库分别作为新的 productspackages 添加到工作区

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

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

$ 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 中开启新的 issues。