プロジェクトバージョンの単一ソース化

Page Status:Complete
Last Reviewed:2015-12-03

プロジェクトのバージョン番号について、単一ソースの原則を維持する多くの手法がある:

  1. setup.py を正規表現でパースしてバージョンを得る。例えば(pip の setup.py より):

    def read(*names, **kwargs):
        with io.open(
            os.path.join(os.path.dirname(__file__), *names),
            encoding=kwargs.get("encoding", "utf8")
        ) as fp:
            return fp.read()
    
    def find_version(*file_paths):
        version_file = read(*file_paths)
        version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
                                  version_file, re.M)
        if version_match:
            return version_match.group(1)
        raise RuntimeError("Unable to find version string.")
    
    setup(
       ...
       version=find_version("package", "__init__.py")
       ...
    )
    

    注釈

    この手法は複雑な正規表現を扱わなければならない欠点がある。

  2. 両方の場所で更新管理ができるか、もしくは両方の場所で使える API を持つ外部ビルドツールを使う。

    そんなツールはほとんどないが、挙げるとすれば bumpversion, changes, zest.releaser だ。この順序に特定の意図はないし、これで全てだとも限らない。

  3. プロジェクト内の専用モジュール(例えば version.py) でグローバル変数 __version__ の値を設定し、 setup.py がそれを読んで exec した値を変数に格納する。

    execfile を使う例:

    execfile('...sample/version.py')
    # now we have a `__version__` variable
    # later on we use: __version__
    

    exec を使う例:

    version = {}
    with open("...sample/version.py") as fp:
        exec(fp.read(), version)
    # later on we use: version['__version__']
    

    この手法を使っている例: warehouse

  4. 単純な VERSION というテキストファイルに値を格納し、 setup.py とプロジェクトコードの両方でそれを読む。

    with open(os.path.join(mypackage_root_dir, 'VERSION')) as version_file:
        version = version_file.read().strip()
    

    この手法の利点は Python に特化していないことだ。どのツールでもバージョンを読み取れる。

    警告

    この手法をとる場合、 VERSION ファイルが全ての source / binary distributions に含まれていることを確認しなければならない(例えば、 MANIFEST.ininclude VERSION を追加するなど)。

  5. setup.py で値を設定し、プロジェクトコードでは pkg_resources API を使う。

    import pkg_resources
    assert pkg_resources.get_distribution('pip').version == '1.2.0'
    

    注意: pkg_resources API が認識するのはインストール環境のメタデータだけであり、これは必ずしも現在インポートされているコードとは限らない。

  6. sample/__init__.py__version__ の値を設定し、 setup.pysample をインポートする。

    import sample
    setup(
        ...
        version=sample.__version__
        ...
    )
    

    この手法はよく使われているが、 sample/__init__.pyinstall_requires 依存関係からパッケージをインポートしていると失敗しうる。 setup.py の実行時点では、その依存関係はまだインストールされていない可能性が高いからだ。

  7. コードではなくバージョン管理システム (Git, Mercurial など)のタグにバージョン番号を保管し、 setuptools_scm を使って自動抽出する。