パッケージ管理は名前付けの連鎖である
本文の状態
日本語全文を表示中
詳細モードで約5分の本文を読めます。
同じ出来事の情報源
この情報源を基点に整理
Andrej Karpathy 厳選
著者が、パッケージ管理には「コンピュータサイエンスの2つの難問」を超える複数の根本的課題(特に依存関係の命名とバージョン管理)が存在すると指摘している。
Source Article
元記事を日本語で読む
本文に関係しない購読案内、埋め込み通知、サイト内プロモーションは除いています。
パッケージマネージャーは通常、その機能によって説明されます。依存関係の解決、コードのダウンロード、成果物のビルドです。しかし、プロセスではなくシステムの構造に注目すると、そのほぼすべての部分が名前付けの問題であり、システム全体が機能するのは、各レイヤーで文字列をどのように解釈するかに合意があり、レジストリが中間に立ってそれらの間を変換するからです。
gem install rails を実行するとき、
企業は同じパッケージに異なる名前を付けたプライベートレジストリを運用したり、異なるパッケージに同じ名前を付けたりします。Nix、Guix、Spackは、それぞれ独自の名前空間を持つ複数のパッケージリポジトリを重ね合わせます。GoはURLベースのモジュールパスを使用し、レジストリ名が文字通りパッケージの識別子に埋め込まれています。どのレジストリと通信しているかによって、システム内の他のすべての名前の意味が決まります。なぜなら、レジストリ名は実際には検索コンテキストであり、パッケージ名を与えると、バージョンのリストを返すからです。
一部のレジストリは、レジストリとパッケージの間に別の名前付けレイヤーを挿入します。Packagistはベンダープレフィックス (symfony/console) を要求します。
org.apache.commons:commons-lang3
名前空間は実際には一連の名前に対する権限の主張であり、誰が @google/ の下で公開できるかといった問題を生み出します。
レジストリを選び、任意の名前空間を辿ると、パッケージ名に到達し、その名前は利用可能なバージョンのリストに解決されます。requests
python-dateutil
時には、プロジェクトはメジャーバージョンをパッケージ名自体に組み込みます。例えば boto3 のように。
タイポスクワッティングは、入力しようとしたものとレジストリが解決したものとの間のギャップを悪用します。スロップスクワッティングは、まだ存在しないが攻撃者によって登録される可能性のあるパッケージ名に対するLLMの幻覚を悪用します。レジストリは、与えられた文字列を何も質問せずに解決します。
そのリストからバージョンを選ぶと、特定のコードのスナップショットとそのメタデータ(依存関係のリスト、ビルドのリスト、メンテナーが変更履歴に記したもの)が得られます。バージョンは数字のように見えますが、実際には文字列であり、1.0.0-beta.2+build.456 を見るとすぐに明らかになります。
1.0a1.post2.dev3
メンテナーアカウントが侵害されると、1.2.4 の公開が
依存関係と要件
各バージョンは依存関係のリストを持ち、各依存関係自体が名前のペアです。パッケージ名とバージョン制約。requests >= 2.28
バージョン管理からの「強制ではなく慣習」という同じ問題がここにも引き継がれます。バージョン制約は、バージョン名の集合を記述する小さな言語であり、すべてのエコシステムが独自のものを考案しました。~> 2.0
一部の依存関係は、さらに別の名前の背後に隠れています。pipにはエクストラ (requests[security]) があります。
pip install requests
pip install requests[security]
これらの制約言語も名前空間レイヤーと組み合わされます。npmの @types/node@^18.0.0
org.apache.commons:commons-lang3:3.12.0
ビルドとプラットフォーム
リゾルバーがバージョンを決定すると、クライアントは適切なビルド成果物を選ぶ必要があり、それはプラットフォーム名の一致を意味します。以前の名前付けレイヤーが主に人間の調整問題であるのとは異なり、プラットフォームの識別は本質的に曖昧です。Rosettaを実行しているM1 Macは、問い合わせる主体によって同時に2つのプラットフォームとなり、manylinux も同様です。
numpy-1.24.0-cp311-cp311-manylinux_2_17_x86_64.whl
nokogiri-1.15.4-x86_64-linux-gnu.gem
成果物のプラットフォーム名が、クライアントが自身の環境に対して算出したプラットフォーム名と一致しない場合、パッケージはインストールされないか、間違ったバイナリが警告なく選択されます。そして、私がプラットフォーム文字列について書いたように、同じM1 Macは aarch64-apple-darwin です。
macosx_11_0_arm64
ソースリポジトリ
名前付けはレジストリで止まりません。ほとんどのパッケージはソースリポジトリを指し示し、それは別の名前のスタックです。ホスト (github.com)、所有者、リポジトリ名、ブランチ名、コミットハッシュ。
gitのバージョンタグは特に興味深いです。なぜなら、それらは2つの命名システムの架け橋だからです。メンテナーが v1.2.3 というgitタグを作成し、レジストリがそのタグをバージョン名 1.2.3 にマッピングします。
名前付けと信頼
これらの各レイヤーで、あなたは名前が意図したものに解決され、レジストリURLが正しいサービスを指し、パッケージ名が想定した人物のものであり、バージョンが正当に公開され、制約が予期しないものを引き込まず、プラットフォームタグ付けされたバイナリが同僚のマシン用のものと同じソースからビルドされたことを信頼しています。その信頼は推移的であり、依存関係の名前とその依存関係の名前を通じて連鎖的に流れ、誰も全体を可視化できない連鎖を形成します。レジストリはこれらの名前のほとんどを意味のあるものにする権威であり、それが誰がレジストリを統治するかという問題が表面化し続ける理由です。
原文を表示
Package managers are usually described by what they do: resolve dependencies, download code, build artifacts. But if you look at the structure of the system instead of the process, nearly every part of it is a naming problem, and the whole thing works because we’ve agreed on how to interpret strings at each layer and because a registry sits in the middle translating between them.
When you run gem install rails
Companies run private registries with different names for the same packages, or the same names for different packages. Nix, Guix, and Spack layer multiple package repositories with their own namespaces on top of each other. Go uses URL-based module paths where the registry name is literally embedded in the package identity. Which registry you’re talking to determines what every other name in the system means, because a registry name is really a lookup context: give it a package name and it hands back a list of versions.
Some registries insert another naming layer between the registry and the package. Packagist requires vendor prefixes (symfony/console
org.apache.commons:commons-lang3
A namespace is really a claim of authority over a family of names, which makes questions like who gets to publish under @google/
Once you’ve picked a registry and navigated any namespace, you arrive at a package name, and that name resolves to a list of available versions. requests
python-dateutil
Sometimes projects bake a major version into the package name itself, like boto3
Typosquatting exploits the gap between what you meant to type and what the registry resolved; slopsquatting exploits LLM hallucinations of package names that don’t exist yet but could be registered by an attacker. The registry will resolve whatever string you give it, no questions asked.
Pick a version from that list and you get a particular snapshot of code, along with its metadata: a list of dependencies, a list of builds, and whatever the maintainer wrote in the changelog. Versions look like numbers but they’re really strings, which becomes obvious as soon as you see 1.0.0-beta.2+build.456
1.0a1.post2.dev3
When a maintainer account is compromised, publishing 1.2.4
Dependencies and requirements
Each version carries a list of dependencies, and each dependency is itself a pair of names: a package name and a version constraint. requests >= 2.28
The same “convention not enforcement” problem from versioning carries over here. The version constraints are a small language for describing sets of version names, and every ecosystem invented its own. ~> 2.0
Some dependencies are themselves hidden behind yet another name. pip has extras (requests[security]
pip install requests
pip install requests[security]
These constraint languages also compose with the namespace layer. npm’s @types/node@^18.0.0
org.apache.commons:commons-lang3:3.12.0
Builds and platforms
Once the resolver has settled on a version, the client needs to pick the right build artifact, and that means matching platform names. Unlike the earlier naming layers, which are mostly human-coordination problems, platform identity is inherently fuzzy: an M1 Mac running Rosetta is simultaneously two platforms depending on who’s asking, and manylinux
numpy-1.24.0-cp311-cp311-manylinux_2_17_x86_64.whl
nokogiri-1.15.4-x86_64-linux-gnu.gem
If the platform name on the artifact doesn’t match the platform name the client computes for its own environment, the package won’t install, or the wrong binary gets selected silently. And as I wrote about in platform strings, the same M1 Mac is aarch64-apple-darwin
macosx_11_0_arm64
Source repositories
The naming doesn’t stop at the registry. Most packages point back to a source repository, and that’s another stack of names: the host (github.com
Version tags in git are particularly interesting because they’re the bridge between two naming systems. A maintainer creates a git tag called v1.2.3
Naming and trust
At each of these layers you’re trusting that a name resolves to what you think it does, that the registry URL points to the right service, that the package name belongs to who you think it does, that a version was published legitimately, that a constraint won’t pull in something unexpected, that a platform-tagged binary was built from the same source as the one for your colleague’s machine. That trust is transitive, flowing through your dependencies’ names and their dependencies’ names in a chain where nobody has full visibility. The registry is the authority that makes most of these names meaningful, which is why the question of who governs registries keeps coming back to the surface.
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み