捕获 Git scm 信息

在配方中处理源代码主要有两种策略

  • 第三方代码:当 conanfile.py 配方打包第三方代码时,例如开源库,通常最好使用 source() 方法下载或克隆该库的源代码。 这是 ConanCenter 的 conan-center-index 仓库所遵循的方法。

  • 您自己的代码:当 conanfile.py 配方打包您自己的代码时,通常最好将 conanfile.py 与源代码放在同一个仓库中。 然后,实现可重现性有 2 种替代方案

    • 使用 exports_sources(或 export_source() 方法)将源代码的副本与 Conan 包中的配方一起捕获。 这非常简单实用,建议在大多数情况下使用。

    • 对于无法将源代码与 Conan 配方一起存储的情况,例如当软件包要供不应完全访问源代码的人使用时,那么当前的 scm 捕获 方法将是可行的方法。

scm 捕获 方法中,不是捕获代码本身的副本,而是捕获该代码的“坐标”,在 Git 的情况下,捕获仓库的 urlcommit。 如果配方需要从源代码构建,它将使用该信息来获取克隆,如果尝试这样做的用户未获得授权,则该过程将失败。 他们仍然可以使用我们分发的预编译二进制文件,但不能从源代码构建或访问代码。

让我们看看它如何通过一个例子工作。 请首先克隆源代码以重新创建此项目。 您可以在 GitHub 上的 examples2 仓库 中找到它们

$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/examples/tools/scm/git/capture_scm

在那里我们会找到一个小的 “hello” 项目,其中包含这个 conanfile.py

from conan import ConanFile
from conan.tools.cmake import CMake, cmake_layout
from conan.tools.scm import Git


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

    # Binary configuration
    settings = "os", "compiler", "build_type", "arch"
    options = {"shared": [True, False], "fPIC": [True, False]}
    default_options = {"shared": False, "fPIC": True}
    generators = "CMakeDeps", "CMakeToolchain"

    def export(self):
        git = Git(self, self.recipe_folder)
        # save the url and commit in conandata.yml
        git.coordinates_to_conandata()

    def source(self):
        # we recover the saved url and commit from conandata.yml and use them to get sources
        git = Git(self)
        git.checkout_from_conandata_coordinates()

    ...

我们需要这段代码在它自己的 Git 仓库中,才能看到它在实际情况下的工作方式,所以请在 examples2 仓库之外创建一个文件夹,并将当前文件夹的内容复制到那里,然后

$ mkdir /home/myuser/myfolder # or equivalent in other OS
$ cp -R . /home/myuser/myfolder # or equivalent in other OS
$ cd /home/myuser/myfolder # or equivalent in other OS

# Initialize the git repo
$ git init .
$ git add .
$ git commit . -m wip
# Finally create the package
$ conan create .
...
======== Exporting recipe to the cache ========
hello/0.1: Exporting package recipe: /myfolder/conanfile.py
hello/0.1: Calling export()
hello/0.1: RUN: git status . --short --no-branch --untracked-files
hello/0.1: RUN: git rev-list HEAD -n 1 --full-history -- "."
hello/0.1: RUN: git remote -v
hello/0.1: RUN: git branch -r --contains cb7815a58529130b49da952362ce8b28117dee53
hello/0.1: RUN: git fetch origin --dry-run --depth=1 cb7815a58529130b49da952362ce8b28117dee53
hello/0.1: WARN: Current commit cb7815a58529130b49da952362ce8b28117dee53 doesn't exist in remote origin
This revision will not be buildable in other computer
hello/0.1: RUN: git rev-parse --show-toplevel
hello/0.1: Copied 1 '.py' file: conanfile.py
hello/0.1: Copied 1 '.yml' file: conandata.yml
hello/0.1: Exported to cache folder: /.conan2/p/hello237d6f9f65bba/e
...
======== Installing packages ========
hello/0.1: Calling source() in /.conan2/p/hello237d6f9f65bba/s
hello/0.1: Cloning git repo
hello/0.1: RUN: git clone "<hidden>"  "."
hello/0.1: Checkout: cb7815a58529130b49da952362ce8b28117dee53
hello/0.1: RUN: git checkout cb7815a58529130b49da952362ce8b28117dee53

