Help us understand the problem. What is going on with this article?

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

More than 5 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

.

.

.

さいごのはネタです。

ngyuki
テック系男子。 ただのやってみた系の記事ははてなブログに、それ以外の技術系のネタは Qiita に投稿します。
https://ngyuki.jp/
headjapan
中規模~大規模の安定した基幹システム・大規模サイトの分析・要件定義・設計・開発を得意とする、総合的な開発会社です。
http://www.headjapan.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした