Windows用パッケージマネージャー Scoop の、マニフェストの作り方と自動更新のしかた

Scoop ではアプリのインストールの仕方をマニフェストという名のJSONファイルに書き、そのマニフェストの集合体をバケットと呼びます。

メジャーなものであれば、Scoop の公式バケットにもすでに充分な数のアプリがあります。ですがマイナーなアプリになると、あるいは日本人しか使わない(使えない)ものになると、意外と収録されていません。

ここでは、マニフェストの書き方にはじまり、その自動更新の仕方までをご紹介します。

そもそも?

そもそも、なぜわざわざマニフェストを自分で作るのでしょう?

公式リポジトリに要望を出せば、誰かが作ってくれるかもしれません。それに、アプリのインストールなんて「普通は」最初の一回だけなのだから、わざわざマニフェストを書く前にインストーラーをポチポチすればいいだけのはずです。

だからマニフェストを書く動機があるとすればそれは、複数回インストールする予定があるから、ということになるでしょうね。たとえば、Windowsの仮想環境を何度もセットアップする必要があるだとか、ですね。ただ私自身は、Windowsの新バージョンが出るたびに (年に複数回) PCをクリーンインストールするような奇特な人間なので、単純に自分自身のために必要だったというのが理由です。

バケットの作り方

マニフェストは単なる一つのJSONファイルにすぎないし、バケットといってもただのフォルダーです。

最初はわざわざバケットのことを意識する必要はないかもしれません。この段落を飛ばして、マニフェストの書き方の方を読んで下さい。

さて作り方ですが、適当なフォルダー (たとえば scoop-bucket とか) を作って、そこにbucketというフォルダーを作ります。

以上です。そこにマニフェストJSONを放り込んで行くわけです。

もしそのバケットGithubなどで公開するつもりなら、scoop-bucket の直下に README.mdLICENSE くらいは用意しておきましょう (後者はGithubに公開してからでも追加できます)。

この記事を読むような人であればターミナルの使い方に習熟していらっしゃるでしょうからコマンドで説明すると、

cd scoop-bucket
mkdir bucket
touch README.md
git init

# その後は
# cd bucket
# touch アプリ名.json
# git add
# git commit

といった流れです。

マニフェストの書き方

さて本題に入りましょう。

Scoop のWikiに情報がまとまっているので、迷ったら参照してください。

github.com

今回はこれを題材にして解説します。MIDI編集ソフト Dominoマニフェストです。

基本となるテンプレート

どんなプロパティがあるかの説明をしながらだと遠回りになるので、テンプレートを提示しておきます。ほぼ全てのプロパティを準備してあるので、作成の際はここから要らないものを取り除いていくだけです。

これをコピーして使ってくださっても結構です。というのも、このGithubリポジトリのライセンスはCC0、つまりパブリックドメインなので。

ただし今回はイチから書き始めていくことにします。

0. ファイルを作る

ここでつけたファイル名がそのまま、インストールするときのアプリ名になります。アルファベット大文字小文字およびハイフンが使えます。公式的には全て小文字で書くルールになっているようです。

今回は、 domino-test.json とでもしておきます。

1. description, homepage

マニフェストの中で必須プロパティは、

  • 説明文
  • 公式サイト
  • ライセンス
  • バージョン番号

の4つです。

まずはそのアプリの公式サイトと簡単な説明文を書きます。公式サイトが無くて、たとえばベクター窓の杜などで配布されているものはそちらを書いても良いかもしれません。

ここまでの記述:

{
    "version": "",
    "description": "Domino: MIDI editor",
    "homepage": "https://takabosoft.com/domino",
}

2. license

次にライセンスを指定します。

フリーソフトとして配布されているものであれば、たいていの場合は Freeware とだけ書けば充分だと思います。

その他明示的に GPLMIT などと指定されている場合は、それに従います。その際、 SPDX ライセンス一覧 に書かれている表記を使います。

{
    // ...省略
    "license": "Freeware",
}

3. checkver

であれば次はバージョン番号…となりそうなものですが、実は書かなくても大丈夫です。後述の自動更新スクリプトにより、自動でセットされます。

そしてその自動更新のために、バージョン番号はどこから取得すればいいかを checkver に指定します。