让我们逐步解释正在发生的事情

  • 当配方导出到 Conan 缓存时,export() 方法执行,git.coordinates_to_conandata(),它通过内部调用 git.get_url_and_commit() 将 Git URL 和 commit 存储在 conandata.yml 文件中。 有关这些方法的更多信息,请参阅 Git 参考

  • 这会获取指向本地 <local-path>/capture_scm 的仓库 URL 和 commit 8e8764c40bebabbe3ec57f9a0816a2c8e691f559

  • 它警告说,一旦软件包上传到服务器并尝试在其他不包含 <local-path>/capture_scm 指向的路径的计算机上从源代码构建此配方,此信息将不足以从源代码重新构建。 这是预期的,因为我们创建的仓库没有任何远程定义。 如果我们的本地克隆定义了远程仓库,并且该远程仓库包含我们正在构建的 commit,则 scm_url 将指向远程仓库,从而使从源代码构建完全可重现。

  • export() 方法将 urlcommit 信息存储在 conandata.yml 中,以供将来重现。

  • 当需要从源代码构建软件包并且它调用 source() 方法时,它会从 git.checkout_from_conandata_coordinates() 方法内的 conandata.yml 文件中恢复信息,该方法在内部调用 git.clone() 并使用该信息来检索源代码。 在这种情况下,它将从 <local-path>/capture_scm 中的本地检出克隆,但如果它定义了远程仓库,它将从远程仓库克隆。

警告

为了实现可重现性,对于这种 scm 捕获 技术而言,当前的检出不应该是脏的,这一点非常重要。 如果它是脏的,则无法保证未来构建的可重现性,因此 git.get_url_and_commit() 可能会引发错误,并要求提交更改。 如果需要超过 1 个 commit,建议在将更改推送到上游仓库之前 squash 这些 commit。

如果我们现在执行第二次 conan create .,由于仓库是脏的,我们将得到

$ conan create .
hello/0.1: Calling export()
ERROR: hello/0.1: Error in export() method, line 19
    scm_url, scm_commit = git.get_url_and_commit()
    ConanException: Repo is dirty, cannot capture url and commit: .../capture_scm

这可以通过使用 git clean -xdf 清理仓库来解决,或者通过向仓库添加一个 .gitignore 文件,其中包含以下内容(无论如何,这对于源代码控制来说可能是一个好的做法)

.gitignore
test_package/build
test_package/CMakeUserPresets.json

坐标捕获使用 Git.get_url_and_commit() 方法,默认情况下会执行

  • 如果仓库是脏的,它将引发异常

  • 如果仓库不是脏的,但 commit 在远程仓库中不存在,它将发出警告,但它将返回本地文件夹作为仓库 url。 这样,可以在不需要将本地 commit 推送到服务器的情况下对其进行测试。 core.scm:local_url=allow 可以消除警告,而 core.scm:local_url=block 将立即引发错误:最后一个值对于 CI 场景可能很有用,可以快速失败并节省稍后在 conan upload 中会被阻止的构建。

  • 如果尝试使用 conan upload 将使用本地 commit 构建的软件包上传到服务器,将会失败,因为这些本地 commit 不在服务器中,然后软件包可能无法重现。 可以通过设置 core.scm:local_url=allow 来避免此上传错误。

  • 如果仓库不是脏的,并且 commit 在服务器中存在,它将返回远程 URL 和 commit。

凭据管理

在上面的示例中,凭据不是必需的,因为我们的本地仓库不需要它们。 但在实际场景中,可能需要凭据。

第一个重要的点是 git.get_url_and_commit() 将捕获 origin 远程仓库的 url。 出于多种原因,此 url 不得编码令牌、用户或密码。 首先,因为这将使过程不可重复,并且不同的构建,不同的用户将获得不同的 url,因此导致不同的配方修订。 url 应始终相同。 推荐的方法是以正交方式管理凭据,例如使用 ssh 密钥。 提供的示例包含一个执行此操作的 Github 操作

.github/workflows/hello-demo.yml
name: Build "hello" package capturing SCM in Github actions
run-name: ${{ github.actor }} checking hello-ci Git scm capture
on: [push]
jobs:
Build:
    runs-on: ubuntu-latest
    steps:
    - name: Check out repository code
        uses: actions/checkout@v3
        with:
        ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
    - uses: actions/setup-python@v4
        with:
        python-version: '3.10'
    - uses: webfactory/[email protected]
        with:
        ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
    - run: pip install conan
    - run: conan profile detect
    - run: conan create .

hello-demo.yml 负责以下事项

  • 检出 actions/checkout@v3 操作接收 ssh-keygit@ 而不是 https 的方式检出

  • webfactory/[email protected] 操作负责在后续任务执行期间也激活 ssh 密钥,而不仅是在检出期间。

  • 有必要在 Github 界面中设置 SSH_PRIVATE_KEY 密钥,以及仓库的 deploy key(包含 ssh 密钥的私钥和公钥部分)

通过这种方式,可以将身份验证和凭据与配方功能完全分离,而不会有泄露凭据的风险。

注意

最佳实践

  • 不要使用在 url 中编码信息的身份验证机制。 这很冒险,很容易在日志中泄露凭据。 建议使用 ssh 密钥等系统机制。

  • 不建议在本地开发中使用 conan create,而是运行 conan install 并在本地构建,以避免过多不必要的提交。 只有当一切在本地工作时,才开始检查 conan create 流程。