Resolver internalsリゾルバ内部
Tipヒント
This document focuses on the internal workings of uv's resolver. For using uv, see the resolution concept documentation.このドキュメントは、uvのリゾルバの内部動作に焦点を当てています。uvの使用については、解決の概念に関するドキュメントを参照してください。
Resolverリゾルバ
As defined in a textbook, resolution, or finding a set of version to install from a given set of requirements, is equivalent to the SAT problem and thereby NP-complete: in the worst case you have to try all possible combinations of all versions of all packages and there are no general, fast algorithms. In practice, this is misleading for a number of reasons:教科書で定義されているように、解決、または与えられた要件のセットからインストールするバージョンのセットを見つけることは、SAT問題と同等であり、したがってNP完全です:最悪の場合、すべてのパッケージのすべてのバージョンのすべての可能な組み合わせを試す必要があり、一般的で迅速なアルゴリズムは存在しません。実際には、これはいくつかの理由から誤解を招くものです:
- The slowest part of resolution in uv is loading package and version metadata, even if it's cached.uvにおける解決の最も遅い部分は、パッケージとバージョンのメタデータを読み込むことであり、キャッシュされていてもです。
- There are many possible solutions, but some are preferable to others. For example, we generally prefer using the latest version of packages.多くの可能な解決策がありますが、いくつかは他のものよりも好まれます。たとえば、一般的にパッケージの最新バージョンを使用することを好みます。
- Package dependencies are complex, e.g., there are contiguous versions ranges — not arbitrary boolean inclusion/exclusions of versions, adjacent releases often have the same or similar requirements, etc.パッケージの依存関係は複雑であり、連続したバージョン範囲が存在します — バージョンの任意のブールの包含/除外ではなく、隣接するリリースはしばしば同じまたは類似の要件を持っています。
- For most resolutions, the resolver doesn't need to backtrack, picking versions iteratively is sufficient. If there are version preferences from a previous resolution, barely any work needs to be done.ほとんどの解決において、リゾルバはバックトラックする必要がなく、バージョンを反復的に選択するだけで十分です。以前の解決からのバージョンの優先順位がある場合、ほとんど作業は必要ありません。
- When resolution fails, more information is needed than a message that there is no solution (as is seen in SAT solvers). Instead, the resolver should produce an understandable error trace that states which packages are involved in away to allows a user to remove the conflict.解決が失敗した場合、解決策がないというメッセージ以上の情報が必要です(SATソルバーで見られるように)。代わりに、リゾルバは、どのパッケージが関与しているかを示す理解可能なエラートレースを生成し、ユーザーが競合を解消できるようにするべきです。
- The most important heuristic for performance and user experience is determining the order in which decisions are made through prioritization.パフォーマンスとユーザーエクスペリエンスにとって最も重要なヒューリスティックは、優先順位付けを通じて意思決定の順序を決定することです。
uv uses pubgrub-rs, the Rust implementation of PubGrub, an incremental version solver. PubGrub in uv works in the following steps:uvは、pubgrub-rs、Rust実装のPubGrub、インクリメンタルバージョンソルバーを使用しています。uvにおけるPubGrubは、以下のステップで動作します:
- Start with a partial solution that declares which packages versions have been selected and which are undecided. Initially, only a virtual root package is decided.部分的な解決策から始めて、どのパッケージのバージョンが選択され、どれが未決定であるかを宣言します。最初は、仮想ルートパッケージのみが決定されます。
- The highest priority package is selected from the undecided packages. Roughly, packages with URLs
(including file, git, etc.) have the highest priority, then those with more exact specifiers (such
as
==), then those with less strict specifiers. Inside each category, packages are ordered by when they were first seen (i.e. order in a file), making the resolution deterministic.最も優先度の高いパッケージが未決定のパッケージから選択されます。大まかに言えば、URL(ファイル、gitなどを含む)を持つパッケージが最も優先度が高く、次により正確な指定子(==など)を持つもの、そしてより緩やかな指定子を持つものが続きます。各カテゴリ内では、パッケージは最初に見られた順(つまり、ファイル内の順序)で並べられ、解決が決定論的になります。 - A version is picked for the selected package. The version must works with all specifiers from the
requirements in the partial solution and must not be previously marked as incompatible. The
resolver prefers versions from a lockfile (
uv.lockor-o requirements.txt) and those installed in the current environment. Versions are checked from highest to lowest (unless using an alternative resolution strategy).選択されたパッケージのバージョンが選ばれます。このバージョンは、部分的な解決策の要件からのすべての指定子と互換性があり、以前に互換性がないとマークされていない必要があります。リゾルバは、ロックファイル(uv.lockまたは-o requirements.txt)からのバージョンと、現在の環境にインストールされているバージョンを優先します。バージョンは高いものから低いものへとチェックされます(代替の解決戦略を使用しない限り)。 - All requirements of the selected package version are added to the undecided packages. uv prefetches their metadata in the background to improve performance.選択されたパッケージバージョンのすべての要件が未決定のパッケージに追加されます。uvは、パフォーマンスを向上させるために、バックグラウンドでそのメタデータをプリフェッチします。
- The process is either repeated with the next package unless a conflict is detected, in which the
resolver will backtrack. For example, the partial solution contains, among other packages,
a 2thenb 2with the requirementsa 2 -> c 1andb 2 -> c 2. No compatible version ofccan be found. PubGrub can determine this was caused bya 2andb 2and add the incompatibility{a 2, b 2}, meaning that when either is picked, the other cannot be selected. The partial solution is restored toa 2with the tracked incompatibility and the resolver attempts to pick a new version forb.プロセスは、次のパッケージで繰り返されるか、競合が検出された場合にはリゾルバがバックトラックします。例えば、部分的な解決策には、他のパッケージの中にa 2が含まれ、次にb 2がa 2 -> c 1およびb 2 -> c 2という要件を持っています。cの互換性のあるバージョンは見つかりません。PubGrubは、これがa 2とb 2によって引き起こされたことを特定し、互換性のないことを{a 2, b 2}として追加します。これは、どちらかが選ばれると、もう一方は選べないことを意味します。部分的な解決策は、追跡された互換性のない状態でa 2に復元され、リゾルバはbの新しいバージョンを選ぼうとします。
Eventually, the resolver either picks compatible versions for all packages (a successful resolution) or there is an incompatibility including the virtual "root" package which defines the versions requested by the user. An incompatibility with the root package indicates that whatever versions of the root dependencies and their transitive dependencies are picked, there will always be a conflict. From the incompatibilities tracked in PubGrub, an error message is constructed to enumerate the involved packages.最終的に、リゾルバはすべてのパッケージに対して互換性のあるバージョンを選択するか(成功した解決)、ユーザーが要求したバージョンを定義する仮想「ルート」パッケージを含む互換性のない状態になります。ルートパッケージとの互換性のない状態は、ルート依存関係およびその遷移依存関係のどのバージョンが選ばれても、常に競合が発生することを示します。PubGrubで追跡された互換性のない状態から、関与するパッケージを列挙するエラーメッセージが構築されます。
Tipヒント
For more details on the PubGrub algorithm, see Internals of the PubGrub algorithm.PubGrubアルゴリズムの詳細については、PubGrubアルゴリズムの内部を参照してください。
In addition to PubGrub's base algorithm, we also use a heuristic that backtracks and switches the order of two packages if they have been conflicting too much.PubGrubの基本アルゴリズムに加えて、競合が多すぎる場合にバックトラックして2つのパッケージの順序を切り替えるヒューリスティックも使用します。
Forkingフォーク
Python resolvers historically didn't support backtracking, and even with backtracking, resolution was usually limited to single environment, which one specific architecture, operating system, Python version, and Python implementation. Some packages use contradictory requirements for different environments, for example:Pythonのリゾルバは歴史的にバックトラックをサポートしておらず、バックトラックがあっても、解決は通常、特定のアーキテクチャ、オペレーティングシステム、Pythonバージョン、およびPython実装を持つ単一の環境に制限されていました。一部のパッケージは、異なる環境に対して矛盾する要件を使用します。例えば:
Since Python only allows one version of each package, a naive resolver would error here. Inspired by Poetry, uv uses a forking resolver: whenever there are multiple requirements for a package with different markers, the resolution is split.Pythonは各パッケージのバージョンを1つしか許可しないため、単純なリゾルバではここでエラーが発生します。Poetryに触発されて、uvはフォークリゾルバを使用します:異なるマーカーを持つパッケージに対して複数の要件がある場合、解決は分割されます。
In the above example, the partial solution would be split into two resolutions, one for
python_version >= "3.11" and one for python_version < "3.11".上記の例では、部分的な解決策は2つの解決に分割されます。1つはpython_version >= "3.11"、もう1つはpython_version < "3.11"です。
If markers overlap or are missing a part of the marker space, the resolver splits additional times — there can be many forks per package. For example, given:マーカーが重複している場合やマーカー空間の一部が欠けている場合、リゾルバはさらに分割されます — パッケージごとに多くのフォークが存在する可能性があります。例えば、次のような場合です:
A fork would be created for sys_platform == 'darwin', for sys_platform == 'win32', and for
sys_platform != 'darwin' and sys_platform != 'win32'.sys_platform == 'darwin'、sys_platform == 'win32'、およびsys_platform != 'darwin' and sys_platform != 'win32'のためにフォークが作成されます。
Forks can be nested, e.g., each fork is dependent on any previous forks that occurred. Forks with identical packages are merged to keep the number of forks low.フォークはネスト可能であり、各フォークは以前に発生したフォークに依存します。同一のパッケージを持つフォークは、フォークの数を少なく保つためにマージされます。
Tipヒント
Forking can be observed in the logs of uv lock -v by looking for
Splitting resolution on ..., Solving split ... (requires-python: ...) and Split ... resolution
took ....フォークはuv lock -vのログで観察でき、Splitting resolution on ...、Solving split ... (requires-python: ...)、およびSplit ... resolution took ...を探すことで確認できます。
One difficulty in a forking resolver is that where splits occur is dependent on the order packages
are seen, which is in turn dependent on the preferences, e.g., from uv.lock. So it is possible for
the resolver to solve the requirements with specific forks, write this to the lockfile, and when the
resolver is invoked again, a different solution is found because the preferences result in different
fork points. To avoid this, the resolution-markers of each fork and each package that diverges
between forks is written to the lockfile. When performing a new resolution, the forks from the
lockfile are used to ensure the resolution is stable. When requirements change, new forks may be
added to the saved forks.フォークリゾルバの難しさの1つは、分割が発生する場所がパッケージが見られる順序に依存することであり、これはuv.lockからの優先順位に依存します。したがって、リゾルバが特定のフォークで要件を解決し、これをロックファイルに書き込んだ場合、再度リゾルバが呼び出されると、優先順位が異なるために異なる解決策が見つかる可能性があります。これを避けるために、各フォークとフォーク間で分岐する各パッケージのresolution-markersがロックファイルに書き込まれます。新しい解決を行う際には、ロックファイルからフォークが使用され、解決が安定することが保証されます。要件が変更されると、新しいフォークが保存されたフォークに追加される場合があります。
Wheel tagsWheel tags
While uv's resolution is universal with respect to environment markers, this doesn't extend to wheel
tags. Wheel tags can encode the Python version, Python implementation, operating system, and
architecture. For example, torch-2.4.0-cp312-cp312-manylinux2014_aarch64.whl is only compatible
with CPython 3.12 on arm64 Linux with glibc>=2.17 (per the manylinux2014 policy), while
tqdm-4.66.4-py3-none-any.whl works with all Python 3 versions and interpreters on any operating
system and architecture. Most projects have a universally compatible source distribution that can be
used when attempted to install a package that has no compatible wheel, but some packages, such as
torch, don't publish a source distribution. In this case an installation on, e.g., Python 3.13, an
uncommon operating system, or architecture, will fail and complain that there is no matching wheel.uvの解決は環境マーカーに関しては普遍的ですが、これはwheelタグには拡張されません。WheelタグはPythonのバージョン、Pythonの実装、オペレーティングシステム、およびアーキテクチャをエンコードできます。例えば、torch-2.4.0-cp312-cp312-manylinux2014_aarch64.whlは、arm64 Linuxでglibc>=2.17を持つCPython 3.12とのみ互換性があります(manylinux2014ポリシーに従って)、一方でtqdm-4.66.4-py3-none-any.whlは、すべてのPython 3バージョンおよび任意のオペレーティングシステムとアーキテクチャのインタープリタで動作します。ほとんどのプロジェクトには、互換性のないwheelを持つパッケージをインストールしようとする際に使用できる普遍的に互換性のあるソース配布がありますが、torchのような一部のパッケージはソース配布を公開していません。この場合、例えばPython 3.13、珍しいオペレーティングシステム、またはアーキテクチャでのインストールは失敗し、一致するwheelがないと不満を言います。
Marker and wheel tag filteringMarker and wheel tag filtering
In every fork, we know what markers are possible. In non-universal resolution, we know their exact
values. In universal mode, we know at least a constraint for the python requirement, e.g.,
requires-python = ">=3.12" means that importlib_metadata; python_version < "3.10" can be
discarded because it can never be installed. If additionally tool.uv.environments is set, we can
filter out requirements with markers disjoint with those environments. Inside each fork, we can
additionally filter by the fork markers.すべてのフォークにおいて、可能なマーカーが何であるかを知っています。非ユニバーサル解決では、それらの正確な値を知っています。ユニバーサルモードでは、少なくともpython要件の制約を知っています。例えば、requires-python = ">=3.12"は、importlib_metadata; python_version < "3.10"を破棄できることを意味します。なぜなら、それは決してインストールされることがないからです。さらに、tool.uv.environmentsが設定されている場合、これらの環境と互換性のないマーカーを持つ要件をフィルタリングできます。各フォーク内では、フォークマーカーによってさらにフィルタリングできます。
There is some redundancy in the marker expressions, where the value of one marker field implies the
value of another field. Internally, we normalize python_version and python_full_version as well
as known values of platform_system and sys_platform to a shared canonical representation, so
they can match against each other.マーカー式には冗長性があり、あるマーカーフィールドの値が別のフィールドの値を暗示することがあります。内部的には、python_versionとpython_full_version、およびplatform_systemとsys_platformの既知の値を共有の標準表現に正規化するため、互いに一致させることができます。
When we selected a version with a local tag (e.g.,1.2.3+localtag) and the wheels don't cover
support for Windows, Linux and macOS, and there is a base version without tag (e.g.,1.2.3) with
support for a missing platform, we fork trying to extend the platform support by using both the
version with local tag and without local tag depending on the platform. This helps with packages
that use the local tag for different hardware accelerators such as torch. While there is no 1:1
mapping between wheel tags and markers, we can do a mapping for well-known platforms, including
Windows, Linux and macOS.ローカルタグ(例:1.2.3+localtag)を持つバージョンを選択し、ホイールがWindows、Linux、macOSのサポートをカバーしていない場合、タグなしの基本バージョン(例:1.2.3)が欠けているプラットフォームのサポートを持つ場合、プラットフォームに応じてローカルタグ付きとタグなしの両方のバージョンを使用してプラットフォームサポートを拡張しようとします。これは、torchのような異なるハードウェアアクセラレータのためにローカルタグを使用するパッケージに役立ちます。ホイールタグとマーカーの間には1:1のマッピングはありませんが、Windows、Linux、macOSなどのよく知られたプラットフォームのためのマッピングを行うことができます。
Metadata consistencyメタデータの一貫性
uv, similar to poetry, requires that wheels of a single version of a package in a specific index
have the same dependencies (Requires-Dist in METADATA), including wheels build from a source
distribution. More generally, uv assumes that each wheel has the same METADATA file in its
dist-info directory.uvは、poetryと同様に、特定のインデックス内のパッケージの単一バージョンのホイールが同じ依存関係(Requires-Dist in METADATA)を持つことを要求します。これは、ソース配布からビルドされたホイールも含まれます。一般的に、uvは各ホイールがそのdist-infoディレクトリに同じMETADATAファイルを持つと仮定しています。
numpy 2.3.2 for example has 73 wheels. Without this assumption, uv would have to make 73 network
requests to fetch its metadata, instead of a single one. Another problem we would have without
metadata consistency is the lack of a 1:1 mapping between markers and wheel tags. Wheel tags can
include the glibc version while the PEP 508 markers cannot represent it. If wheels had different
metadata, a universal resolver would have to track two dimensions simultaneously, PEP 508 markers
and wheel tags. This would increase complexity a lot, and the correspondence between the two is not
properly specified. PEP 508 markers have been introduced specifically to allow different
dependencies between different platform, i.e. to have a single dependency declaration for all
wheels, such as project.[optional-]dependencies. If the markers are not sufficient, we should
extend PEP 508 markers instead of using a parallel system of wheel tags.例えば、numpy 2.3.2は73のホイールを持っています。この仮定がなければ、uvはそのメタデータを取得するために73のネットワークリクエストを行わなければならず、1回のリクエストで済むところが、非常に非効率的になります。メタデータの一貫性がない場合の別の問題は、マーカーとホイールタグの間に1:1のマッピングがないことです。ホイールタグはglibcバージョンを含むことができますが、PEP 508マーカーはそれを表現できません。ホイールが異なるメタデータを持っている場合、ユニバーサルリゾルバはPEP 508マーカーとホイールタグの2つの次元を同時に追跡しなければならず、これにより複雑さが大幅に増加します。また、両者の対応関係は適切に指定されていません。PEP 508マーカーは、異なるプラットフォーム間で異なる依存関係を許可するために特に導入されました。つまり、すべてのホイールに対して単一の依存関係宣言を持つことができるようにするためです。例えば、project.[optional-]dependenciesのように。マーカーが不十分な場合は、ホイールタグの並行システムを使用するのではなく、PEP 508マーカーを拡張すべきです。
Another aspect of metadata consistency is that a source distribution must build into a wheel with
the same metadata as the wheels, or if there are no wheels, into the same metadata each time. If
this assumption is violated, sound dependency locking becomes impossible: Consider a package A has a
source distribution. During resolution, we build A v1 and obtain the dependencies B>=2,<3. We lock
A==1 and B==2. When installing the lockfile on the target machine, we build again and obtain
dependencies B>=3,<4 and C>=1,<2. The lockfile fails to install: Due to the changed constraints,
the locked version of B is incompatible, and there's no locked candidate for C. Re-resolving
after this would both be a reproducibility problem (the lockfile is effectively ignored) and a
security concern (C has not been reviewed, neither was B==3). It's possible to fail on
installation if that happens, but a late error, possibly during deployment, is a bad user
experience. There is already a case where uv fails on installation, packages with no source
distribution and only platform specific wheels incompatible with the current platform. While uv has
required environments as
mitigation, this requires a not well known configuration option, and questions around (un)supported
environments are one of the most common problem for uv users. A similar situation with source
distributions should be avoided.メタデータの一貫性の別の側面は、ソース配布がホイールにビルドされる際に、ホイールと同じメタデータを持つ必要があるか、ホイールがない場合は毎回同じメタデータにビルドされる必要があるということです。この仮定が破られると、健全な依存関係のロックが不可能になります。例えば、パッケージAがソース配布を持っているとします。解決中に、A v1をビルドし、依存関係B>=2,<3を取得します。A==1とB==2をロックします。ターゲットマシンでロックファイルをインストールするとき、再度ビルドし、依存関係B>=3,<4とC>=1,<2を取得します。ロックファイルのインストールに失敗します:制約が変更されたため、ロックされたBのバージョンが互換性がなく、Cのロックされた候補がありません。この後に再解決することは、再現性の問題(ロックファイルが実質的に無視される)とセキュリティの懸念(Cはレビューされておらず、B==3も同様です)を引き起こします。これが発生した場合、インストールに失敗する可能性がありますが、遅延エラー、特にデプロイ中のエラーは悪いユーザー体験です。uvがインストールに失敗するケースがすでに存在します。ソース配布がなく、現在のプラットフォームと互換性のないプラットフォーム固有のホイールを持つパッケージです。uvは必要な環境を緩和策として持っていますが、これはあまり知られていない設定オプションを必要とし、(非)サポートされている環境に関する質問はuvユーザーにとって最も一般的な問題の1つです。ソース配布に関しても同様の状況は避けるべきです。
While older versions of torch and tensorflow had inconsistent metadata, all recent versions have consistent metadata, and we are not aware of any major package with inconsistent metadata. There is however no requirement in the Python packaging standards that metadata must be consistent, and requests to enforce this in the standards have been rejected (https://discuss.python.org/t/enforcing-consistent-metadata-for-packages/50008).古いバージョンのtorchやtensorflowはメタデータが一貫していませんでしたが、最近のすべてのバージョンはメタデータが一貫しています。メタデータが一貫していない主要なパッケージは存在しないと認識しています。ただし、Pythonパッケージング標準にはメタデータが一貫している必要があるという要件はなく、この要件を標準に適用するリクエストは拒否されています(https://discuss.python.org/t/enforcing-consistent-metadata-for-packages/50008)。
There are packages that have native code that links against the native code in another package, such
as torch. These package may support building against a range of torch versions, but once built, they
are constrained to a specific torch version, and the runtime torch version must match the build-time
version. These are currently a pain point across all package managers, as all major package managers
from pip to uv cache source distribution builds. uv supports multiple builds depending on the
version of the already installed package using
tool.uv.extra-build-dependencies
with match-runtime = true. This is a workaround that needs to be made on the user side for each
affected package, instead of library developers declaring this requirement, which would be possible
with native standards support.ネイティブコードが他のパッケージのネイティブコードにリンクするパッケージがあります。例えばtorchなどです。これらのパッケージは、さまざまなtorchバージョンに対してビルドをサポートする場合がありますが、一度ビルドされると、特定のtorchバージョンに制約され、ランタイムのtorchバージョンはビルド時のバージョンと一致する必要があります。これは、pipからuvまでのすべての主要なパッケージマネージャーにおいて現在の痛点です。すべての主要なパッケージマネージャーはソース配布のビルドをキャッシュします。uvは、 tool.uv.extra-build-dependenciesを使用して、すでにインストールされているパッケージのバージョンに応じて複数のビルドをサポートします。これは、ライブラリ開発者がこの要件を宣言するのではなく、影響を受ける各パッケージについてユーザー側で行う必要がある回避策です。これはネイティブ標準のサポートがあれば可能です。
Requires-pythonRequires-python
To ensure that a resolution with requires-python = ">=3.9" can actually be installed for the
included Python versions, uv requires that all dependencies have the same minimum Python version.
Package versions that declare a higher minimum Python version, e.g., requires-python = ">=3.10",
are rejected, because a resolution with that version can't be installed on Python 3.9. This ensures
that when you are on an old Python version, you can install old packages, instead of getting newer
packages that require newer Python syntax or standard library features.解決策が requires-python = ">=3.9" を持つ場合、含まれているPythonバージョンに対して実際にインストール可能であることを保証するために、uvはすべての依存関係が同じ最小Pythonバージョンを持つことを要求します。例えば、requires-python = ">=3.10" のように、より高い最小Pythonバージョンを宣言するパッケージバージョンは拒否されます。なぜなら、そのバージョンの解決策はPython 3.9にインストールできないからです。これにより、古いPythonバージョンを使用しているときに、より新しいPython構文や標準ライブラリの機能を必要とする新しいパッケージを取得するのではなく、古いパッケージをインストールできることが保証されます。
uv ignores upper-bounds on requires-python, with special handling for packages with only
ABI-specific wheels. For example, if a package declares requires-python = ">=3.8,<4", the <4
part is ignored. There is a detailed discussion with drawbacks and alternatives in
#4022 and this
DPO thread, this section
summarizes the aspects most relevant to uv's design.uvはrequires-pythonの上限を無視し、ABI特有のホイールのみを持つパッケージに特別な処理を行います。例えば、パッケージがrequires-python = ">=3.8,<4"を宣言している場合、<4の部分は無視されます。詳細な議論と欠点、代替案については、#4022およびこのDPOスレッドに記載されており、このセクションではuvの設計に最も関連する側面を要約します。
For most projects, it's not possible to determine whether they will be compatible with a new version before it's released, so blocking newer versions in advance would block users from upgrading or testing newer Python versions. The exceptions are packages which use the unstable C ABI or internals of CPython such as its bytecode format.ほとんどのプロジェクトでは、新しいバージョンがリリースされる前に互換性があるかどうかを判断することはできないため、事前に新しいバージョンをブロックすると、ユーザーが新しいPythonバージョンにアップグレードしたりテストしたりすることができなくなります。例外は、不安定なC ABIやCPythonの内部(バイトコード形式など)を使用するパッケージです。
Introducing a requires-python upper bound to a project that previously wasn't using one will not
prevent the project from being used on a too recent Python version. Instead of failing, the resolver
will pick an older version without the bound, circumventing the bound.以前は上限を使用していなかったプロジェクトにrequires-pythonの上限を導入しても、プロジェクトがあまりにも新しいPythonバージョンで使用されるのを防ぐことはありません。失敗するのではなく、解決者は上限のない古いバージョンを選択し、上限を回避します。
For the resolution to be as universally installable as possible, uv ensures that the selected
dependency versions are compatible with the requires-python range of the project. For example, for
a project with requires-python = ">=3.12", uv will not use a dependency version with
requires-python = ">=3.13", as otherwise the resolution is not installable on Python 3.12, which
the project declares to support. Applying the same logic to upper bounds means that bumping the
upper Python version bound on a project makes it compatible with less dependency versions,
potentially failing to resolve when no version of a dependency supports the required range. (Bumping
the lower Python version bound has the inverse effect, it only increases the set of supported
dependency versions.)解決策ができるだけ普遍的にインストール可能であるように、uvは選択された依存関係のバージョンがプロジェクトのrequires-python範囲と互換性があることを保証します。例えば、requires-python = ">=3.12"を持つプロジェクトの場合、uvはrequires-python = ">=3.13"を持つ依存関係のバージョンを使用しません。そうでないと、解決策はプロジェクトがサポートすると宣言しているPython 3.12にインストールできなくなります。同じ論理を上限に適用すると、プロジェクトの上限Pythonバージョンを引き上げることは、依存関係のバージョンとの互換性を低下させ、必要な範囲をサポートする依存関係のバージョンが存在しない場合に解決に失敗する可能性があります。(下限Pythonバージョンを引き上げることは逆の効果を持ち、サポートされる依存関係のバージョンのセットを増やすだけです。)
Note that this is different for Conda, as the Conda solver also determines the Python version, so it can choose a lower Python version instead. Conda can also change metadata after a release, so it can update compatibility for a new Python version, while metadata on PyPI cannot be changed once published.これはCondaにとって異なることに注意してください。CondaソルバーはPythonバージョンも決定するため、より低いPythonバージョンを選択することができます。また、Condaはリリース後にメタデータを変更できるため、新しいPythonバージョンに対する互換性を更新できますが、PyPIのメタデータは公開後に変更できません。
Ignoring an upper bound is a problem for packages such as numpy which use the version-dependent C
API of CPython. As of writing, each numpy release support 4 Python minor versions, e.g., numpy 2.0.0
has wheels for CPython 3.9 through 3.12 and declares requires-python = ">=3.9", while numpy 2.1.0
has wheels for CPython 3.10 through 3.13 and declares requires-python = ">=3.10". This means that
when uv resolves a numpy>=2,<3 requirement in a project with requires-python = ">=3.9", it
selects numpy 2.0.0 and the lockfile doesn't install on Python 3.13 or newer. To alleviate this,
whenever uv rejects a version that requires a newer Python version, we fork by splitting the
resolution markers on that Python version. This behavior can be controlled by --fork-strategy. In
the example case, upon encountering numpy 2.1.0 we fork into Python versions >=3.9,<3.10 and
>=3.10 and resolve two different numpy versions:上限を無視することは、CPythonのバージョン依存のC APIを使用するnumpyのようなパッケージにとって問題です。執筆時点で、各numpyリリースは4つのPythonマイナーバージョンをサポートしています。例えば、numpy 2.0.0はCPython 3.9から3.12までのホイールを持ち、requires-python = ">=3.9"を宣言しています。一方、numpy 2.1.0はCPython 3.10から3.13までのホイールを持ち、requires-python = ">=3.10"を宣言しています。これは、uvがrequires-python = ">=3.9"を持つプロジェクトでnumpy>=2,<3の要件を解決する際に、numpy 2.0.0を選択し、ロックファイルがPython 3.13以降にインストールされないことを意味します。これを緩和するために、uvが新しいPythonバージョンを必要とするバージョンを拒否するたびに、そのPythonバージョンで解決マーカーを分割してフォークします。この動作は--fork-strategyで制御できます。例の場合、numpy 2.1.0に遭遇した際に、Pythonバージョン>=3.9,<3.10と>=3.10にフォークし、2つの異なるnumpyバージョンを解決します。
numpy==2.0.0; python_version >= "3.9" and python_version < "3.10"
numpy==2.1.0; python_version >= "3.10"
There's one case where uv does consider the upper bound: When the project uses an upper bound on
requires Python, such as requires-python = "==3.13.*" for an application that only deploys to
Python 3.13. uv prunes wheels from the lockfile that are outside the range (e.g., cp312 and
cp314) in a post-processing step, which does not influence the resolution itself.uvが上限を考慮するケースが1つあります。プロジェクトがrequires-python = "==3.13.*"のようにPythonの上限を使用している場合、Python 3.13にのみデプロイするアプリケーションです。uvは、解決自体には影響を与えない後処理ステップで、範囲外のホイール(例えば、cp312やcp314)をロックファイルから削除します。
URL dependenciesURL依存関係
In uv, a dependency can either be a registry dependency, a package with a version specifier or the
plain package name, or a URL dependency. All requirements in the form {name} @ {url} are URL
dependencies, and also all dependencies that have a git,url, path, or workspace source.uvでは、依存関係はレジストリ依存関係、バージョンスペシファイアを持つパッケージまたは単純なパッケージ名、またはURL依存関係のいずれかです。{name} @ {url}の形式のすべての要件はURL依存関係であり、git、url、path、またはworkspaceソースを持つすべての依存関係も含まれます。
When a URL is declared for a package, uv pins the package to this URL, and the version this URL
implies. If there are two conflicting URLs for a package, the resolver errors, as a URL can only be
declared as something akin to an exact == pin, and not as list of URLs. A list of URLs is
supported through flat indexes instead.パッケージにURLが宣言されると、uvはそのパッケージをこのURLおよびこのURLが示すバージョンに固定します。パッケージに対して2つの矛盾するURLがある場合、リゾルバはエラーを返します。なぜなら、URLは正確な==ピンのように宣言されることしかできず、URLのリストとして宣言することはできないからです。URLのリストは、フラットインデックスを通じてサポートされています。
uv requires that URLs are either declared directly (in the project, in a workspace member, in a constraint, or in an override, any location that is discovered directly), or by other URL dependencies. uv discovers all URL dependencies and their transitive URL dependencies ahead of the resolution and pins all packages to the URLs and the versions they imply.uvは、URLが直接宣言されるか(プロジェクト内、ワークスペースメンバー内、制約内、またはオーバーライド内、直接発見される任意の場所)、または他のURL依存関係によって宣言される必要があります。uvは、解決の前にすべてのURL依存関係とその推移的URL依存関係を発見し、すべてのパッケージをそのURLおよびそれが示すバージョンに固定します。
uv does not allow URLs in index packages. This has two reasons: One is a security and predictability aspect, that forbids registry distributions to point to non-registry distributions and helps auditing which URLs can be accessed. For example, when only using one index URL and no URL dependencies, uv will not install any package from outside the index.uvはインデックスパッケージ内でのURLを許可しません。これには2つの理由があります。1つはセキュリティと予測可能性の観点で、レジストリ配布が非レジストリ配布を指すことを禁じ、どのURLにアクセスできるかの監査を助けます。たとえば、1つのインデックスURLのみを使用し、URL依存関係がない場合、uvはインデックスの外部からパッケージをインストールしません。
The other is that URLs can add additional versions to the resolution. Say the root package depends
on foo, bar, and baz, all registry dependencies. foo depends on bar >= 2, but bar only has version
1 on the index. With the incremental approach, this is an error: foo cannot be fulfilled, there is a
resolver error. If URLs on index packages were allowed, it could be that there is a version of baz
declares a dependency on baz-core and that has a version that declares
bar @ https://example.com/bar-2-py3-none-any.whl adding a version of bar that makes requirements
resolve. If a dependency can add new versions, discarding any version in the resolver would require
looking at all possible versions of all direct and transitive dependencies. This breaks the core
assumption incremental resolvers make that the set of versions for a package is static and would
require to always fetch the metadata for all possibly reachable version.もう1つの理由は、URLが解決に追加のバージョンを加える可能性があることです。ルートパッケージがfoo、bar、bazに依存しているとします。これらはすべてレジストリ依存関係です。fooはbar >= 2に依存していますが、barはインデックスにバージョン1しかありません。増分アプローチでは、これはエラーです:fooは満たされず、リゾルバエラーが発生します。インデックスパッケージでURLが許可されている場合、bazのバージョンがbaz-coreに依存関係を宣言し、そのバージョンがbar @ https://example.com/bar-2-py3-none-any.whlを宣言してbarのバージョンを追加し、要件を解決できる可能性があります。依存関係が新しいバージョンを追加できる場合、リゾルバ内の任意のバージョンを破棄するには、すべての直接および推移的依存関係のすべての可能なバージョンを確認する必要があります。これにより、増分リゾルバが持つコアの仮定が壊れ、すべての到達可能なバージョンのメタデータを常に取得する必要があります。
Prioritization優先順位付け
Prioritization is important for both performance and for better resolutions.優先順位付けは、パフォーマンスとより良い解決のために重要です。
If we try many versions we have to later discard, resolution is slow, both because we have to read metadata we didn't need and because we have to track a lot of (conflict) information for this discarded subtree.多くのバージョンを試すと、後で破棄しなければならないため、解決は遅くなります。これは、必要のないメタデータを読み取る必要があるためと、この破棄されたサブツリーのために多くの(衝突)情報を追跡する必要があるためです。
There are expectations about which solution uv should choose, even if the version constraints allow multiple solutions. Generally, a desirable solution prioritizes use the highest versions for direct dependencies over those for indirect dependencies, it avoids backtracking to very old versions and can be installed on a target machine.複数の解決策を許可するバージョン制約があっても、uvがどの解決策を選択すべきかについての期待があります。一般的に、望ましい解決策は、直接依存関係に対しては最高のバージョンを使用し、間接依存関係に対してはそれを避け、非常に古いバージョンへのバックトラッキングを避け、ターゲットマシンにインストールできるものです。
Internally, uv represent each package with a given package name as a number of virtual packages, for example, one package for each activated extra, for dependency groups, or for having a marker. While PubGrub needs to choose a version for each virtual package, uv's prioritization works on the package name level.内部的に、uvは特定のパッケージ名を持つ各パッケージを複数の仮想パッケージとして表現します。たとえば、各アクティブなエクストラ、依存関係グループ、またはマーカーを持つための1つのパッケージです。PubGrubは各仮想パッケージのバージョンを選択する必要がありますが、uvの優先順位付けはパッケージ名レベルで機能します。
Whenever we encounter a requirement on a package, we match it to a priority. The root package and
URL requirements have the highest priority, then singleton requirements with the == operator, as
their version can be directly determined, then highly conflicting packages (next paragraph), and
finally all other packages. Inside each category, packages are sorted by when they were first
encountered, creating a breadth first search that prioritizes direct dependencies including
workspace dependencies over transitive dependencies.パッケージに対する要件に遭遇するたびに、それを優先順位に一致させます。ルートパッケージとURL要件は最も高い優先順位を持ち、次に==演算子を持つシングルトン要件が続きます。これらのバージョンは直接決定できるため、次に高度に衝突するパッケージ(次の段落)、最後に他のすべてのパッケージです。各カテゴリ内では、パッケージは最初に遭遇した時期によってソートされ、ワークスペース依存関係を含む直接依存関係を優先する幅優先探索が作成されます。
A common problem is that we have a package A with a higher priority than package B, and B is only compatible with older versions of A. We decide the latest version for package A. Each time we decide a version for B, it is immediately discarded due to the conflict with A. We have to try all possible versions of B, until we have either exhausted the possible range (slow), pick a very old version that doesn't depend on A, but most likely isn't compatible with the project either (bad) or fail to build a very old version (bad). Once we see such conflict happen five time, we set A and B to special highly-conflicting priority levels, and set them so that B is decided before A. We then manually backtrack to a state before deciding A, in the next iteration now deciding B instead of A. See #8157 and #9843 for a more detailed description with real world examples.一般的な問題は、パッケージAの優先度がパッケージBよりも高く、Bが古いバージョンのAとしか互換性がない場合です。私たちはパッケージAの最新バージョンを決定します。Bのバージョンを決定するたびに、それはAとの競合のために即座に破棄されます。私たちはBのすべての可能なバージョンを試さなければなりませんが、可能な範囲を使い果たすまで(遅い)、Aに依存しない非常に古いバージョンを選ぶか(悪い)、しかしおそらくプロジェクトとも互換性がない(悪い)か、非常に古いバージョンのビルドに失敗する(悪い)ことになります。このような競合が5回発生すると、AとBを特別な高い競合優先度に設定し、BがAの前に決定されるように設定します。その後、次のイテレーションでAを決定する前の状態に手動でバックトラックし、今度はAの代わりにBを決定します。詳細な説明と実際の例については、#8157および#9843を参照してください。