WindowsコマンドラインツールScoopのすすめ（バージョン管理編）

## 前提
この記事は以下のような方にお勧めいたします。

- WindowsUpdateが適用されたWindows10で開発をしている
- インストール、設定作業を省力化したい
- 同じアプリケーションの別バージョンを使い分けたい
- Scoopがインストール済み

Scoopがどんなツールかまだよくわからない方や、未インストールの方は[WindowsコマンドラインインツールScoopのすすめ（基礎編）](https://qiita.com/talesleaves/items/0880bf31359715035a3c)などを読んでいただけると幸いです。

## Scoopでのバージョン管理
Scoopではすべてのアプリに対してではありませんが、バージョン管理のためのバケット[^1]を公開しています。
[^1]: 分類別にアプリが登録されているGitリポジトリのことです
Scoopで安全にバージョン管理されているアプリケーションは['versions'バケット](https://github.com/ScoopInstaller/Versions/tree/master/bucket)に登録されています。node.jsやpythonなどが含まれています。
また、[PHP](https://github.com/ScoopInstaller/PHP)と[java](https://github.com/lukesampson/scoop/wiki/Java)についてはそれぞれ、専用のバケットが用意されています。
それぞれのバケットは初期状態では使用できないため、`scoop bucket add バケット名`でScoopに追加してから利用します。
ここではまだ専用バケットのないnode.jsで試してみたいと思います。

### 'versions'バケットの追加
`scoop bucket add versions`で各アプリの旧バージョンを扱えるようにします。

```powershell
PS C:\Users\user> scoop bucket add versions
Checking repo... ok
The versions bucket was added successfully.
```
シンプルに終了してしまいましたが、node.jsを検索してみます。

```powershell
PS C:\Users\user> scoop search node.js
No matches found. #node.jsでは見つからなかった

PS C:\Users\user> scoop search node
'main' bucket:
    eventstore (4.1.2) --> includes 'EventStore.ClusterNode.exe'
    node-chakracore (10.13.0)
    nodejs-lts (10.15.3)
    nodejs (12.2.0)
    sliksvn (1.9.7) --> includes 'svn-populate-node-origins-index.exe'

'versions' bucket:
    nodejs010 (0.10.48)
    nodejs012 (0.12.18)
    nodejs10 (10.15.3)
    nodejs11 (11.15.0)
    nodejs12 (12.2.0)
    nodejs4 (4.9.1)
    nodejs5 (5.12.0)
    nodejs6 (6.17.1)
    nodejs7 (7.10.1)
    nodejs8 (8.16.0)
    nodejs9 (9.11.2)
```

多くのバージョンが登録されていますが、mainバケットにある'nodejs'が、標準でインストールされる最新バージョンです。mainにはLTSバージョンもあるようです。
nodejs以外のバージョン指定は実際のところは別アプリとして登録され、それぞれのバージョンでの安定、または最新のバージョンが登録されています。
ここでは最新のバージョンと、ひとつ前のバージョンでversionsバケットにあるnodejs11をインストールしてみます。

### まとめてインストール
```powershell
PS C:\Users\user> scoop install nodejs nodejs11
Installing 'nodejs' (12.2.0) [64bit]
node-v12.2.0-win-x64.7z (9.5 MB) [============================================================] 100%
Checking hash of node-v12.2.0-win-x64.7z ... ok.
Extracting node-v12.2.0-win-x64.7z ... done.
Linking D:\Applications\Scoop\apps\nodejs\current => D:\Applications\Scoop\apps\nodejs\12.2.0
Persisting bin
Persisting cache
Running post-install script...
'nodejs' (12.2.0) was installed successfully!
Installing 'nodejs11' (11.15.0) [64bit]
node-v11.15.0-win-x64.7z (9.3 MB) [===========================================================] 100%
Checking hash of node-v11.15.0-win-x64.7z ... ok.
Extracting node-v11.15.0-win-x64.7z ... done.
Linking D:\Applications\Scoop\apps\nodejs11\current => D:\Applications\Scoop\apps\nodejs11\11.15.0
Persisting bin
Persisting cache
Running post-install script...
'nodejs11' (11.15.0) was installed successfully!
```

インストール時に複数のアプリ名（バージョン）を指定してそれらをまとめてインストールしてみました。[^2]
[^2]: まったく別のアプリでも複数を指定すると指定した順にインストールしてくれます

インストールされたnode.jsのバージョンを確認してみます。

```powershell
PS C:\Users\user> node --version
v11.15.0
```
インストール時の指定の順番がもとで、現在は後からインストールされたバージョン11が設定されています。

### バージョンの切り替え
切り替えの方法を確認します。
同じアプリのバージョンを切り替えるには、`scoop reset`コマンドを使用します。
使用するバージョンは`scoop reset アプリ名（＝バージョン名）`とアプリ名（バージョン）指定して切り替えることができます。

```powershell
PS C:\Users\user> scoop reset nodejs #最新バージョンに切り替え
Resetting nodejs (12.2.0).
Linking D:\Applications\Scoop\apps\nodejs\current => D:\Applications\Scoop\apps\nodejs\12.2.0
Persisting bin
Persisting cache

PS C:\Users\user> node --version #確認
v12.2.0

PS C:\Users\user> scoop reset nodejs11 #古いバージョンにもどして
Resetting nodejs11 (11.15.0).
Linking D:\Applications\Scoop\apps\nodejs11\current => D:\Applications\Scoop\apps\nodejs11\11.15.0
Persisting bin
Persisting cache

PS C:\Users\user> node --version #確認
v11.15.0
```
あくまでもバージョン切り替えの確認のみでしたが、このように簡単に行うことができます。これは、Linuxのalternativesコマンドによく似ています。
独立したバケットを持つJavaやphpでも、同様にバージョン、**Javaなら各ベンダーのJDKすらも切り替えが可能**です。
これは開発時にバージョンを跨いで動作確認を行う場合などに非常に有用です。

## Scoopでのアプリのバージョンアップ
Scoopでインストール済みのアプリをアップデートする場合、`scoop update アプリ名`コマンドを使用します。
非常に手軽で便利です[^3]が、Scoopでのアップデートには、ちょっとした仕掛けがあります。
[^3]: もちろん、アプリのアップデート自体に破壊的変更がある場合には対応する必要がありますし、設定ファイル等のバックアップを取っておく方が安全です。

### アップデートの仕組み
手動でコマンドラインツールをアップデートする場合、インストール済みのフォルダへ新しいファイルを上書きすることが多いです。PATHや設定の保存場所など変更しにくい設定を引き継ぐ必要があるからですが、この方法では当然ながら、前のバージョンへの復旧は簡単ではありません。

Scoopのアップデートでは同じフォルダへ上書きするのではなく、新しいバージョンを別フォルダへインストールして、アプリへのパスを切り替える作業[^4]を行っています。この作業は、前述のresetコマンドと共通の作業です。
[^4]: 厳密にはジャンクションの接続先を切り替えてアクセスするバージョンのディレクトリを変えています [https://github.com/lukesampson/scoop/wiki/The-'Current'-Version-Alias](https://github.com/lukesampson/scoop/wiki/The-'Current'-Version-Alias)

このため、Scoopの管理下には、**旧バージョンのアプリはそのまま残っています**。（バージョンアップでの破壊的変更がなければ）設定などに使用されていた環境変数や、アプリへのPATHも変更されていません。
この仕組みによって、Scoopでインストールしたアプリにアップデートによって
不具合が生じていた時などに、アプリを旧バージョンへ切り替えることも可能になります。

### インストール済の同名アプリのバージョンを切り替える
`scoop reset`コマンドはヘルプによると、本来はアプリの（前述のような複数のバージョンの間での）コンフリクトを解決するコマンドとなっていますが、実際には同名のアプリのインストール済みのバージョン間での切り替えにも使用できます。[^5]
[^5]: ~~公式のドキュメントでも記載がなく、推奨される使い方ではないかもしれません~~ [公式のFAQ](https://github.com/ScoopInstaller/Scoop/wiki/FAQ#how-do-i-switch-between-different-versions-of-an-app)に記載がありました。

インストール済みのアプリのバージョンを切り替える場合、`scoop reset アプリ名@バージョン`と入力します。
既にインストールされているGitに「2.20.1.windows.1」と「2.21.0.windows.1」の2バージョンがあるとして、このバージョン間で切り替えを行ってみます。

```powershell
PS C:\Users\user> scoop reset git@2.20.1.windows.1
Resetting git (2.20.1.windows.1).
Linking D:\Applications\Scoop\apps\git\current => D:\Applications\Scoop\apps\git\2.20.1.windows.1
Creating shim for 'git'.
Creating shim for 'gitk'.
Creating shim for 'git-gui'.
Creating shim for 'tig'.
Creating shim for 'git-bash'.
Creating shortcut for Git Bash (git-bash.exe)

PS C:\Users\user> git --version
git version 2.20.1.windows.1

PS C:\Users\user> scoop reset git@2.21.0.windows.1
Resetting git (2.21.0.windows.1).
Linking D:\Applications\Scoop\apps\git\current => D:\Applications\Scoop\apps\git\2.21.0.windows.1
Creating shim for 'git'.
Creating shim for 'gitk'.
Creating shim for 'git-gui'.
Creating shim for 'tig'.
Creating shim for 'git-bash'.
Creating shortcut for Git Bash (git-bash.exe)

PS C:\Users\user> git --version
git version 2.21.0.windows.1
```

このように、インストール済みの同名アプリの間でも、バージョンを切り替えることができます。
ある時点で追加・修正された機能の互換性の確認や、アップデートで不具合が発生してしまった場合のロールバックとして利用できると思います。
前述のバケットによるバージョン管理と合わせると**Javaやnode.jsの頻繁なアップデートやセキュリティパッチも恐れる必要がありません**。とても喜ばしいことだと思います。

### インストール済みのバージョンを確認する
この機能を活用するために、過去のインストール済みのバージョンを確認するには、`scoop info アプリ名`が利用できます。
先ほどのGitで実行してみた場合の例です。

```powershell
PS C:\Users\user> scoop info git
Name: git
Version: 2.21.0.windows.1
Website: https://git-for-windows.github.io/
License: GPL-2.0 (https://spdx.org/licenses/GPL-2.0.html)
Manifest:
  D:\Applications\Scoop\buckets\main\bucket\git.json
Installed:
  D:\Applications\Scoop\apps\git\2.20.1.windows.1
  D:\Applications\Scoop\apps\git\2.21.0.windows.1
Binaries:
   cmd\git.exe cmd\gitk.exe cmd\git-gui.exe usr\bin\tig.exe git-bash.exe
Environment:
  GIT_INSTALL_ROOT=D:\Applications\Scoop\apps\git\current
Notes
-----
To get Git to recognise OpenSSH, you will need to run

scoop install openssh
\[environment]::setenvironmentvariable('GIT_SSH', (resolve-path (scoop which ssh)), 'USER')

and then restart powershell.
```
上記のように、`Insalled:`の末尾にインストール済みのバージョンが表示されます。バージョンの切り替えの際にはこの末尾の部分を指定してください。

### 同名アプリでのバージョン切り替えの注意事項
セキュリティパッチも巻き戻りが起こりますので、最新のバージョンに問題がないのであれば同名のアプリでは最新バージョンを利用するべきです。
また、versionsバケットやmainバケットにLTSなどの管理された保守バージョンがないかを検索しておくのが良いでしょう。

## 旧バージョンの削除(アンインストール)
前述のように、Scoopはアップデートを行っても旧バージョンの削除を行いません。コマンドラインのアプリの容量は大きくないものがほとんどですが、際限なくため込むこともできません。
これを解消するために、旧バージョンを削除する`scoop cleanup アプリ（バージョン）名`コマンドも用意されています。
ため込みすぎるのはよくありませんが、旧バージョンを保持しておく利点もありますので、適宜利用していくと良いでしょう。

## 最後に
開発環境のツールやアプリのアップデートには時間と手間が必要ですが、Scoopによってその負担が大幅に軽減されます。
特にScoopで管理されている言語のランタイムについては積極的にアップデートしつつ、随時`scoop reset`を利用していくことをお勧めします。