たいていのアプリは、配布サイトのどこかにバージョン番号が書かれているので、それを探します。

Domino の場合はおあつらえ向きに更新履歴のページがありました。これを checkver.url に書きます。

そのHTML内で、バージョン番号にあたる部分を正規表現で抜き出します。今回の場合は H4 タグにバージョン番号が書かれているので、その部分にマッチする正規表現を書き、これを checkver.regex に書きます。

複数マッチする場合は、最初の一つが使われます。

正規表現で置換したときに $1 になる部分にバージョン番号が入るようにすればOKです。

{
    // ...省略
    "checkver": {
        "url": "https://takabosoft.com/domino/releasenotes",
        "regex": "<h4>Domino Ver.([\\d.]+)"
    },
}

4. autoupdate

checkver で取得されるバージョン番号をもとに、アプリをダウンロードするURLを指定します。

Domino の場合は、 https://takabosoft.com/download/win/domino/Domino144.zip となっており、 144 の部分にバージョン番号を当てはめれば今後のバージョンアップにも追随できると思われます。

そして、さきほど正規表現にマッチしたバージョン番号は $version という変数で受け取れます。ただ今回は、 Domino144.zip のように、ピリオドを抜いた形で書かなければなりません。こんなときは代わりに $cleanVersion を使います。その他、色々なパターンのバージョン番号に対応できるよう、様々な変数が用意されています。

一覧は バージョン変数の一覧 に書かれていますが、ここにも掲載しておきます。

変数
$version 3.7.1
$underscoreVersion 3_7_1
$dashVersion 3-7-1
$cleanVersion 371
$matchHead ピリオド区切りで最初の2または3個の数字。
  • 3.7.1-rc.1 → 3.7.1
  • 3.7.1.2-rc.1 → 3.7.1
  • 3.7-rc.1 → 3.7
$matchTail $matchHead の残り。
  • 3.7.1-rc.1 → -rc.1
  • 3.7.1.2-rc.1 → .2-rc.1
  • 3.7-rc.1 → -rc.1
$preReleaseVersion 最後の-以降の全て
  • 3.7.1-rc.1 → rc.1

$version3.7.1.2 だったとすると、ピリオドで区切られて各部が変数に入ります。

変数
$majorVersion 3
$minorVersion 7
$patchVersion 1
$buildVersion 2

正規表現で捕獲した文字列は、順に $match<数字> として使えます。正規表現で名前付き捕獲を使うと、 $match<名前(先頭大文字)> として使えます。

v3.7.1/3.7 という文字列にマッチさせるために v(?<version>[\d.]+)\/(?<short>[\d.]+) という正規表現を使った場合、以下のようになります。

変数
$match1 または $matchVersion 3.7.1
$match2 または $matchShort 3.7
{
    // ...省略
    "autoupdate": {
        "url": "https://takabosoft.com/download/win/domino/Domino$cleanVersion.zip#/dl.zip"
    }
}

手動でバージョン番号の更新

さて、ここまで書けたら、一旦、バージョン番号がちゃんと取得できるか試してみましょう。

と、その前に注意点が3つ。

  • 空文字 ("") ではなく何かしら文字を入れておくこと ("-" とか)。スクリプトの仕様で、空文字をセットしたプロパティは、存在しないのと同じと解釈されるようで、エラーのもとになります。
  • マニフェストの動作テストをするときは、バージョン番号をゼロなどできるだけ低くする。checkver で取ってきたバージョン番号よりも version の番号の方がが大きいとき、更新が行われません。
  • URL末尾の #/dl.zip については、今は気にしないでください (後述します)。今回はなくても動きますが、これこそ scoop のポータブル化の仕組みの秘密です。
{
    "version": "0",
    "description": "Domino: MIDI editor",
    "homepage": "https://takabosoft.com/domino",
    "license": "Freeware",
    "url": "-",
    "hash": "-",
    "checkver": {
        "url": "https://takabosoft.com/domino/releasenotes",
        "regex": "<h4>Domino Ver.([\\d.]+)"
    },
    "autoupdate": {
        "url": "https://takabosoft.com/download/win/domino/Domino$cleanVersion.zip#/dl.zip"
    }
}

