处理包中的源代码

上一教程章节中,我们为 “Hello World” C++ 库创建了一个 Conan 包。我们使用了 Conanfile 的 exports_sources 属性来声明库源代码的位置。当源文件与 Conanfile 在同一文件夹中时,此方法是定义源文件位置的最简单方法。然而,有时源文件存储在仓库或远程服务器的文件中,而不是与 Conanfile 位于同一位置。在本节中,我们将通过添加一个 source() 方法来修改之前创建的 recipe,并解释如何

  • 从存储在远程仓库的 zip 文件中获取源代码。

  • git 仓库的分支中获取源代码。

请先克隆源代码以重新创建此项目。你可以在 GitHub 上的 examples2 仓库中找到它们

$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/tutorial/creating_packages/handle_sources

项目结构与之前示例中的相同,但没有库的源代码

.
├── CMakeLists.txt
├── conanfile.py
└── test_package
    ├── CMakeLists.txt
    ├── conanfile.py
    └── src
        └── example.cpp

从存储在远程仓库的 zip 文件中获取源代码

让我们看看 conanfile.py 中的变化

from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout
from conan.tools.files import get


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 source(self):
        # Please, be aware that using the head of the branch instead of an immutable tag
        # or commit is a bad practice and not allowed by Conan
        get(self, "https://github.com/conan-io/libhello/archive/refs/heads/main.zip",
                  strip_root=True)

    def config_options(self):
        if self.settings.os == "Windows":
            del self.options.fPIC

    def layout(self):
        cmake_layout(self)

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

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

    def package(self):
        cmake = CMake(self)
        cmake.install()

    def package_info(self):
        self.cpp_info.libs = ["hello"]

如你所见,recipe 是相同的,但没有像之前那样声明 exports_sources 属性,例如

exports_sources = "CMakeLists.txt", "src/*", "include/*"

我们使用这些信息声明了一个 source() 方法

def source(self):
    # Please, be aware that using the head of the branch instead of an immutable tag
    # or commit is strongly discouraged, unsupported by Conan and likely to cause issues
    get(self, "https://github.com/conan-io/libhello/archive/refs/heads/main.zip",
              strip_root=True)

我们使用了 conan.tools.files.get() 工具,它会首先从我们作为参数传递的 URL 下载 zip 文件,然后解压缩。注意,我们传递了 strip_root=True 参数,这样如果所有解压缩的内容都在一个单独的文件夹中,所有内容都会移动到父文件夹(详情请查看 conan.tools.files.unzip() 参考文档)。

警告

期望将来获取源代码时产生相同的结果。强烈不鼓励且不支持使用可变的源,例如 git 中的移动引用(例如 HEAD 分支)或内容可能随时间变化的文件 URL。不遵循此做法将导致未定义行为,可能导致中断。

zip 文件的内容与我们之前放在 Conan recipe 旁边的源代码相同,因此如果你执行 conan create,结果将与之前相同。

$ conan create .

...

-------- Installing packages ----------

Installing (downloading, building) binaries...
hello/1.0: Calling source() in /Users/user/.conan2/p/0fcb5ffd11025446/s/.
Downloading update_source.zip

hello/1.0: Unzipping 3.7KB
Unzipping 100 %
hello/1.0: Copying sources to build folder
hello/1.0: Building your package in /Users/user/.conan2/p/tmp/369786d0fb355069/b

...

-------- Testing the package: Running test() ----------
hello/1.0 (test package): Running test()
hello/1.0 (test package): RUN: ./example
hello/1.0: Hello World Release!
hello/1.0: __x86_64__ defined
hello/1.0: __cplusplus199711
hello/1.0: __GNUC__4
hello/1.0: __GNUC_MINOR__2
hello/1.0: __clang_major__13
hello/1.0: __clang_minor__1
hello/1.0: __apple_build_version__13160021

请检查高亮显示的关于下载和解压缩操作消息的行。

git 仓库的分支获取源代码

现在,让我们修改 source() 方法,以便从 git 仓库而不是 zip 文件获取源代码。我们只展示相关部分

...

from conan.tools.scm import Git


class helloRecipe(ConanFile):
    name = "hello"
    version = "1.0"

    ...

    def source(self):
        git = Git(self)
        git.clone(url="https://github.com/conan-io/libhello.git", target=".")

    ...

在这里,我们使用了 conan.tools.scm.Git() 工具。Git 类实现了多种处理 git 仓库的方法。在这种情况下,我们调用了 clone() 方法,将 https://github.com/conan-io/libhello.git 仓库克隆到默认分支,使用与源代码相同的文件夹而不是子文件夹(通过传递 target="." 参数)。

警告

如上所述,这只是一个简单示例。Git() 的源也必须是不可变的。需要检出不可变的标签或特定的提交以保证正确行为。不允许使用仓库的 HEAD,这可能导致未定义行为和中断。

要在仓库中检出提交或标签,我们使用 Git 工具的 checkout() 方法

def source(self):
    git = Git(self)
    git.clone(url="https://github.com/conan-io/libhello.git", target=".")
    git.checkout("<tag> or <commit hash>")

有关 Git 类方法的更多信息,请查看 conan.tools.scm.Git() 参考文档。

请注意,也可以通过调用 self.run() 方法运行其他命令。

使用 conandata.yml 文件

我们可以与 conanfile.py 文件在同一文件夹中创建一个名为 conandata.yml 的文件。此文件将由 Conan 自动导出和解析,我们可以从 recipe 中读取这些信息。这非常方便,例如,用于提取外部源代码仓库的 URL、zip 文件等。以下是 conandata.yml 的一个示例

sources:
  "1.0":
    url: "https://github.com/conan-io/libhello/archive/refs/heads/main.zip"
    sha256: "7bc71c682895758a996ccf33b70b91611f51252832b01ef3b4675371510ee466"
    strip_root: true
  "1.1":
    url: ...
    sha256: ...

recipe 不需要针对每个版本的代码进行修改。我们可以将指定版本的所有 keysurlsha256strip_root)作为参数传递给 get 函数,在这种情况下,这使我们能够验证下载的 zip 文件具有正确的 sha256。因此,我们可以将 source 方法修改为这样

def source(self):
    get(self, **self.conan_data["sources"][self.version])
    # Equivalent to:
    # data = self.conan_data["sources"][self.version]
    # get(self, data["url"], sha256=data["sha256"], strip_root=data["strip_root"])

另请参阅