捕获 Git scm 信息¶
在 recipe 中处理源代码主要有两种策略
第三方代码:当
conanfile.pyrecipe 用于打包第三方代码(例如开源库)时,通常最好使用source()方法来下载或克隆该库的源代码。这是conan-center-index仓库针对 ConanCenter 所遵循的方法。您自己的代码:当
conanfile.pyrecipe 用于打包您自己的代码时,通常最好将conanfile.py与源代码放在同一个仓库中。然后,有两种方法可以实现可重现性:使用
exports_sources(或export_source()方法)将源代码的副本与 recipe 一起捕获到 Conan 包中。这非常简单实用,并且推荐用于大多数情况。当无法将源代码存储在 Conan recipe 旁边时(例如,当包的消费者根本不应访问源代码时),那么当前的 **scm 捕获** 方法将是合适的方式。
在 **scm 捕获** 方法中,不是捕获代码本身的副本,而是捕获该代码的“坐标”。在 Git 的情况下,捕获的是仓库的 url 和 commit。如果 recipe 需要从源代码构建,它将使用这些信息来获取克隆;如果尝试构建的用户没有授权,该过程将失败。他们仍然能够使用我们分发的预编译二进制文件,但无法从源代码构建或访问代码。
让我们通过一个例子来看看它是如何工作的。请首先克隆源代码以重新创建此项目。您可以在 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
让我们一步一步地解释正在发生的事情。
当 recipe 被导出到 Conan 缓存时,
export()方法执行git.coordinates_to_conandata(),它通过内部调用git.get_url_and_commit()将 Git URL 和 commit 存储在conandata.yml文件中。有关这些方法的更多信息,请参阅 Git 参考。这会获取指向本地
<local-path>/capture_scm的仓库 URL 和 commit8e8764c40bebabbe3ec57f9a0816a2c8e691f559。它会发出警告,即一旦包被上传到服务器并在其他计算机上尝试从源代码构建,这些信息**不足以**从源代码重新构建该 recipe,因为其他计算机将不包含
<local-path>/capture_scm指向的路径。这是意料之中的,因为我们创建的仓库没有任何远程定义。如果我们的本地克隆定义了远程,并且该远程包含我们正在构建的commit,那么scm_url将指向远程仓库,从而使从源代码构建完全可重现。export()方法会将url和commit信息存储在conandata.yml中,以供将来重现。当需要从源代码构建包并调用
source()方法时,它会在git.checkout_from_conandata_coordinates()方法中从conandata.yml文件中恢复信息,该方法内部调用git.clone()来检索源代码。在这种情况下,它将从本地检出<local-path>/capture_scm进行克隆,但如果它定义了远程,它将从远程克隆。
警告
为了实现可重现性,对于这种 **scm 捕获** 技术而言,当前检出不应是脏状态(dirty)。如果它是脏状态,将无法保证将来的构建可重现性,因此 git.get_url_and_commit() 可能会引发错误,并要求提交更改。如果需要不止一个 commit,建议在将更改推送到上游仓库之前将这些 commit 合并(squash)。
如果我们现在再次运行 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 文件来解决(这无论如何都是一个好的源代码控制实践):
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中被阻止的构建。使用本地 commit 构建的包在尝试使用
conan upload上传到服务器时将失败,因为这些本地 commit 不在服务器上,因此包可能无法重现。可以通过设置core.scm:local_url=allow来避免此上传错误。如果仓库不是脏的,并且 commit 存在于服务器上,它将返回远程 URL 和 commit。
凭证管理¶
在上面的示例中,不需要凭证,因为我们的本地仓库不需要它们。但在实际场景中,可能需要凭证。
git.get_url_and_commit() 捕获 origin 远程的 URL。此 URL 不得编码 token、用户或密码,原因如下。首先,因为它会使过程不可重现,不同的构建、不同的用户将获得不同的 URL,因此也会获得不同的 recipe 版本。URL 应始终相同。推荐的方法是以外部方式管理凭证,例如使用 SSH 密钥。提供的示例包含一个执行此操作的 Github Action。
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/ssh-agent@v0.7.0
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-key作为git@而非https接收。webfactory/ssh-agent@v0.7.0操作确保 SSH 密钥在后续任务执行期间(不仅仅是在检出时)也处于激活状态。需要在 Github 界面中设置
SSH_PRIVATE_KEY秘密,以及仓库的deploy key(包含 SSH 密钥的私有和公共部分)。
这样,就可以完全将身份验证和凭证与 recipe 功能分开,而不会有泄露凭证的风险。
注意
最佳实践
请勿使用在 URL 中编码信息的身份验证机制。这很危险,很容易在日志中泄露凭证。建议使用 SSH 密钥等系统机制。
不推荐在本地开发时进行
conan create,而是运行conan install并在本地构建,以避免过多不必要的 commit。只有当本地一切正常后,才开始检查conan create的流程。