scoop のインストールされたフォルダー内に、 checkver.ps1 というPowershellスクリプトが付属しています。

# マニフェストのあるフォルダーに移動しておく
cd scoop-bucket\bucket

# 「scoop prefix <アプリ名>」 で、そのアプリのインストール場所が取れる
& "$(scoop prefix scoop)\bin\checkver.ps1" -App domino-test

バージョン番号が出ましたか? domino: 1.44 (scoop version is 0) autoupdate available のようになれば成功です。

それでは、-Update 引数を付けて実行します。これにより、最新バージョンの番号とURLがマニフェストに書き込まれます。今後はいきなり -Update 引数を付けて実行しても問題ありません。

& "$(scoop prefix scoop)\bin\checkver.ps1" -App domino-test -Update

ちなみに -Force フラグを付けて実行すると、前述のように、checkver で取ってきたバージョン番号よりも version の番号の方がが大きくても、強制的に更新しようとします。

& "$(scoop prefix scoop)\bin\checkver.ps1" -App domino-test -Update -Force

5. bin, shortcuts

ここまでのステップで、すでにscoopでのインストールはできる状態になっています。

ですが今のままでは、スタートメニューのショートカットがないのでアプリを起動するのが少し面倒です。なのでショートカットを追加していきます。

インストールされると、本体は C:\Users\<ユーザー名>\scoop\apps\domino\current\Domino.exe というところにあります。

C:\Users\<ユーザー名>\scoop\apps\domino-test\current\ を基準として、そこからの相対パスでショートカットを指定します。配列の1つ目が実行ファイルへのパス、2つ目がショートカットの名前です。

bin も同様にショートカットなのですが、こちらはターミナルから起動する際のコマンド名です。今回の場合、Dominoをターミナルから起動する意味はほとんどないので省略しても良かったのですが、あとから追加するのも面倒なので書いておきました。

{
    // ...省略
    "bin": "Domino.exe",
    "shortcuts": [
        [
            "Domino.exe",
            "Domino"
        ]
    ],
}

注意として、以下のような1階層の配列ではありません。配列の配列で書きます。

{
    // ...省略
    "shortcuts": [
        "Domino.exe",
        "Domino"
    ],
}

6. persist

これは何かというと名前のとおり、設定ファイルを「persist (存続)」させるものです。ここに指定したファイルやフォルダーを専用の場所に隔離します。

これにより、

  • バージョンアップを経ても同じ設定を引き継げる (scoopではバージョンアップごとに上書きせず、別フォルダーにインストールする)
  • ポータブル化しつつ、バックアップが容易になる

などの利点があります。

隔離といっても、移動してしまってはアプリの動作に支障をきたすので、persistフォルダー内のファイルと、それが本来ある場所とをシンボリックリンク (ないしハードリンク) で繋ぐことで実現しています。

Domino の場合、起動すると IniFiles というフォルダーが作られ、そこにユーザーごとの設定が記録されていきますので、これを指定します。

{
    // ...省略
    "persist": "IniFiles",
}

ただし、フォルダーでなくファイル (たとえば、 settings.ini みたいな) を persist したい場合に一癖あるので、ご注意を。そのままだと、隔離先に settings.ini という名前の「フォルダー」が作られてしまいます。これを回避するには、別のプロパティ pre_installスクリプトを書かなければならないのですが、これはまたの機会に。私のリポジトリ内でもいくつかこれを使っているので、必要な場合はご覧ください。

インストールしてみる

ここまで書いたら、最初に掲示したJSONと同じものが出来上がっているはずです。

では、これを使って実際にインストールしてみます。

# マニフェストのあるフォルダーに移動しておく
cd scoop-bucket\bucket

scoop install .\domino-test.json

うまくいきましたか?これで無事にマニフェストが一つ出来上がりました!

スタートメニューを開いて、「全てのアプリ」→「Scoop Apps」フォルダーを開いてみてください。そこに今記述したアプリが追加されているはずです。

公開後

このバケットGithub などで公開した場合は、忘れずに自分のバケットを sccop に追加しておきましょう。

# scoop bucket add <バケット名> <バケットURL> の形式。
scoop bucket add <わかりやすい名前> https://github.com/<ユーザー名>/<リポジトリ名>

