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

  • 123
    Like
  • 3
    Comment
More than 1 year has 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

.

.

.

さいごのはネタです。