从源文件获取conda meta.yaml的软件包版本号

13

我想重新组织我的Python包版本控制,这样我只需在一个地方更新版本号,最好是在一个Python模块或文本文件中。对于所有需要使用版本号的地方,似乎都可以从源代码中加载它:from mypkg import __version__,或者至少将其解析为文本文件。但我似乎找不到一种方法来在conda meta.yaml文件中实现这一点。有没有办法从外部源加载版本号到meta.yaml文件中呢?

我知道有Git环境变量,但我不想为每个经过本地conda存储库测试的alpha/beta/rc提交打标签。我可以使用pyyaml中的!!python/object来加载Python对象,但conda不支持任意的Python执行。我也没有看到任何其他Jinja2功能能够实现。我可以编写一个脚本来更新多个地方的版本号,但我真的希望只修改一个文件作为确定性版本号。感谢任何帮助。


提供给任何在更好的答案出现之前查看此内容的人:我已经决定使用git标签,并在我的setup.py中添加了一个特殊命令,用于在一些打包工具(如Windows的Inno Setup)中升级版本,同时更新version.py文件并进行必要的git提交和标签。 - djhoese
3个回答

15
自从conda-build-3.16.1(2018年11月)以来,以下是编程设置conda recipe内的version的方法。这些示例是您传递给conda-buildmeta.yaml的一部分,如此处所述。
A. 利用setup.py的版本: 如果你构建一个python包,那么这个配方非常完美,因为setup.py无论如何都需要它,所以您肯定已经想到了这个方法。
{% set data = load_setup_py_data() %}

package:
  name: mypackage
  version: {{ data.get('version') }}

请注意,有时您必须明确告诉conda配方在哪里找到它,如果它不在与setup.py相同的目录中:

{% set data = load_setup_py_data(setup_file='../setup.py', from_recipe_dir=True) %}

现在可以继续进行:

$ conda-build conda-recipe

B. Git环境变量

如果您的项目被标记在git上,并且您使用conda接受的有效版本号标签格式(例如2.5.1v2.5.1),则此方法非常有用。


package:
  name: hub
  version: {{ GIT_DESCRIBE_TAG }}

现在继续进行:

$ conda-build conda-recipe

C. 传递env变量:

这对于非Python conda包非常有用,其中版本来自各种不同的地方,并且您可以完善其值 - 例如将v2.5.1转换为2.5.1


package:
  name: mypkg
  version: {{ environ.get('MYPKG_VERSION', '') }}

然后创建一个可执行脚本来获取版本,我们称之为 script-to-get-mypkg-version

现在继续加载设置版本的环境变量:

$ MYPKG_VERSION=`script-to-get-mypkg-version` conda-build conda-recipe

根据conda-build版本的不同,您可能需要使用os.environ.get而不是environ.get文档使用后者。


这不起作用

请注意,如果此操作过去曾起作用(如2016年某个答案所述),现在已不再起作用。

package:
  name: mypkg
build:
  script_env:
    - VERSION

$ VERSION=`script-to-get-mypkg-version` conda-build conda-recipe
conda-build 在这种情况下忽略了环境变量 VERSION来源

对于C语言,我认为应该使用os.environ.getenviron.get对我没有起作用。 - hrzafer
谢谢,增加了这个可能性。文档在没有os的情况下使用它,对我来说也可以正常工作,也许他们最近停止了从os导入。 - stason

4

有很多方法可以到达您的终点。以下是conda本身的操作...

conda版本信息的真实来源是conda/__init__.py中的__version__。正如您所建议的那样,它可以在Python代码中以编程方式加载,例如from conda import __version__。它还被硬编码到setup.py(此处)(请注意此代码),因此从命令行运行python setup.py --version是获取该信息的规范方式。

在conda-build的1.x版本中,添加一行

$PYTHON setup.py --version > __conda_version__.txt

build.sh中,我们将使用我们的真实数据源来设置构建软件包的版本。然而,__conda_version__.txt文件已被弃用,并且很可能会在conda-build 2.0发布时被删除。在最近的conda-build版本中,推荐的方法是在jinja2上下文中使用load_setup_py_data(),这将使您可以访问setup.py中的所有元数据。具体来说,在meta.yaml文件中,我们将有以下内容

package:
  name: conda
  version: "{{ load_setup_py_data().version }}"

现在,__version__变量是如何在conda/__init__.py中设置的...

在源代码中看到的是一个对auxlib.packaging.get_version()函数的调用。该函数按顺序执行以下操作:

  1. 首先查找文件conda/.version,如果找到则将其内容作为版本标识符返回
  2. 接下来查找VERSION环境变量,如果设置了则将其值作为版本标识符返回
  3. 最后查找git describe --tags输出,并在可能的情况下返回版本标识符(必须安装git,必须是git仓库等等)
  4. 如果以上都没有得到版本标识符,则返回None
现在只剩下最后一个小技巧。在conda的setup.py文件中,我们将build_pysdistcmdclass设置为由auxlib.packaging提供的内容。基本上我们有:
from auxlib import packaging
setup(
    cmdclass={
        'build_py': packaging.BuildPyCommand,
        'sdist': packaging.SDistCommand,
    }
)

