构建软件包:build()
方法#
已经使用了包含 build()
方法的 Conan 配方,并学习了如何使用它来调用构建系统并构建我们的软件包。在这个教程中,我们将修改这个方法,并解释你可以如何使用它来执行以下操作:
构建和运行测试
按条件对源代码进行补丁处理
根据条件选择要使用的构建系统
请首先克隆源代码以重新创建此项目。你可以在 GitHub 上的 examples2 存储库中找到它们:
git clone https://github.com/conan-io/examples2.git
cd examples2/tutorial/creating_packages/build_method
为您的项目构建和运行测试#
你会注意到 conanfile.py 文件有一些变化。检查相关部分:
配方引入的变更#
1class helloRecipe(ConanFile):
2 name = "hello"
3 version = "1.0"
4
5 ...
6
7 def source(self):
8 git = Git(self)
9 git.clone(url="https://github.com/conan-io/libhello.git", target=".")
10 # Please, be aware that using the head of the branch instead of an immutable tag
11 # or commit is not a good practice in general
12 git.checkout("with_tests")
13
14 ...
15
16 def requirements(self):
17 if self.options.with_fmt:
18 self.requires("fmt/8.1.1")
19 self.test_requires("gtest/1.11.0")
20
21 ...
22
23 def generate(self):
24 tc = CMakeToolchain(self)
25 if self.options.with_fmt:
26 tc.variables["WITH_FMT"] = True
27 tc.generate()
28
29 def build(self):
30 cmake = CMake(self)
31 cmake.configure()
32 cmake.build()
33 if not self.conf.get("tools.build:skip_test", default=False):
34 test_folder = os.path.join("tests")
35 if self.settings.os == "Windows":
36 test_folder = os.path.join("tests", str(self.settings.build_type))
37 self.run(os.path.join(test_folder, "test_hello"))
38
39 ...
将
gtest/1.11.0
要求添加到配方中作为test_requires()
。这是一种用于测试库(如 Catch2 或 gtest)的要求类型。使用
tools.build:skip_test
配置(默认为False
),来告诉 CMake 是否要构建和运行测试。有几点需要注意:如果将
tools.build:skip_test
配置设置为True
,Conan 将自动将BUILD_TESTING
变量注入 CMake 并设置为OFF
。在下一节中,我们会看到我们使用这个变量在CMakeLists.txt
中决定是否构建测试。在
build()
方法中使用tools.build:skip_test
配置,在构建包和测试之后,来决定是否要运行测试。在这种情况下,使用
gtest
进行测试,并且必须检查构建方法是否要运行测试。此配置也会影响如果你使用CTest
和Meson.test()
for Meson 时CMake.test()
的执行。
库源代码中引入的变更#
首先请注意,使用了 libhello
库的另一个分支。这个分支在库方面有两个新特性:
在库源代码中添加了名为
compose_message()
的新函数,以便可以为此函数添加一些单元测试。这个函数只是根据传入的参数创建一条输出消息。正如我们在上一节中提到的,库的
CMakeLists.txt
文件使用了BUILD_TESTING
CMake 变量,该变量有条件地添加了测试目录。
CMakeLists.txt
:
cmake_minimum_required(VERSION 3.15)
project(hello CXX)
...
if (NOT BUILD_TESTING STREQUAL OFF)
add_subdirectory(tests)
endif()
...
BUILD_TESTING
CMake 变量由 Conan 声明并设置为 OFF
(如果尚未定义),每当 tools.build:skip_test
配置设置为值 True
时。此变量通常在您使用 CTest 时由 CMake 声明,但使用 tools.build:skip_test
配置时,即使您使用其他测试框架,也可以在您的 CMakeLists.txt
中使用它。
在测试文件夹中有使用 googletest 进行测试的 CMakeLists.txt
:
cmake_minimum_required(VERSION 3.15)
project(PackageTest CXX)
find_package(GTest REQUIRED CONFIG)
add_executable(test_hello test.cpp)
target_link_libraries(test_hello GTest::gtest GTest::gtest_main hello)
对 compose_message()
函数的功能进行基本测试:
#include "../include/hello.h"
#include "gtest/gtest.h"
namespace {
TEST(HelloTest, ComposeMessages) {
EXPECT_EQ(std::string("hello/1.0: Hello World Release! (with color!)\n"), compose_message("Release", "with color!"));
...
}
}
现在已经回顾了代码中的所有变化,试试这些改动:
1$ conan create . --build=missing -tf=""
2...
3[ 25%] Building CXX object CMakeFiles/hello.dir/src/hello.cpp.o
4[ 50%] Linking CXX static library libhello.a
5[ 50%] Built target hello
6[ 75%] Building CXX object tests/CMakeFiles/test_hello.dir/test.cpp.o
7[100%] Linking CXX executable test_hello
8[100%] Built target test_hello
9hello/1.0: RUN: ./tests/test_hello
10Capturing current environment in /Users/user/.conan2/p/tmp/c51d80ef47661865/b/build/generators/deactivate_conanbuildenv-release-x86_64.sh
11Configuring environment variables
12Running main() from /Users/user/.conan2/p/tmp/3ad4c6873a47059c/b/googletest/src/gtest_main.cc
13[==========] Running 1 test from 1 test suite.
14[----------] Global test environment set-up.
15[----------] 1 test from HelloTest
16[ RUN ] HelloTest.ComposeMessages
17[ OK ] HelloTest.ComposeMessages (0 ms)
18[----------] 1 test from HelloTest (0 ms total)
19
20[----------] Global test environment tear-down
21[==========] 1 test from 1 test suite ran. (0 ms total)
22[ PASSED ] 1 test.
23hello/1.0: Package '82b6c0c858e739929f74f59c25c187b927d514f3' built
24...
如您所见,测试已被构建并运行。现在使用命令行中的 tools.build:skip_test
配置来跳过测试的构建和运行:
$ conan create . -c tools.build:skip_test=True -tf=""
...
[ 50%] Building CXX object CMakeFiles/hello.dir/src/hello.cpp.o
[100%] Linking CXX static library libhello.a
[100%] Built target hello
hello/1.0: Package '82b6c0c858e739929f74f59c25c187b927d514f3' built
...
现在你可以看到,只构建了库目标,没有构建或运行任何测试
条件性地修补源代码#
如果您需要修补源代码,推荐的方法是在 source()
方法中执行。有时,如果该补丁依赖于设置或选项,您必须在启动构建之前使用 build()
方法将补丁应用于源代码。Conan 提供了几种实现这一功能的方法。其中一种方法是使用 replace_in_file
工具:
import os
from conan import ConanFile
from conan.tools.files import replace_in_file
class helloRecipe(ConanFile):
name = "hello"
version = "1.0"
# Binary configuration
settings = "os", "compiler", "build_type", "arch"
options = {"shared": [True, False], "fPIC": [True, False]}
default_options = {"shared": False, "fPIC": True}
def build(self):
replace_in_file(self, os.path.join(self.source_folder, "src", "hello.cpp"),
"Hello World",
"Hello {} Friends".format("Shared" if self.options.shared else "Static"))
请注意,在 build()
中应尽量避免打补丁,仅在非常特殊的情况下才进行,因为这会使您在本地开发包时更加困难(我们将在后面的本地开发流程部分对此进行更详细的解释)。
有条件地选择构建系统#
在构建过程中,有些包需要根据不同的平台选择不同的构建系统,这种情况并不少见。例如,hello
库可以在 Windows 上使用 CMake 构建,在 Linux 和 macOS 上使用 Autotools 构建。这可以很容易地在 build()
方法中这样处理:
...
class helloRecipe(ConanFile):
name = "hello"
version = "1.0"
# Binary configuration
settings = "os", "compiler", "build_type", "arch"
options = {"shared": [True, False], "fPIC": [True, False]}
default_options = {"shared": False, "fPIC": True}
...
def generate(self):
if self.settings.os == "Windows":
tc = CMakeToolchain(self)
tc.generate()
deps = CMakeDeps(self)
deps.generate()
else:
tc = AutotoolsToolchain(self)
tc.generate()
deps = PkgConfigDeps(self)
deps.generate()
...
def build(self):
if self.settings.os == "Windows":
cmake = CMake(self)
cmake.configure()
cmake.build()
else:
autotools = Autotools(self)
autotools.autoreconf()
autotools.configure()
autotools.make()
...