その後、 scoop update すると、あなたの作ったマニフェストが利用できるようになっています。

ただし、いきなりインストールする前に、さきほどローカルのJSONで試しにインストールしたほうをアンインストールしておきましょう。インストールのときは拡張子つきで名前を指定しましたが、アンインストールのときはアプリ名だけで大丈夫です。

ローカルでインストールしたのかリポジトリ経由でインストールしたのかわからなくなった場合は、

scoop list

とすると、 Source 欄にインストール元が書かれているので、その中にローカルのファイルパスが書かれているかどうかで判別できます。

マニフェストの自動更新

マニフェストができたからといって、これで終わりではあまり意味がありません。もしそのアプリに新バージョンが公開されても、それに追随できないからです。

上で書いたように、手動で更新コマンドを毎日実行するというのも悪くないと思いますが (私も最初はそうしていました)、どうせなら自動で更新するようにしてみましょう。

git と Github を使ったことがあるなら、おすすめは Github Actions を使う方法です。とはいえ自分でイチからワークフロー設定を書き上げる必要はありません。 C:\Users\<ユーザー名>\scoop\buckets\main\.github\workflows に、公式も使っているワークフローが置いてあるので、これをお借りしましょう。

さきほどのフォルダーを、あなたのバケットにコピーしてきます。バケットscoop-buckets という名前であれば、 scoop-buckets\.github\workflows です。

そして、マニフェストも含めてそれらを git にコミットします。あとはこの scoop-buckets リポジトリGithub にプッシュすれば、一日に数回、全自動でマニフェストを更新してくれます。

Actions の実行結果は、Github上であなたのリポジトリを開き、上のメニューにある 「Actions」から確認できます。

ちなみに、 excavator.yml が自動更新を担当しているのですが、その中に実行間隔を指定する箇所があります。標準では一日に6回も更新を確認するようになっています。さすがにそんなに頻繁にチェックしなくても問題ないと思いますので、頻度を下げておいてもいいと思います。自分は以下のように、12時間ごとに(一日に2回)チェックするようにしています。

  schedule:
    # run every 12 hours
    - cron: '20 */12 * * *'

#/dl.zip

マニフェストの URL 欄に、 #/dl.zip という見慣れない文字列が付いていました。これについても紹介しておきます。

記事で取り上げたDominoはもともとzip形式で配布されているので、インストール作業はそれを解凍するだけで済みました。

ですが他のアプリでは、.exe.msi といったインストーラー形式のこともあります。こういった場合に意味を持つ指示なのです。

URL の末尾に #/ およびファイル名を書いておくと、 scoop はそのインストーラーを指定したファイル名で保存します。たとえば元の名前が installer.exe だったとして、 installer.exe#/dl.7z と指定すると、ファイルは dl.7z という名前で保存されます。

これで何がいいかというと、 exeファイルを 7-zip で解凍できるようになるのです (7-zip 自体は scoop のインストール時に自動でインストールされています)。そうすると、インストーラーを起動せずに、直接中身を取り出せるようになります。こうして scoop は、インストーラー付きのアプリでもポータブル化することができるわけです。

その他

他にも様々なプロパティがあるので、見てみてください。結構自由度が高いです。

  • ##: コメントが書ける。
  • architecture: 32bit 版と 64bit 版がある場合にそれぞれの動作を記述できる。
  • depends: アプリの依存関係を定義できる。依存アプリは、まだインストールされていなければ自動でインストールされる。
  • env_set: 環境変数をセットできる。
  • extract_dir: zipファイルの中の特定のフォルダーだけを取り出す。zipの中に同名のフォルダーがある場合に有用。
  • installer: 独自のインストール処理を記述できる。標準機能では不十分なときに。
  • notes: インストール後にターミナルに書かれる、ユーザーへのメッセージ。
  • pre_install: インストール直前の処理を記述できる。私は、もっぱらファイル (フォルダーでなく) を persist するときに使っている。
  • post_install: インストール直後の処理を記述できる。
  • shortcuts: 実はショートカットの引数やアイコンも指定できる。
  • url: 実は複数のURLを指定できる。