这些特殊的命令类实际上会修改内置/安装包中的conda/__init__.py文件,使得__version__变量硬编码为一个字符串字面值,并且不使用auxlib.packaging.get_version()函数。
在您的情况下,如果不想为每个发布版本打标签,您可以使用上述所有方法,并从命令行使用“VERSION”环境变量设置版本。类似于:
VERSION=1.0.0alpha1 conda build conda.recipe

在您的meta.yaml配方的build部分中,您需要添加一个script_env键,以便告诉conda-build将VERSION环境变量传递到构建环境中。请保留HTML标签。
build:
  script_env:
    - VERSION

我想补充一下,在 https://github.com/conda/conda-build/tree/master/tests/test-recipes/metadata/source_setup_py_data 中有这方面的例子,并且这适用于在 setup.py 中设置版本的任何方式 - versioneer、setuptools-scm 或其他方式。还要注意,load_setup_py_data 曾经被命名为 load_setuptools。这在 1.21.12 中已经改变。现在两个名称都是有效的,但是 load_setuptools 将输出一个关于被弃用的警告消息。 - msarahan
太棒了。从load_setup_py_data的PR来看,它似乎还没有很长时间可用(至少现在这个名字)。感谢您的帮助,我今天会试一下。但是在此期间,如果anaconda软件包出现错误报告(如段错误等),应该放在conda/conda还是ContinuumIO/anaconda-recipes中? - djhoese
软件包的错误报告通常应该提交到ContinuumIO/anaconda-issues。如果您对某个配方有具体的改进意见,请在ContinuumIO/anaconda-recipes上提交PR或问题。conda/conda专门用于处理conda这个软件包管理工具的问题。 - msarahan
@msarahan 我也是这么想的,但似乎conda/conda在软件包方面存在很多问题,我不太确定。 - djhoese
@kalefranz 感谢您告诉我关于 load_setup_py_data 的信息,这正是我所需要的。有没有计划将其记录下来? - djhoese
显示剩余2条评论

1

手册 __version__

如果您的版本在单独的_version.py文件中,可以在不加载整个包的情况下导入。

# coding: utf-8
# file generated by setuptools_scm
# don't change, don't track in version control
version = '0.0.9.post2+g6481728.d20200518.dirty'

在我的情况下,这是自动生成的,但下一步保持不变。
__init__.py中,您有一行代码from ._version import version as __version__ 然后在setup.py中,您可以这样做。这也是我在sphinx的conf.py中导入版本的方式。
source_dir = Path("src/<my_package>")
sys.path.insert(0, str(source_dir))

from _version import  version

setup(version=version)
...

另外,除了导入_version文件之外,您还可以尝试手动解析它,这样您就不必将任何内容添加到sys.path中。

然后在meta.yaml中进行操作。

{% set data = load_setup_py_data() %}
{% set version = data.get('version')  %}


package:
  name: <my_package>
  version: {{ version }}

我遇到了相反的问题。我忘记定期更新我的版本,所以一直在寻找将git仓库作为软件包版本的单一来源的方法。我使用了setuptools_scm
我尝试了很多事情,有些使用了符合pep517标准的pyproject.toml,有些没有,但最终这是适合我的方法。
优点是你不需要使用那个庞大的versioneer.py,而是在构建时写入到_version.py中。

setup.py

from setuptools import setup
import setuptools_scm


def my_local_scheme(version: setuptools_scm.version.ScmVersion) -> str:
    """My local node and date version."""
    node_and_date = setuptools_scm.version.get_local_node_and_date(version)
    dirty = ".dirty" if version.dirty else ""
    return str(node_and_date) + dirty

version = setuptools_scm.get_version(
    write_to="src/<my_package>/_version.py",
    version_scheme="post-release",
    local_scheme=my_local_scheme,
)
setup(version=version,)

setup.cfg中,还有关于setup()元数据和选项的其他内容。其中一个必须存在的是:
[options]
package_dir=
    =src
packages = <my_package>
install_requires = setuptools_scm

src/<my_package>/_version.py

生成的结果是:

# coding: utf-8
# file generated by setuptools_scm
# don't change, don't track in version control
version = '0.0.3.post0+g887e418.d20200518.dirty'

我将其添加到我的.gitignore文件中。

src/<my_package>/__init__.py

"""<package_description>"""
from ._version import version as  __version__

meta.yaml

{% set data = load_setup_py_data() %}
{% set version = data.get('version')  %}


package:
  name: capacity_simulation
  version: {{ version }}

source:
  path: .

build:
  noarch: python
  number: {{ environ.get('GIT_DESCRIBE_NUMBER', 0) }}
  script: python -m pip install --no-deps --ignore-installed .
  include_recipe: False

requirements:
  build:
    - setuptools_scm
...

pyproject.toml

为了能够使用 pip wheel .

您需要在 pyproject.toml 中添加此部分内容。

[build-system]
requires = ["setuptools>=34.4", "wheel", "setuptools_scm"]
build-backend = "setuptools.build_meta"

那么针对我的原始问题的答案,你是在重复@stason在他们的答案中所说的(A部分),对吗? - djhoese
是的,但是setup.py获取版本的源自动化来自CVS。我已经找了很久了,所以我想分享我的解决方案。如果您在__init__.py中手动设置它,则可以在setup.py中加载它。 - Maarten Fabré
看起来我有点误读了你的问题。我添加了如何从另一个文件获取它的内容。我在我的Sphinx conf.py中使用它。 - Maarten Fabré

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接