はじめに
ソフトウェア開発において、パッケージマネージャはインフラの根幹を担っている。毎日使う npm install、pip install、brew install、yum install といったコマンドの裏には、依存関係の解決、バージョン管理、バイナリ配布という複雑なメカニズムが隠れている。
この記事では、4つの主要なパッケージマネージャの内部原理を掘り下げ、それぞれのエコシステムに自分のソフトウェアを登録する方法まで解説する。
Part 1: npmの仕組み(JavaScript / Node.js)
1-1. npmとは
npm(Node Package Manager)はJavaScriptエコシステムの標準パッケージマネージャである。3つの核心的な構成要素がある。
レジストリ(Registry): すべてのパッケージが保存される中央リポジトリ。registry.npmjs.org にホスティングされ、世界中にCDNで配信される。2026年時点で300万以上のパッケージが登録されている。
package.json: プロジェクトのメタデータと依存関係を宣言するファイル。
{
"name": "my-project",
"version": "1.0.0",
"dependencies": {
"express": "^4.18.0",
"lodash": "~4.17.21"
},
"devDependencies": {
"jest": "^29.0.0"
}
}
node_modules: 依存関係が実際にインストールされるディレクトリ。npmはデフォルトでネストされた構造を使用するが、可能な限りホイスティング(巻き上げ)によってフラット化する。
1-2. 依存関係の解決メカニズム
npmの依存関係解決は、4つの主要な概念で説明できる。
Semver(セマンティックバージョニング):
npmはsemverルールに従う。バージョンはMAJOR.MINOR.PATCH形式で、各範囲指定子の意味は以下の通り。
| 指定子 | 意味 | 例 |
|---|---|---|
^4.18.0 | MAJORを固定、MINORとPATCHを許可 | 4.18.0以上5.0.0未満 |
~4.17.21 | MAJORとMINORを固定、PATCHのみ許可 | 4.17.21以上4.18.0未満 |
4.18.0 | その正確なバージョンのみ | 4.18.0のみ |
>=4.0.0 | そのバージョン以上すべて | 4.0.0以上 |
Lockファイル:
package-lock.json はインストールされた全パッケージの正確なバージョン、整合性ハッシュ、解決されたURLを記録する。これにより、チーム全体が同一の依存関係ツリーを再現できる。
{
"name": "my-project",
"lockfileVersion": 3,
"packages": {
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-abc123..."
}
}
}
ホイスティング(巻き上げ):
パッケージAがlodash 4.17.21に依存し、パッケージBもlodash 4.17.21に依存する場合、npmはlodashを最上位のnode_modulesに1回だけインストールする。ただし、異なるバージョンが必要な場合は、下位のnode_modulesに重複インストールされる。
ファントム依存関係:
ホイスティングにより、プロジェクトが直接依存していないパッケージをimportできてしまう問題。package.jsonに宣言していないにもかかわらず、たまたま使える状態になっている。後で依存関係ツリーが変わると突然壊れる原因となる。
1-3. npm vs yarn vs pnpm
3つのパッケージマネージャの主要な違いを比較する。
| 項目 | npm | yarn (Berry) | pnpm |
|---|---|---|---|
| ストレージ方式 | node_modules(ホイスト) | Plug'n'Play (PnP) | content-addressable store + symlinks |
| Lockファイル | package-lock.json | yarn.lock | pnpm-lock.yaml |
| ファントム依存関係防止 | なし | あり(strict PnP) | あり(隔離されたnode_modules) |
| ディスク使用量 | 多い | 少ない | 非常に少ない(ハードリンク) |
| ワークスペース | npm workspaces | yarn workspaces | pnpm workspaces |
| パフォーマンス | 普通 | 良い | 非常に良い |
pnpmのコアアイデア:
pnpmはグローバルなcontent-addressable storeにパッケージを1回だけ保存し、プロジェクトのnode_modulesにはハードリンクを作成する。同じバージョンのlodashを10プロジェクトで使っても、ディスクには1回しか保存されない。
~/.pnpm-store/
v3/
files/
ab/cdef1234... # lodash 4.17.21の実際のファイル
project-a/node_modules/.pnpm/
lodash@4.17.21/
node_modules/
lodash/
index.js --> ~/.pnpm-store/v3/files/ab/cdef1234... (ハードリンク)
1-4. npmにパッケージを登録する
自分のパッケージをnpmレジストリに公開する手順。
ステップ1: アカウント作成とログイン
npm adduser
# すでにアカウントがある場合
npm login
ステップ2: パッケージの初期化
mkdir my-awesome-lib
cd my-awesome-lib
npm init
ステップ3: package.jsonの完成
{
"name": "my-awesome-lib",
"version": "1.0.0",
"description": "A library that does awesome things",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build"
},
"keywords": ["awesome", "utility"],
"author": "Your Name",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/yourname/my-awesome-lib"
}
}
ステップ4: 公開
# ビルドの確認
npm run build
# 公開前のプレビュー(含まれるファイルを確認)
npm pack --dry-run
# 実際に公開
npm publish
# スコープ付きパッケージ(パブリック)
npm publish --access public
ステップ5: バージョン管理
# パッチバージョンを上げる (1.0.0 -> 1.0.1)
npm version patch
# マイナーバージョンを上げる (1.0.1 -> 1.1.0)
npm version minor
# メジャーバージョンを上げる (1.1.0 -> 2.0.0)
npm version major
# 公開
npm publish
Part 2: uvの仕組み(Python)
2-1. uvとは
uvはAstral社がRustで開発した超高速Pythonパッケージマネージャ兼プロジェクト管理ツールである。pipの代替でありながら、pipの10倍から100倍の速度を実現している。
uvが速い理由は複数ある。
- Rustで記述: ネイティブバイナリにコンパイルされ、Pythonインタプリタのオーバーヘッドがない
- 並列ダウンロード: 依存関係の解決とダウンロードを同時に実行する
- グローバルキャッシュ: 一度ダウンロードしたパッケージはすべてのプロジェクトで再利用される
- 最適化されたSATソルバー: 依存関係グラフを効率的に解決する
# uvのインストール
curl -LsSf https://astral.sh/uv/install.sh | sh
# プロジェクトの作成
uv init my-project
cd my-project
# 依存関係の追加
uv add requests flask
# 依存関係の同期(lockファイルベース)
uv sync
2-2. pip vs uv vs poetry vs conda
| 項目 | pip | uv | poetry | conda |
|---|---|---|---|---|
| 言語 | Python | Rust | Python | Python/C |
| 依存関係解決 | バックトラッキング | SATソルバー | SATソルバー | SATソルバー |
| Lockファイル | なし(手動freeze) | uv.lock | poetry.lock | environment.yml |
| 仮想環境管理 | なし(別途venv) | あり(内蔵) | あり(内蔵) | あり(内蔵) |
| 速度(コールドインストール) | 遅い(基準) | 10-100倍速い | 2-5倍速い | 遅い |
| ビルドシステム | setuptools | 自己解決 | 自己ビルド | 自己ビルド |
| 非Pythonパッケージ | なし | なし | なし | あり(numpy Cライブラリなど) |
速度比較(実際のベンチマーク):
# requests + flask + sqlalchemy のインストール(コールドキャッシュ)
pip install: 12.4s
poetry install: 8.1s
uv sync: 0.8s # 15倍速い
2-3. uvの依存関係解決アルゴリズム
uvはPubGrubアルゴリズムに基づくSATソルバーを使用している。段階的に見ていこう。
1. 依存関係グラフの構築:
プロジェクトの直接依存関係から始めて、各パッケージのメタデータを読み取り、推移的(transitive)な依存関係グラフを構築する。
2. 制約伝播(Constraint Propagation):
各パッケージのバージョン要件を制約条件に変換し、これを伝播させて可能なバージョン空間を絞り込む。
3. 単位伝播(Unit Propagation):
可能な値が1つだけ残った変数がある場合、その値を確定し、関連する制約を更新する。
4. 競合駆動節学習(CDCL: Conflict-Driven Clause Learning):
競合が発生すると、その原因を分析して「学習節(learned clause)」を追加する。これにより、同じ失敗を繰り返さない。
例: A>=1.0 は B>=2.0 を要求するが、C<1.5 は B<2.0 を要求
-> 競合検出
-> 学習: A>=1.0 AND C<1.5 は同時に成立不可
-> バックトラックして別のバージョンを試行
2-4. PyPIにパッケージを登録する
PythonパッケージをPyPI(Python Package Index)に登録する現代的な方法。
ステップ1: pyproject.tomlの作成
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-python-lib"
version = "1.0.0"
description = "A useful Python library"
readme = "README.md"
requires-python = ">=3.9"
license = "MIT"
authors = [
{ name = "Your Name", email = "you@example.com" }
]
dependencies = [
"requests>=2.28.0",
"pydantic>=2.0",
]
[project.urls]
Homepage = "https://github.com/yourname/my-python-lib"
Documentation = "https://my-python-lib.readthedocs.io"
[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"]
ステップ2: ビルド
# ビルドツールのインストール
uv add --dev build
# ビルドの実行
python -m build
# dist/ ディレクトリに .whl と .tar.gz ファイルが生成される
ls dist/
# my_python_lib-1.0.0-py3-none-any.whl
# my_python_lib-1.0.0.tar.gz
ステップ3: TestPyPIでのテスト
# twineのインストール
uv add --dev twine
# TestPyPIにアップロード
python -m twine upload --repository testpypi dist/*
# テストインストール
pip install --index-url https://test.pypi.org/simple/ my-python-lib
ステップ4: 本番PyPIへのデプロイ
# PyPIにアップロード
python -m twine upload dist/*
# 誰でもインストール可能に
pip install my-python-lib
# または
uv add my-python-lib
Trusted Publisher設定(GitHub Actions):
name: Publish to PyPI
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install build
- run: python -m build
- uses: pypa/gh-action-pypi-publish@release/v1
この方法を使えば、APIトークンなしでPyPIにデプロイできる。GitHubのOIDCトークンで認証が行われる。
Part 3: RPM(Red Hat / CentOS / Rocky Linux)
3-1. RPMとは
RPM(Red Hat Package Manager)はRed Hat系Linuxディストリビューションのパッケージ管理システムである。中核的な構成要素は以下の通り。
RPMファイル: .rpm 拡張子を持つバイナリパッケージファイル。コンパイルされたプログラム、設定ファイル、ドキュメント、インストール/アンインストールスクリプトを含む。
RPMデータベース: /var/lib/rpm に位置し、インストールされた全パッケージの情報を追跡する。
yum / dnf: RPMの依存関係解決問題を解決する上位レベルのツール。RPMは単一パッケージしかインストールできないが、dnfは依存関係を自動的に解決し、リモートリポジトリからパッケージをダウンロードする。
# RPMの直接使用(依存関係の自動解決なし)
rpm -ivh package-1.0.0-1.el9.x86_64.rpm
# dnfの使用(依存関係の自動解決あり)
dnf install nginx
# パッケージ情報の照会
rpm -qi nginx
# パッケージに含まれるファイルのリスト
rpm -ql nginx
Specファイル: RPMパッケージをビルドするためのレシピファイル。ソースコードのコンパイル方法、どのファイルをどこにインストールするか、どの依存関係が必要かを定義する。
3-2. RPMパッケージの作成
ステップ1: ビルド環境の準備
# ビルドツールのインストール
dnf install rpm-build rpmdevtools
# ビルドディレクトリ構造の作成
rpmdev-setuptree
# 生成される構造:
# ~/rpmbuild/
# BUILD/ - ビルドが実行されるディレクトリ
# RPMS/ - ビルドされたRPMファイル
# SOURCES/ - ソースtarball
# SPECS/ - specファイル
# SRPMS/ - ソースRPMファイル
ステップ2: Specファイルの作成
Name: myapp
Version: 1.0.0
Release: 1%{?dist}
Summary: My awesome application
License: MIT
URL: https://github.com/yourname/myapp
Source0: %{name}-%{version}.tar.gz
BuildRequires: gcc
BuildRequires: make
Requires: openssl-libs
%description
MyApp is an awesome application that does useful things.
It supports multiple platforms and is easy to configure.
%prep
%autosetup
%build
%configure
%make_build
%install
%make_install
%files
%license LICENSE
%doc README.md
%{_bindir}/myapp
%{_mandir}/man1/myapp.1*
%config(noreplace) %{_sysconfdir}/myapp.conf
%changelog
* Sat Apr 12 2026 Your Name <you@example.com> - 1.0.0-1
- Initial package
ステップ3: ビルド
# ソースtarballをSOURCESにコピー
cp myapp-1.0.0.tar.gz ~/rpmbuild/SOURCES/
# RPMビルド (-ba: バイナリとソースRPMの両方)
rpmbuild -ba ~/rpmbuild/SPECS/myapp.spec
# ビルドされたRPMの確認
ls ~/rpmbuild/RPMS/x86_64/
# myapp-1.0.0-1.el9.x86_64.rpm
ステップ4: ローカルリポジトリの作成
# createrepoのインストール
dnf install createrepo_c
# リポディレクトリの作成
mkdir -p /var/www/html/myrepo/
# RPMのコピー
cp ~/rpmbuild/RPMS/x86_64/myapp-*.rpm /var/www/html/myrepo/
# リポメタデータの生成
createrepo /var/www/html/myrepo/
# /etc/yum.repos.d/myrepo.repo
[myrepo]
name=My Custom Repository
baseurl=http://myserver.example.com/myrepo/
enabled=1
gpgcheck=0
3-3. DEB vs RPM 比較
| 項目 | RPM(Red Hat系) | DEB(Debian系) |
|---|---|---|
| ディストリビューション | RHEL, CentOS, Rocky, Fedora | Debian, Ubuntu, Mint |
| パッケージ形式 | .rpm | .deb |
| 低レベルツール | rpm | dpkg |
| 高レベルツール | yum / dnf | apt / apt-get |
| パッケージ定義 | specファイル | debian/ ディレクトリ (control, rulesなど) |
| ビルドツール | rpmbuild | dpkg-buildpackage |
| リポ作成 | createrepo | apt-ftparchive / reprepro |
| スクリプトステージ | pre/post install/uninstall | preinst/postinst/prerm/postrm |
| 署名 | GPG | GPG (apt-key) |
主な違いは設計哲学にある。RPMのspecファイルはすべてを1つのファイルにまとめる一方、DEBのdebian/ディレクトリは役割ごとにファイルを分離する。
Part 4: Homebrew(macOS / Linux)
4-1. Homebrewの仕組み
HomebrewはmacOS(およびLinux)の非公式パッケージマネージャである。核心的な概念を見ていこう。
Formula: パッケージのインストール方法を定義するRubyスクリプト。ソースURL、ビルドオプション、依存関係、インストール手順を含む。
Tap: Formulaをまとめたgitリポジトリ。デフォルトのTapはhomebrew-coreで、誰でも自分のTapを作成できる。
Cellar: パッケージが実際にインストールされる場所。macOSでは /opt/homebrew/Cellar/(Apple Silicon)または /usr/local/Cellar/(Intel)に位置する。
Keg-only: Cellarにインストールされるが、PATHにシンボリックリンクが作成されないパッケージ。システムにすでに同じプログラムがある場合の競合を防ぐ。代表的な例がopensslである。
# パッケージのインストール
brew install wget
# インストールパスの確認
brew --prefix wget
# /opt/homebrew/opt/wget
# Cellar内部の構造
ls /opt/homebrew/Cellar/wget/1.21.4/
# bin/ etc/ share/
# keg-onlyパッケージの強制リンク
brew link --force openssl@3
Bottle: 事前にコンパイルされたバイナリパッケージ。ソースからビルドする代わりにBottleをダウンロードすれば、インストール速度が大幅に向上する。ほとんどの公式FormulaにはmacOSとLinux用のBottleが提供されている。
4-2. Homebrewにソフトウェアを登録する
Homebrewにソフトウェアを登録する方法は3つある。
方法1: 個人Tapの作成
最も簡単で制約が少ない方法。
ステップ1: GitHubリポジトリの作成
homebrew-mytap という名前でGitHubリポジトリを作成する。Homebrewは homebrew- プレフィックスをTap名として認識する。
ステップ2: Formulaの作成
# Formula/myapp.rb
class Myapp < Formula
desc "My awesome command-line application"
homepage "https://github.com/yourname/myapp"
url "https://github.com/yourname/myapp/archive/refs/tags/v1.0.0.tar.gz"
sha256 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
license "MIT"
depends_on "go" => :build
def install
system "go", "build", *std_go_args(ldflags: "-s -w -X main.version=#{version}")
end
test do
assert_match "myapp version #{version}", shell_output("#{bin}/myapp --version")
end
end
ステップ3: 使用
# Tapの追加
brew tap yourname/mytap
# インストール
brew install yourname/mytap/myapp
# またはTap追加後に直接インストール
brew install myapp
方法2: homebrew-coreにPRを提出
公式Homebrewに含まれるには、厳格な基準を満たす必要がある。
必須条件:
- GitHubで30以上のスター(または十分なユーザーベース)
- 安定したリリースタグ
- オープンソースライセンス
- CI/CDによる自動ビルド
- macOSとLinuxの両方でビルド可能
# homebrew-coreのクローンとFormula追加
brew tap --force homebrew/core
cd $(brew --repository homebrew/core)
# Formula作成ヘルパー
brew create https://github.com/yourname/myapp/archive/refs/tags/v1.0.0.tar.gz
# Formulaの検証
brew audit --new myapp
brew test myapp
# PRの提出(GitHub CLI)
gh pr create --title "myapp 1.0.0 (new formula)" --body "Description of the tool..."
方法3: Cask(GUIアプリ)の登録
.app、.dmg、.pkg 形式のmacOS GUIアプリケーションを配布する場合に使用する。
# Casks/myguiapp.rb
cask "myguiapp" do
version "2.1.0"
sha256 "abc123def456..."
url "https://github.com/yourname/myguiapp/releases/download/v#{version}/MyGuiApp-#{version}.dmg"
name "MyGuiApp"
desc "A beautiful GUI application"
homepage "https://myguiapp.example.com"
app "MyGuiApp.app"
zap trash: [
"~/Library/Application Support/MyGuiApp",
"~/Library/Preferences/com.yourname.myguiapp.plist",
]
end
# Caskのインストール
brew install --cask myguiapp
4-3. Formula作成の詳細
Homebrew FormulaはRuby DSLで記述される。主要な構成要素を見ていこう。
class ComplexApp < Formula
desc "A complex application with many build options"
homepage "https://complexapp.dev"
# 安定バージョンのソース
url "https://github.com/yourname/complexapp/archive/refs/tags/v2.0.0.tar.gz"
sha256 "deadbeef..."
# HEADバージョン(開発中)
head "https://github.com/yourname/complexapp.git", branch: "main"
license "Apache-2.0"
# ビルド依存関係
depends_on "cmake" => :build
depends_on "pkg-config" => :build
# ランタイム依存関係
depends_on "openssl@3"
depends_on "sqlite"
# プラットフォーム制限
depends_on :macos
def install
args = %W[
--prefix=#{prefix}
--with-openssl=#{Formula["openssl@3"].opt_prefix}
--with-sqlite=#{Formula["sqlite"].opt_prefix}
]
system "./configure", *args
system "make", "install"
# シェル補完スクリプトのインストール
bash_completion.install "completions/complexapp.bash"
zsh_completion.install "completions/_complexapp"
fish_completion.install "completions/complexapp.fish"
end
# インストール後の案内メッセージ
def caveats
<<~EOS
To start complexapp as a service:
brew services start complexapp
EOS
end
# インストール検証テスト
test do
assert_match version.to_s, shell_output("#{bin}/complexapp --version")
system "#{bin}/complexapp", "check"
end
end
Formulaの主要DSLメソッド:
| メソッド | 用途 | 例 |
|---|---|---|
url | ソースダウンロードURL | url "https://..." |
sha256 | 整合性検証ハッシュ | sha256 "abc..." |
depends_on | 依存関係の宣言 | depends_on "openssl@3" |
install | ビルドとインストール手順 | system "make", "install" |
test | インストール検証 | assert_match ... |
prefix | インストール基本パス | /opt/homebrew/Cellar/app/1.0 |
bin | 実行ファイルパス | prefix/"bin" |
etc | 設定ファイルパス | prefix/"etc" |
share | 共有データパス | prefix/"share" |
Part 5: パッケージマネージャ大比較
5-1. 総合比較マトリクス
| 項目 | npm | PyPI (uv/pip) | RPM (dnf) | Homebrew | APT (deb) | snap | flatpak |
|---|---|---|---|---|---|---|---|
| 対象 | Node.jsライブラリ | Pythonライブラリ | システムパッケージ | CLI/GUIアプリ | システムパッケージ | デスクトップアプリ | デスクトップアプリ |
| プラットフォーム | クロスプラットフォーム | クロスプラットフォーム | RHEL系 | macOS/Linux | Debian系 | Linux | Linux |
| レジストリ | npmjs.com | pypi.org | ベンダーリポ | homebrew-core | ベンダーリポ | snapcraft.io | flathub.org |
| 隔離方式 | node_modules | virtualenv | なし(システム全体) | Cellar+symlinks | なし(システム全体) | サンドボックス | サンドボックス |
| 依存関係解決 | semver範囲 | SATソルバー | libsolv (SAT) | 自前 | APTソルバー | snap自己管理 | ランタイム共有 |
| 自動更新 | なし | なし | dnf-automatic | brew upgrade | unattended-upgrades | snapd(自動) | なし |
| セキュリティ署名 | npm署名 | GPG/Sigstore | GPG | コード署名(Cask) | GPG | Snap Store署名 | Flathub署名 |
| サイズの懸念 | なし | なし | なし | なし | なし | 大(バンドル) | 大(ランタイム) |
5-2. どのパッケージマネージャを使うべきか
JavaScript/TypeScriptライブラリを配布したい場合: npmまたはGitHub Packagesを使用する。
Pythonライブラリを配布したい場合: PyPIに登録し、uv/pipでインストールしてもらう。
Linuxサーバーにシステムレベルのパッケージを配布したい場合: RPM(RHEL系)またはDEB(Debian系)パッケージを作成する。
macOS用のCLIツールを配布したい場合: Homebrew Formulaを作成して個人Tapに公開するか、homebrew-coreにPRを提出する。
クロスプラットフォームのデスクトップアプリを配布したい場合: snapまたはflatpakを検討する。snapは自動更新が内蔵されており、flatpakはよりオープンなエコシステムを持つ。
おわりに
パッケージマネージャは単なるインストールツールではない。依存関係解決というNP完全問題を実用的に解き、数百万のパッケージを安全に配布し、開発者エコシステムの血管の役割を果たしている。
各パッケージマネージャの内部原理を理解すれば、依存関係の競合をより速く解決し、キャッシュを効率的に管理し、自分のソフトウェアを世界に配布するプロセスがはるかに楽になる。
自分のプロジェクトをnpm、PyPI、Homebrew、RPMのどこにでも登録してみよう。パッケージマネージャの仕組みを体感する最善の方法は、自分でパッケージを作ってみることだ。
参考資料
- npm公式ドキュメント: https://docs.npmjs.com/
- uv公式ドキュメント: https://docs.astral.sh/uv/
- RPM Packaging Guide: https://rpm-packaging-guide.github.io/
- Homebrew Formulaクックブック: https://docs.brew.sh/Formula-Cookbook
- PyPI配布ガイド: https://packaging.python.org/
- PubGrubアルゴリズム: https://nex3.medium.com/pubgrub-2fb6470504f
- pnpm公式ドキュメント: https://pnpm.io/
현재 단락 (1/395)
ソフトウェア開発において、パッケージマネージャはインフラの根幹を担っている。毎日使う `npm install`、`pip install`、`brew install`、`yum install` ...