Edited at

Composer でパッケージの削除は composer remove が良いと思ったらそうでも無かった

More than 3 years have passed since last update.

Composer で複数のパッケージをインストールしていて、開発を進めるうちにそれらの一部が不要になること、あると思います。そんなとき、どうやって不要になったパッケージを削除しているでしょうか?

幾つか方法はあると思うので、それらの動作を確認してみます。

まず、実験のために composer.json を a b c d の 4 つのディレクトリに作成します。それぞれ下記の内容です。


a/composer.json

{

"name": "oreore/a",
"require": {
"oreore/c": "~1.0"
}
}


b/composer.json

{

"name": "oreore/b",
"require": {
"oreore/c": "~1.0",
"oreore/d": "~1.0"
}
}


c/composer.json

{

"name": "oreore/c"
}


d/composer.json

{

"name": "oreore/d"
}

それぞれのディレクトリを git リポジトリにして v1.0.0 タグを作成します。

for x in a b c d; do (

cd $x
git init
git add .
git commit -m first
git tag v1.0.0
); done

1つ上のディレクトリにも composer.json を作成します。repositories で a b c d の .git ディレクトリを指定します。


composer.json

{

"repositories": [
{ "packagist": false },
{ "type": "vcs", "url": "file://./a/.git" },
{ "type": "vcs", "url": "file://./b/.git" },
{ "type": "vcs", "url": "file://./c/.git" },
{ "type": "vcs", "url": "file://./d/.git" }
],
"require": {
"oreore/a": "~1.0",
"oreore/b": "~1.0"
}
}

require には a と b だけ記述していますが、a は c に依存しており、b は c と d に依存しているため、a b c d のすべてがインストールされます。

$ composer install

Loading composer repositories with package information
Installing dependencies (including require-dev)
- Installing oreore/c (v1.0.0)
Cloning 2d9272d949393688db7dc9438ed5ef5526d6378c

- Installing oreore/a (v1.0.0)
Cloning f48fd8b11e1fb71fb6638d1c765ab556da16e136

- Installing oreore/d (v1.0.0)
Cloning 2af7c6202d11209cd60e28e97b83d1686e1fd945

- Installing oreore/b (v1.0.0)
Cloning 0dc81980cf7e78c0541b5ed6df7cbd40b34efbe3

Writing lock file
Generating autoload files

依存関係のグラフは次のようになっています。

graph.png

この状態から b が不要になったので b を削除します。


composer update

一番ありがちなのが composer.json から不要になったパッケージを手で削除して composer update だと思います。


composer.json

{

"repositories": [
{ "packagist": false },
{ "type": "vcs", "url": "file://./a/.git" },
{ "type": "vcs", "url": "file://./b/.git" },
{ "type": "vcs", "url": "file://./c/.git" },
{ "type": "vcs", "url": "file://./d/.git" }
],
"require": {
"oreore/a": "~1.0"
}
}

$ composer update

Loading composer repositories with package information
Updating dependencies (including require-dev)
- Removing oreore/b (v1.0.0)
- Removing oreore/d (v1.0.0)
Writing lock file
Generating autoload files

しかしこれはあまり良くありません、例えば次のようにインストールしていたパッケージの次のバージョン v1.0.1 がリリースされていた場合、

for x in a b c d; do (

cd $x
touch README.md
git add .
git commit -m v1.0.1
git tag v1.0.1
); done

composer update だと composer.lock で固定されていたパッケージのバージョンが更新されてしまいます。

$ composer update

Loading composer repositories with package information
Updating dependencies (including require-dev)
- Removing oreore/b (v1.0.0)
- Removing oreore/d (v1.0.0)
- Updating oreore/c (v1.0.0 => v1.0.1)
Checking out afe5adc298e999dd4241306cf39f22a4a8bb7001

- Updating oreore/a (v1.0.0 => v1.0.1)
Checking out 3c1000a38b7e806f5a022ded8f8811c7b1e4d426

Writing lock file
Generating autoload files


composer update [package]

composer update は引数でパッケージ名を指定できます。なので、composer.json から不要になったパッケージを手で削除して、


composer.json

{

:
"require": {
"oreore/a": "~1.0"
}
}

composer update で不要になったパッケージ名を指定すると、そのパッケージの削除だけが行われます。

$ composer update oreore/b

Loading composer repositories with package information
Updating dependencies (including require-dev)
- Removing oreore/b (v1.0.0)
Writing lock file
Generating autoload files

がしかし、これだと b から依存していた d が削除されていません。b が不要なら d も不要なので一緒に削除したいです。


composer update --with-dependencies [package]

composer update--with-dependencies オプションを追加すると、指定したパッケージから依存しているパッケージも一緒に更新されます。

