Skip to content
Published on

パッケージマネージャ完全ガイド — npm, uv, RPM, Homebrew の仕組みとソフトウェア登録方法

Authors

はじめに

ソフトウェア開発において、パッケージマネージャはインフラの根幹を担っている。毎日使う npm installpip installbrew installyum 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.0MAJORを固定、MINORとPATCHを許可4.18.0以上5.0.0未満
~4.17.21MAJORと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つのパッケージマネージャの主要な違いを比較する。

項目npmyarn (Berry)pnpm
ストレージ方式node_modules(ホイスト)Plug'n'Play (PnP)content-addressable store + symlinks
Lockファイルpackage-lock.jsonyarn.lockpnpm-lock.yaml
ファントム依存関係防止なしあり(strict PnP)あり(隔離されたnode_modules)
ディスク使用量多い少ない非常に少ない(ハードリンク)
ワークスペースnpm workspacesyarn workspacespnpm 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

項目pipuvpoetryconda
言語PythonRustPythonPython/C
依存関係解決バックトラッキングSATソルバーSATソルバーSATソルバー
Lockファイルなし(手動freeze)uv.lockpoetry.lockenvironment.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.0B>=2.0 を要求するが、C<1.5B<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, FedoraDebian, Ubuntu, Mint
パッケージ形式.rpm.deb
低レベルツールrpmdpkg
高レベルツールyum / dnfapt / apt-get
パッケージ定義specファイルdebian/ ディレクトリ (control, rulesなど)
ビルドツールrpmbuilddpkg-buildpackage
リポ作成createrepoapt-ftparchive / reprepro
スクリプトステージpre/post install/uninstallpreinst/postinst/prerm/postrm
署名GPGGPG (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ソースダウンロードURLurl "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. 総合比較マトリクス

項目npmPyPI (uv/pip)RPM (dnf)HomebrewAPT (deb)snapflatpak
対象Node.jsライブラリPythonライブラリシステムパッケージCLI/GUIアプリシステムパッケージデスクトップアプリデスクトップアプリ
プラットフォームクロスプラットフォームクロスプラットフォームRHEL系macOS/LinuxDebian系LinuxLinux
レジストリnpmjs.compypi.orgベンダーリポhomebrew-coreベンダーリポsnapcraft.ioflathub.org
隔離方式node_modulesvirtualenvなし(システム全体)Cellar+symlinksなし(システム全体)サンドボックスサンドボックス
依存関係解決semver範囲SATソルバーlibsolv (SAT)自前APTソルバーsnap自己管理ランタイム共有
自動更新なしなしdnf-automaticbrew upgradeunattended-upgradessnapd(自動)なし
セキュリティ署名npm署名GPG/SigstoreGPGコード署名(Cask)GPGSnap 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のどこにでも登録してみよう。パッケージマネージャの仕組みを体感する最善の方法は、自分でパッケージを作ってみることだ。


参考資料