Workspaces¶
警告
此功能是新的孵化功能的一部分。这意味着它仍在开发中,并且正在寻找用户测试和反馈。有关更多信息,请参阅 孵化部分。
在上一节中,我们研究了可编辑包以及如何定义自定义布局。让我们介绍工作区的概念以及如何使用它。
介绍¶
重要
可以通过定义环境变量 CONAN_WORKSPACE_ENABLE=will_break_next 来启用工作区功能。值 will_break_next 用于强调它将在下一个版本中更改,此功能仅用于测试,不能在生产环境中使用。
Conan 工作区 使您可以以编排或单体(也称为超级构建)方式将多个包作为 可编辑 模式进行管理
编排,我们表示 Conan 从应用程序/消费者开始(如果存在),逐个构建可编辑的包。
单体,我们表示可编辑的包被构建为单体,为整个工作区生成单个结果(生成器等)。
请注意,添加到工作区的包会自动解析为 可编辑 包。这些可编辑的包被命名为工作区的 packages。
如何定义工作区¶
工作区由文件 conanws.yml 和/或 conanws.py 定义。任何 Conan 工作区命令都会从当前工作目录向上遍历文件系统到文件系统根目录,直到找到其中一个文件。这将定义“根”工作区文件夹。 conanws 文件中的路径旨在是相对的,以便在必要时可以重新定位,或者可以提交到类似 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 定义了带有
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() 脚本会定义的目标一样,例如 liba::liba。如果不是这种情况,则始终可以定义一些本地 ALIAS 目标。
这个超级构建 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 文件
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})")
嵌入的 conanfile 类 class MyWs(ConanFile) 的作用很重要,它定义了超级项目必要的生成器和布局。
conan workspace super-install 不会单独安装不同的包,对于此命令,工作区的包不再作为独立的实体存在,它们只是被视为依赖关系图中的单个“节点”,因为它们将成为超级项目构建的一部分。因此,只有一个生成的 conan_toolchain.cmake 和一个通用的依赖集 xxx-config.cmake 文件,适用于所有超级项目外部依赖项。
在上面的 build_order(self, order) 方法中,order 参数是一个有序列表的列表,表示构建的拓扑排序顺序。内部列表的元素表示工作区中的包,并且是包含引用 ref(类型为 RecipeReference)和每个包的源文件夹 folder 的字典。此 folder 将是配方 layout() 中指定的 source_folder。
注意
最佳实践
对于工作区配方,建议保持简单的布局,将 conanfile.py 放在每个包存储库的根目录中,源文件夹也是存储库的根目录,并将 CMakeLists.txt 放在存储库的根目录中。这简化了许多任务,例如 git clone <repo> && cd repo && conan install/build,并且也使工作区更易于定义和管理。
上面的模板没有外部依赖项,但是当存在外部依赖项时,一切都会以相同的方式工作。可以使用
$ 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 的超级项目。但是,可以定义使用其他构建系统的超级项目,例如添加不同的 .vcxproj 子项目的 MSBuild 解决方案文件。只要超级项目知道如何聚合和管理子项目,就可以实现。
如果存在某种结构,conanws.py 中的 add() 方法也可以管理将子项目添加到超级项目。
编排构建¶
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 lib 作为新的 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 上打开新票证。