まずは composer.json を手で修正して、


composer.json

{

:
"require": {
"oreore/a": "~1.0"
}
}

composer update --with-dependencies で不要になったパッケージを指定します。

$ composer update --with-dependencies oreore/b

Loading composer repositories with package information
Updating dependencies (including require-dev)
- Removing oreore/b (v1.0.0)
- Removing oreore/d (v1.0.0)
- Updating oreore/c (v1.0.0 => v1.0.1)
Checking out afe5adc298e999dd4241306cf39f22a4a8bb7001

Writing lock file
Generating autoload files

b と d を両方とも削除することができました。がしかし、a と b の両方から依存している c が更新されてしまいました。


composer remove [package]

最近になって Composer に remove というコマンドが追加されていることに気づきました。このコマンドを使うと composer.json の修正も行ってくれるので composer.json を手で修正する必要がありません。

$ composer remove oreore/b

Loading composer repositories with package information
Updating dependencies (including require-dev)
- Removing oreore/b (v1.0.0)
Writing lock file
Generating autoload files

がしかし、composer update でパッケージ名を指定したときと同じように、b からしか依存されていない d が削除されませんでした。


composer remove --update-with-dependencies [package]

composer remove には --update-with-dependencies というオプションを付けることができます。これなら d も一緒に削除されそうです。

$ composer remove --update-with-dependencies oreore/b

Loading composer repositories with package information
Updating dependencies (including require-dev)
- Removing oreore/b (v1.0.0)
- Removing oreore/d (v1.0.0)
- Updating oreore/c (v1.0.0 => v1.0.1)
Checking out afe5adc298e999dd4241306cf39f22a4a8bb7001

Writing lock file
Generating autoload files

b と d の両方が削除されました。がしかし、composer update --with-dependencies と同じように a と b の両方から依存されている c が更新されてしまいました。


composer remove ---no-update [package]

composer remove には --no-update というオプションを付けることができます。これなら更新は行われなさそうです。

$ composer remove --no-update oreore/b

ん? なにも起こらない?

$ cat composer.json

{
"repositories": [
{ "packagist": false },
{ "type": "vcs", "url": "file://./a/.git" },
{ "type": "vcs", "url": "file://./b/.git" },
{ "type": "vcs", "url": "file://./c/.git" },
{ "type": "vcs", "url": "file://./d/.git" }
],
"require": {
"oreore/a": "~1.0"
}
}

どうやら composer.json を修正するだけになるようです。


まとめ

結局、どうやっても b と b からだけ依存されているパッケージをまとめて 削除だけ を行うことはできませんでした。

どしても b と d の削除だけを行いたければ、次のようにすると良いでしょう。

まずは composer remove --no-updatecomposer.json を修正します。

$ composer remove --no-update oreore/b

次に composer update --dry-run を実行します。

$ composer update --dry-run

Loading composer repositories with package information
Updating dependencies (including require-dev)
- Uninstalling oreore/b (v1.0.0)

- Uninstalling oreore/d (v1.0.0)

- Updating oreore/c (v1.0.0) to oreore/c (v1.0.1)

- Updating oreore/a (v1.0.0) to oreore/a (v1.0.1)

すると、実際に composer update を実行すると何が起こるかが表示されます。

Uninstalling と表示されたパッケージを composer update に羅列します。

$ composer update oreore/b oreore/d

Loading composer repositories with package information
Updating dependencies (including require-dev)
- Removing oreore/b (v1.0.0)
- Removing oreore/d (v1.0.0)
Writing lock file
Generating autoload files

不要になったパッケージの削除だけが行われます。


シェル関数にしてみました。

composer-uninstall () {

composer remove --no-update "$@"
composer update --dry-run |
grep -Eo -e '- Uninstalling\s+\S+' |
cut -d' ' -f3 |
xargs composer update
}

次のように実行します。

$ composer-uninstall oreore/b

Loading composer repositories with package information
Updating dependencies (including require-dev)
- Removing oreore/b (v1.0.0)
- Removing oreore/d (v1.0.0)
Writing lock file
Generating autoload files

.

.

.

どうせなら Bash の超ステキ機能 も使ってみましょうか。

export composer_uninstall='() {

composer remove --no-update "$@"
composer update --dry-run |
grep -Eo -e "- Uninstalling\s+\S+" |
cut -d" " -f3 |
xargs composer update
}'

exec $SHELL -l

次のように実行します。

$ composer_uninstall oreore/b

Loading composer repositories with package information
Updating dependencies (including require-dev)
- Removing oreore/b (v1.0.0)
- Removing oreore/d (v1.0.0)
Writing lock file
Generating autoload files

.

.

.

さいごのはネタです。