Misskeyを開いたら、それはミス廃への片道切符。もう、後戻りはできない―――。 …ふざけました。NPLです。
今回はMisskey専用クライアントアプリ「Miria」のSnapパッケージ化の記録を簡単にまとめていきます。
Advent Calenderの季節ですが、特に関係はありません。
注意
この記事の内容はほとんどプログラミングをしたことが無い人間によって書かれています。間違った内容が紛れている可能性が高いため、後述する参考記事や、その他複数の情報を参考にしながら読み進めてください。特に、この記事では解説していない内容も多数存在するため、Snapcraft.ioのドキュメントを参考にしてください
Miriaとは
Miriaは、そらいろ氏が開発した、FlutterベースのMisskeyクライアントアプリです。
Misskeyに特化した機能開発による「複数アカウントのタブ切り替え」や「(初の)MFMの互換表示」などの特徴が存在します。
タブには通常のHTLやLTLに限らず、リスト・アンテナ・チャンネル・ロールなどを割り当てることもできます。また、Misskeyクライアントとしては珍しいWebViewを使わないMFM互換表示を利用することができます。
そんなMiriaですが、Flutterをベースに開発されていることから、Android・Windows・iOS/iPadOS/macOSでサポートされています。
そして、Flutter SDKを利用すれば、Linux用アプリを同一のコードでビルドすることができます。
今回は、MiriaをSnapパッケージで利用できるようにするため、snapcarft
に挑戦してみることにしました。
注意:MiriaのSnapパッケージを作成する話であり、現在Snap Storeを検索してもMiriaは存在しません。
Snapパッケージについて
SnapパッケージはUbuntuの開発元Canonicalが推奨しているパッケージです。debパッケージと違い、必要な依存関係を開発元が本体のパッケージにまとめて配布する方式です。snap
コマンドやSnap Store(Snapcraft.io)からパッケージのインストールを行うことができます。
debパッケージやrpmパッケージの方がユーザー数は多いとは思いますが、Flutter公式ドキュメントにSnapパッケージ化の方法が存在することや、Snapcraft側にもドキュメントが揃っていることから、Snapパッケージを選択しました。
Flatpakじゃだめだったの?
日本語記事が少ない時点で諦めました。
一応パッケージ化することはできたものの、仕様の理解が進まなかった故、維持管理に問題のある状態の設定ファイルが出来上がってしまったため、お蔵入りになりました。
参考
この記事の内容のほとんどは、gihyo.jpの「Snapパッケージ入門」を参考にしています。この記事を読む前に、参考記事の方を読むことをオススメします(この記事より詳細なことが書かれており、そちらの方が理解しやすいため)。
MiriaをSnapパッケージ化する
ここでは、Flutter公式ドキュメントに従ったパッケージングを行います。
これからFlutterアプリをSnapパッケージにするときはSnapcraftのドキュメントを読むことをオススメします。(理由)
1, プロジェクトフォルダ内にSnapパッケージ用ファイルを作成する
ここではmiria-snap
ディレクトリをプロジェクトフォルダとして作業を行います。
事前にsnapcraft
をSnapでインストールしておきます。
~$ sudo snap install snapcraft --classic
プロジェクトフォルダに入り、Snapパッケージ作成のためのファイルを追加します。
~$ mkdir miria-snap && cd miria-snap
~/miria-snap$ snapcraft init
これでプロジェクトフォルダ内にsnap
フォルダとsnap/snapcraft.yaml
ファイルが作成されました。
2, snapcraft.yaml
をドキュメントに習って編集する
初期状態のsnapcraft.yaml
の中身はこのようになっています。
name: my-snap-name # you probably want to 'snapcraft register <name>'
base: core22 # the base snap is the execution environment for this snap
version: '0.1' # just for humans, typically '1.2+git' or '1.3.2'
summary: Single-line elevator pitch for your amazing snap # 79 char long summary
description: |
This is my-snap's description. You have a paragraph or two to tell the
most important story about your snap. Keep it under 100 words though,
we live in tweetspace and your description wants to look good in the snap
store.
grade: devel # must be 'stable' to release into candidate/stable channels
confinement: devmode # use 'strict' once you have the right plugs and slots
parts:
my-part:
# See 'snapcraft plugins'
plugin: nil
まずはこのファイルをFlutterドキュメントに合わせたものに変更します。
- name: my-snap-name # you probably want to 'snapcraft register <name>'
+ name: miria-snap
- base: core22 # the base snap is the execution environment for this snap
+ base: core18
- version: '0.1' # just for humans, typically '1.2+git' or '1.3.2'
+ version: '1.0.13+87'
- summary: Single-line elevator pitch for your amazing snap # 79 char long summary
+ summary: Misskey Client for Mobile
description: |
- This is my-snap's description. You have a paragraph or two to tell the
- most important story about your snap. Keep it under 100 words though,
- we live in tweetspace and your description wants to look good in the snap
- store.
+ Misskey Client App built with Flutter
grade: devel # must be 'stable' to release into candidate/stable channels
confinement: devmode # use 'strict' once you have the right plugs and slots
parts:
- my-part:
+ miria-snap:
# See 'snapcraft plugins'
- plugin: nil
+ plugin: flutter
+ source: https://github.com/shiosyakeyakini-info/miria.git
+ flutter-target: lib/main.dart
+
+ apps:
+ miria-snap:
+ command: miria
+ extentions: [flutter-stable]
今はFlutterドキュメントに合わせてcore22
からcore18
に変更していることを覚えておいてください。
基本的に前半部分はアプリの名前や説明欄などを編集しています。
confinement
はユーザーに対するアプリのセキュリティ設定であり、開発中はdevmode
を選択しますが、リリース時にはstrict
で動くようにしなければなりません(ただし、エディターなどのファイルの書き込みの制限を緩める必要のある場合にはclassic
が使用できます)。
parts
では、パッケージに含めるプログラムの追加などを行います。今回の場合、miria-snap
のパーツとして、MiriaのソースコードをGitHubから取得し(source)、Flutterのビルド環境を用意して(plugin)、lib/main.dart
を対象としたビルドを行う(flutter-target)ように記述しています。
apps
では、実際に実行するコマンドの記述などを行います。今回はアプリ名がmiria-snap
であるため、apps:miria-snap:
の並びで作成します。アプリのインストール後に端末からmiria-snap
を入力してアプリを実行することができるようになります。
apps:command:
には、実際に実行されるプログラムのコマンドを入力します。ここではビルドしたmiria
の実行ファイル名"miria
"を入力します。
Flutterはビルド成果物をbuild/linux/x64/release/bundle
以下にlib, data, miria
を生成しますが、snapcraft
はそれらをSnapアプリのフォルダのルートに配置します。つまり、コマンドの内容は{Snapパッケージのフォルダルート}/miria
のような配置になります。
apps:miria:
としてしまった場合、アプリ名とコマンド名が一致せず、端末からの実行時にmiria-snap.miria
と入力しなければならないため、apps
以下の名前とsnapcraft.yaml
先頭のname
は一致するようにします。
apps:miria-snap:extentions:
には、Flutterに関連するplugs
や環境変数などを自動的に追加するためflutter-stable
を入力します。
基本的な内容はこれで書き終えたので、このままsnapcraft build
を実行すればSnapパッケージのビルドが開始されます。
ただし、このままではMiriaのビルドは完了しません。
3, 依存関係を追加する
ログを確認すると、Snapパッケージのビルド中の"Miria本体のビルド"が完了できていません。これは依存関係が不足していることが原因です。
Miriaではflutter_secure_storage
とmedia_kit
が使用されており、ビルド時にはlibsecret-1-dev, libmpb-dev
が、ユーザーの使用時にはlibsecret-1-0, libmpv1
が必要となります。
このため、ビルド専用の依存関係と、Snapパッケージに同梱する用の依存関係を追加します。
依存関係をparts:miria-snap:
以下のそれぞれbuild-packages
とstage-packages
に記述しました。
[...]
parts:
miria-snap:
[...]
+ build-packages:
+ - libsecret-1-dev
+ - libmpv-dev
+ stage-packages:
+ - libsecret-1-0
+ - libmpv1
参考1: flutter_secure_storage | Flutter Package libjsoncpp
は最近のバージョンから不要になりました。
参考2: media_kit | Dart Package mpv
をstage-packages
に含めるとMPVプレーヤーがデスクトップアプリとして一緒にインストールされてしまうのでlibmpv
のみで十分です。
これで再度snapcraft build
を実行すると、Miria本体のビルドまで実行することができます。
これ以降のステップに進むには、一旦生成されたSnapパッケージをインストールして、動作を確認しながら進めていくことをおすすめします。
~/miria-snap$ sudo snap install --dangerous --devmode ./miria-snap_1.0.13+87_amd64.snap
4, セキュリティレベルをstrict
にする
ある程度動くものになってきたため、今度はconfinement
をdevmode
からstrict
に切り替え、必要な機能の洗い出しを行います。
[...]
- confinement: devmode
+ confinement: strict
Snapでは必要な機能のみをplug
で接続する必要があります。
extentions
にflutter-stable
を設定しているので、desktop, desktop-legacy, gsettings, opengl, wayland, x11
は、自動的に接続されます。
この時点で再度ビルドを実行し、すべての機能が動作するか確認します。Miriaの場合、"動画の音声が聞こえない"、"ログイン情報が保存されていない"という、2つの不具合がみつかりました。
音声の再生は、audio-playback
というプラグ、ログイン情報の保存はpassword-manager-service
というプラグによって管理されているため、この2つをapps:
以下に追加します。
[...]
apps:
[...]
+ plugs:
+ - audio-playback
+ - password-manager-service
参考: The flutter extension | Snapcraft documentation
ただし、password-manager-service
は機密情報を扱うプラグであるため、アプリをインストールしても自動ではプラグが機能しないようになっています。 ユーザーにアプリを公開する前に、password-manager-service
へのアクセスを許可する操作を行うよう告知することを忘れないでください。
$ sudo snap connect miria-snap:password-manager-service
参考: Snapcraft.yaml reference | Snapcraft documentation
5, ファイル選択ダイアログを表示できるようにする
Miriaには画像ファイルをノートに添付して投稿する機能がありますが、SnapにしたMiriaではファイル選択のメニューが表示されなくなっています。
Miriaのファイル選択機能はflutter_file_picker
によって呼び出されていますが、flutter_file_picker
内部ではzenity --file-selection
(もしくは、qarma
やkdialog
でそれに類するコマンド)を呼び出しています。
Ubuntu Desktop自体にはzenity
が含まれているものの、Snapからは呼び出すことができないため、zenity
をパッケージに含めるようにする必要があります。
FLutterはGTKを使用したアプリであるためzenity
で統一しました。
[...]
parts:
[...]
+ zenity:
+ plugin: nil
+ stage-packages:
+ - zenity
+ prime:
+ - usr/bin/zenity
+ - usr/share/zenity/*
parts
以下に、miria-snap
とは別にzenity
を作成し、zenity
を用意して、prime
でパッケージに関連するファイルをSnapパッケージに含めるように指定しました。
参考: Inform users with custom dialogs - doc - snapcraft.io
6, デスクトップアイコンを追加する
今のままではデスクトップアプリなのにアプリのメニューボタンが存在しない状態になってしまうので、デスクトップエントリーファイルとアイコン画像を追加します。
デスクトップエントリーファイルとアイコン画像は次のコマンドでsnap/gui
ディレクトリを作成して、そこに追加します。
~/miria-snap $ mkdir -p snap/gui
~/miria-snap $ touch snap/gui/miria-snap.desktop
デスクトップエントリーファイルには以下の内容を追加します。
[Desktop Entry]
Version=1.0
Name=Miria Snap
GenericName=Misskey Client
Type=Application
Exec=miria-snap
Icon=${SNAP}/meta/gui/miria-snap.png
Comment=Misskey client app for mobile (Linux build)
Comment[ja]=モバイル向けのMisskeyクライアントアプリ(Linux向けビルド)
Keywords=Misskey;Miria;みりあ
Terminal=false
StartupNotify=false
Exec
にはアプリ名を追加します。Icon
はsnap/gui/mria-snap.png
を使用する場合、snap
以下のファイルはSnapパッケージ作成時に${SNAP}/meta
以下にコピーされるため${SNAP}/meta/gui/miria-png.png
を指定します。
keywords
は、Firefoxのデスクトップエントリーを見たところ、各言語ごとに分ける必要がありそうです(キーワードが多くなければ1つにまとめたままでもいいとは思いますが…)。
7, バージョン情報を自動で取得する
現在はversion
を直接記述しています(version: 1.0.13+87
はこの記事作成時の最新バージョン)が、Snapパッケージビルド時にこのバージョンを手動で書き換えるのは面倒なので、毎回バージョン情報を自動で取得するように変更します。
1, 直接記述されたバージョン情報を削除
[...]
- version: 1.0.13+87
+ adopt-info: miria-snap
version
を削除する代わりに、バージョンを取得する処理を行うパーツ名(今回はmiria-snap
)をadopt-info
に追加します。
2, ソースコード取得の処理を上書き
本来自動的に行われるソースコードの取得処理に変更を加えます。
[...]
parts:
miria-snap:
+ override-pull: |
+ snapcraftctl pull
+ VERSION=$(cat pubspac.yaml | grep "version[:]" | cut -d " " -f 2)
+ snapcraftctl set-version "$VERSION"
snapcraftctl pull
は本来行われるソースコードの取得処理を行うコマンドです。
VERSION
変数に、pubspac.yaml
に記述されたバージョンを代入して、snapcraftctl set-version
に渡しています。
Miriaではflutter pub run cider version
でバージョン情報のみを取得する適切な方法がありますが、この時点ではFlutterのセットアップが済んでいないため利用できません。そのため、cat, grep, cut
を駆使した酷いコードになっています。
参考1: 第660回 自作のsnapパッケージをSnap Storeに公開する | gihyo.jp
参考2: Override build steps | Snapcraft documentation
完成(のはずだった)
これにてリリース可能なMiria Snapパッケージが完成しました。お疲れ様でした。
…で終わらなかった。
core18
からの脱却
ここまで作ってきたSnapパッケージはcore18
というUbuntu 18.04 LTSベースのビルドが行われていました(Snapパッケージビルド時に起動するLXDコンテナもUbuntu 18.04です)。
しかし、Ubuntu 18.04 LTSのサポート期限はリリースから5年の2023年であり、MiriaのSnapパッケージを作成し始めた頃にはサポート終了の直前でした。Snapcraftも同様にcore18
のサポートを終了することは容易に予想でき(そして的中し)たので、Ubuntu 22.04 LTSベースのcore22
に移行する必要があります。
core22
に移行する
core18
からcore22
の間にはいくつか変更点があります。
1, Flutter Extentionの廃止
core18
ではextentions
にflutter-stable
を設定することでplug
などが自動的に接続されていましたが、core22
では用意されていません。
代わりとしてgnome
を選択する必要があります。
flutter-stable
とgnome
のプラグの内容はほとんど同じなので不要だったのかもしれない。
2, snapcraftctl
の廃止
override-pull
やoverride-build
内で処理を上書きするときに、既定の処理を呼び出すsnapcraftctl
が廃止され、代わりにcraftctl
を利用するようになりました。
override-pull
内でsnapcraftctl pull
としていたコマンドやoverride-build
内でsnapcraftctl build
としていたコマンドなどは、どちらもcraftctl default
に置き換えられます。
参考: Using the craftctl tool | Snapcraft documentation
置き換える
上記の2つを置き換えていきます。
extentions
[...]
apps:
[...]
- extentions: [flutter-stable]
+ extentions: [gnome]
上記で記した通り、flutter-stable
をgnome
に置き換えるだけです。使用するplugs
に変更はないのでそのままです。
snapcraftctl
[...]
parts:
miria-snap:
[...]
- snapcraftctl pull
+ craftctl default
VERSION=$(cat pubspac.yaml | grep "version[:]" | cut -d " " -f 2)
- snapcraft set-version $VERSION
+ craftctl set version=$VERSION
上記で記したsnapcraftctl pull
→craftctl default
の他、set-version
オプションをset version=<version>
に置き換える必要があります。
これでコマンドの置き換えなどが完了しました。
これにてMiriaのSnapパッケージ化は一旦完了です。
まとめ
今回は、MiriaをSnapパッケージにするまでの流れを解説(記録?)しました。
基本的にはgihyo.jpの記事を参考に大まかに内容を理解してから、Snapcraftのドキュメントを参考に詳細を埋めていく形でMiriaをSnapパッケージにすることができました。
作ってみた感想としては、「とりあえずパッケージ化して動くものを作りたい」という面では悪くない選択肢ではないかと思います。
また、「もう少し日本でSnapパッケージを利用する記事増えないかな〜?」と期待しています。
今後
今回はパッケージ化するところ(「とりあえず動くものを作る」段階)まで記事にしましたが、今のままでは起動時間が長いことやパッケージサイズが200MBとちょと大きいという問題を抱えているため、これを改善する(「細かい調整」の段階)内容を時間があればまとめていきたいと思います。