たすけてあっぷる、iPhoneのロック画面に閉じれないモーダルダイアログが出たの!
一見ロック画面に表示されるただのダイアログなんですが、どちらのボタンを押しても閉じてくれません。
経緯
ノイズキャンセリングヘッドセットはBoseのQC35IIを使ってます。
もう5年半か……
これ今はもう珍しくもないBluetoothで複数デバイスに繋がるもので、MacとiPhoneのPCとMacとMacに繋がるようにしています。 ただ複数に繋がってるとノイズが入ったり、Macに繋がってる時にiPhoneに通知来て音鳴ったりしたらそっちに切り替わったりするので、安定して使いたい時は接続切ってます。
で、繋がってる状態でiPhoneのBluetoothボタンでプチっと切ると冒頭のダイアログが出ます。 普段はそのまま閉じるだけなんですが、今回はBluetooth切断→ダイアログ表示間いろんな操作をした結果、ロック画面に操作閉じてくれないダイアログが出る状態になりました。
試行錯誤
iPhoneは少し前からボタンを長押しするだけだと電源切ったり再起動はできなくなっています。
(ダイアログ出てるスクショとれなかったけど、この上に新規接続云々のダイアログが出てます)
長押しすると「スライドで電源オフ」が出て、それスライドすると切れるのですが、今回はダイアログが邪魔してスライドできない。 画面操作できない状態でも再起動したいはあるあるで、強制再起動的なのはないのかと調べたらありまして。
iPhoneを強制的に再起動する - Apple サポート (日本)
まー手順用意されてるよね、うんうん。へぇ面白い操作だなぁと思いながらやってみた。
……緊急電話のカウントダウンが始まるんだが???
iOSのバージョンも合ってるしなぁ、どうにかならないかなぁとMacに繋いでみたりしたんだけどどうもできなくて。 パスコード入力の画面は出るけどダイアログがあるからダメだし、FaceIDにもなってくれないし……
これはもしかしてAppleStoreに持ち込むか?いや電池切れるの待つか?でもフル充電しちゃってるぞ??とか思ってたところで、AppleStoreの場所を確認しとくかとサポートページを開いたら、問い合わせがあったのを思い出しました。電話はやだけどチャットならなんとか頑張れるかもしれない、頑張れ私。(よく考えたらiPhoneロックされてるから電話もできないわ。)
問い合わせ
ということでAppleのチャットサポートに聞いてみました。 ウィンドウひらいて2分くらいで応答してくれたので、状況とやってみたことをぶちまける。
そしたら「お困りですよね、急いでますよね」というよりそうメッセージに続けて「強制再起動してみて」とページを案内されました。
「やったけどなぁ……」と思いながら開いてみたら違うページだった。 で書いてる通りにやってみたら再起動できた。
そっこー解決した。ありがとうあっぷる!
(解決時のツイート)
改めて確認すると
違うページだけど、見直してみると手順は同じ。
1つ目にみたページの画像で、「音量下げボタンを押しっぱなしでサイドボタンを押す」と誤認していた模様。焦るとダメだね!
案内してくれたページはアニメーションGIFになっていたので、間違えずにできたようです。
なるほどねー気づかなかったー。
……てか今気づいた。
これ書き始めた時は「1つ目の通りにやっても強制再起動できないんですけど!?」って書くつもりだったのに、ただの私のうっかりを晒すだけのものになってしまった。つらい。
API仕様をHTMLファイルで出力する(SpringBoot+Redocly CLI)
- SpringBoot 3.1.0
- springdoc-openapi 2.1.0
- springdoc-openapi-gradle-plugin 1.6.0
できるようになること
./gradlew generateOpenApiHtml
このコマンドを実行するだけで ./build/redoc-static.html
ができます。この子は「zero-dependency HTML」なので適当な場所にこのファイルだけ持っていって大丈夫です。CIで出力したあとに持っていくファイルが少ないのは楽でいいですよね。
あ、CDNのJSとかフォントとか使ってるのでインターネットに繋がる環境にはしてね。
今回はこれができるようになるまでの仕組みをだらだら書くよ。だらだら。
springdoc-openapiでAPI仕様を見る
SpringBootでは springdoc-openapi を使うと http://localhost:8080/swagger-ui/index.html でSwagger UIを使ってブラウザで見れるようになります。
こんなの。
これで満足なことも多いのですが、実務では静的HTMLファイルが欲しいってなったりします。こういう時に:
- Swagger UIはアプリケーションが動作している時にしか見れない
- そのためアプリケーションにアクセスできない状態(ダウンしていたり、権限的な問題があったり)だと使用できない
- 開発中のAPIが半端に見えると混乱する
- 第三者に渡したい
「ブラウザで見れるならそのまま持っていったら?」と思うかもしれませんが、Swagger UIはそれ自体で情報を持っているわけではありません。
springdoc-openapiを使用すると org.webjars:swagger-ui
が依存に入りますが、このリソースがそのまま表示されているだけです。
詳しくは springdoc-openapiのドキュメントを見てください。依存関係の図とかわかりやすいです。
http://localhost:8080/swagger-ui/index.html をブラウザで開いて「ソースを表示」ってやるのと GitHub/swagger-api/swagger-uiのindex.html が完全に一致することからも素のSwaggerUIでしかないことが分かるかと思います。
ではアプリケーションのAPI情報はどのように取得しているかですが、 http://localhost:8080/v3/api-docs で見れるJSONがそれで、springdoc-openapiによってOpenAPI仕様で出力されるようになっています。springdoc-openapiの本業はこれで、SwaggerUIで表示できるのはおまけ的な機能です。
流れをおさらい。
実際のフレームワークの動きとは異なっています(このタイミングでControllerにアクセスしたりしない)が、どこにあるかのイメージです。SwaggerUIの時点ではアプリケーションに関係なく、分けられるってところを伝えたい感。
ブラウザの動きは開発ツールとかで見てみるとわかるかと。
index.html
開いてから api-docs
取りに行っておりますね。
だもんでSwagger UIは独立したアプリケーションであり、別にローカル以外でも適当なOpenAPIのJSONを食わせればそのまま表示できるってのがわかるかと思います。
ローカルで表示したい
「SwaggerUIをローカルに持ってきて、OpenAPIのJSONもローカルに持ってきて、そのままひらけばよくない?」とか思うかもしれませんが、ローカルリソースをそのまま開けるほど最近のブラウザは甘くありません。 「ローカルファイル間ならよくね?」って思ったりもするんですが、それやるとローカルファイルからリモートアクセスを遮断しないといけなくなり、世界が困る(クソデカ主語)。ブラウザの設定変えたりすればできますが、それも同様。
こういう時は開発者端末でならローカルにHTTPサーバーをさくっと立ててしまえば良いのですが、非開発者やローカルでHTTPサーバーを実行する機会の少ない人も多く、みんなにそんなことを求められないです。 見たい人全員の手元にOpenAPIブラウザ的なもの(SwaggerUIもその一種)があれば良いのですが、それも求められるものでもないと思います。
ということで(強引)、ローカルで見るにはHTMLファイルにする必要があるのです。
OpenAPIをHTMLにするツールはいくつかありまですが、今回は Redocly CLI を使います。redoc-cliの後継です。
使い方はドキュメントを見てもらえればいいんですが、 npx
が入っているなら @redocly/cli build-docs
にOpenAPI仕様の場所を教えてあげるだけです。
SpringBootアプリケーションが動作している状態であればこんな感じで redoc-static.html
が実行ディレクトリにできます。
% npx @redocly/cli build-docs http://localhost:8080/v3/api-docs Found undefined and using theme.openapi optionsPrerendering docs 🎉 bundled successfully in: redoc-static.html (50 KiB) [⏱ 7ms].
これで満足してもいいと思いますが、もう少し行きたい。
OpenAPI仕様を自動出力したい
「HTML出そ、えーとSpringBootアプリケーション起動して……」ってやるのはかったるいと感じる私の同類さんは、勝手にファイルが出力されると嬉しいでしょう。
springdoc-openapi
はそういう同類さんのためにプラグインを用意してくれています。私はGradle派なので(仕事の6割はMavenなんだけど)、ここでは springdoc-openapi-gradle-plugin を紹介します。MavenPluginもあるね、使ったことないけど。
使い方はプラグイン追加したらタスクが生えるので、それ実行するだけです。
plugins { id "org.springframework.boot" version "3.1.0" id "org.springdoc.openapi-gradle-plugin" version "1.6.0" }
こうして ./gradlew generateOpenApiDocs
を実行するだけ。ワオ簡単。
ドキュメントはSpringBoot2.7になってるけど、3.1でも問題なく動いてるから気にしてない。SpringBoot自体にどうこうするもんでもないしね。
ポート変えてたりProfileとかパラメタ与えたくなったら springdoc-openapi-gradle-pluginの設定方法 を見てください。それなりに複雑なアプリケーションでやったら引っかかったけど、設定で十分対応できたので多分大丈夫。ダメだったらコントリビュートチャンスですよ。
ちなみにドキュメントでは毎回 clean
叩いてるけど、これはコード変えようが出力済みだったら UP-TO-DATE
になっちゃうから。私は clean
すると他の消えていやんってなるので UP-TO-DATE
避けは以下を入れてます。お好みで。
tasks.withType(org.springdoc.openapi.gradle.plugin.OpenApiGeneratorTask).configureEach {
outputs.upToDateWhen { false }
}
upToDateWhen
が何かしらない?大丈夫、普通は知らないと思うよ。Gradleのドキュメントに載ってるよ!(親切)
もちろんメソッドのアノテーション書き換えとかならともかく、クラス名の変更とか削除になってくるとゴミは残るので、安定したドキュメントが欲しいなら clean
した方がいい。使い分け使い分け。面倒ならいつも clean
すればいいんじゃないかな。その場合は「毎回 clean
叩くこと」とかするのはダサいから dependsOn
した方がいいだろけど。
問題はSpringBootアプリケーションを実際に動かすので、起動失敗したり時間がかかるような構成だと使えないこと。 簡単に起動できないなら、ローカルで起動できないような構成を見直したほうがいいよ?悪いこと言わないから。って言っても難しいところもあるのは知ってるけど……。 これを機に正常化するとか考えてもいいんじゃないかな。
なお springdoc-openapi-gradle-plugin などを使用しないOpenAPI仕様の自動出力の別解として、SpringBootTestを使用した出力もできます。 こんな感じのテストを書くだけ。え、pluginよりこっちのが楽?私もそう思うよ……
@Test void generateOpenApiJSON() throws Exception { mockMvc.perform(get("/v3/api-docs")) .andDo(result -> Files.write(Paths.get("openapi.json"), result.getResponse().getContentAsByteArray()) ); }
ある程度までであればこれで十分かもしれませんが、いくつか気になる点とか注意とかあるので書いておきますね:
- これは一体なんのテストなんだ?ってなる。折り合いつけられれば大丈夫。
@SpringBootTest
でならいけるけど@WebMvcTest
とかだと一捻り必要。- SpringBootTestはあくまでテストなんで動くアプリケーションとの違いがないこともない。springdoc-openapi-gradle-pluginはちゃんとアプリケーション動かしてる。
ご利用は計画的に😉
さておき、さあこれで build/openapi.json
に出力されるようになった。
でもHTMLにするのめんどいよね。
HTML化
あとはRedocly CLIを叩ければいいのでなんでもいいんだ。 Redocly CLIは普通にローカルファイルを食べてくれるので、こう。
% npx @redocly/cli build-docs build/openapi.json Found undefined and using theme.openapi optionsPrerendering docs 🎉 bundled successfully in: redoc-static.html (50 KiB) [⏱ 8ms].
SpringBootアプリケーションが動作してる必要もないし、どうとでもなる気はするよね。で、前のステップとまとめて雑に考えるとこれくらいが選択肢になるか。
1がポータビリティ高くていい気はする。2だとRedocly CLIのバージョン管理ができてよさげ。3はGradleから出たくない人向け。
ここでは雑に3、さらに gradle-node-plugin を使ったりせず、手元で npx
が使えるのを期待する。
tasks.register("generateOpenApiHtml", Exec) { dependsOn 'generateOpenApiDocs' group = "openapi" workingDir "${DEFAULT_BUILD_DIR_NAME}" commandLine 'npx', '@redocly/cli', 'build-docs', 'openapi.json' }
これで冒頭のように ./gradlew generateOpenApiHtml
でHTML出るようになります。
上から2つ目の group
はなくても実行に問題ないけど、なんとなく ./gradlew tasks
に並んで表示させたかったから入れてる。
これでHTMLでAPIドキュメントが手に入るようになった。
よーし、これをCIで実行する……となると npx
が入ってなきゃだし、キャッシュしなきゃ毎回ダウンロードだるいし、うん、いろいろあるね。その辺はどうにかなるでしょ。どうにかしましょう。今回は書きません。
ここまできたらSwaggerUIはなくてもいいかもしれません。APIクライアントとしては便利かもしれませんが、RedocのHTMLを正とするならSwaggerUIは外して普段から自分たちもRedocを見る方が改善が進んだりします。どっぐふーでぃんぐどっぐふーでぃんぐ。 springdoc-openapiもswagger部分は切り離せるように作られてますしね。
おれたちのたたかいははじまったばかりだ(完)
まとめ
実際動かすための対応はすごく少ないんだけど、まとめません。自分で拾って。 それぞれのツールが何をするものか、切れ目はどこにあるかをわかっていないとどハマりする系の話だと思いますので。
これに近いことでやっているところもありますし、springdoc-openapi-gradle-pluginとかは使用せず動作しているアプリケーションに向けてRedocly CLIをCIで叩いてるものもあります。 ドキュメントのライフサイクルとか、管理単位とか、アプリケーションの数とか、組織体制とか、その辺諸々勘案して選択できるためには小さい道具をそれぞれ理解してうまく組み合わせるのが、ハマりどころも少ないし対応幅も広くなると思います。 たとえばAPIドキュメントとしての充足を求める局面ではSwaggerUIよりRedocでカスタマイズかけていく方が実用的でしょうし、ドキュメントを見ながら改善を繰り返している時はCIを通してようやく見れる状態はフィードバックが遅すぎるはず。手元で確認できる道具を備えているのに越したことはありません。
こういうのをUNIXという考え方では「ソフトウェアの梃子」って表現してる。
UNIXとか触ったことない、触る機会は今後もないだろうって人でも読んでおくといいと思うんだ。小さめで144ページと薄い本だし。 (ちなみに本書はシェルスクリプト推しなので選択肢3は、うん、まぁ。気にしない気にしない。)
ローカルでSonarQubeを触ってみる(MavenとJaCoCoと)
- SonarQube 9.9.1-community
- Maven 3.9.1
これはなに
SonarQubeよくわからん人向け。
「何できるもんなんだろ」とか「現場でSonarQube使ってるものの、設定とか影響範囲わからなくて怖い」とか、色々とあると思う。ローカルでいじくれるとイメージも湧きやすいしハードル下がるかなと思って書いた。
あとJaCoCo周りはあちこちでなんやかんやあるので。
準備
SonarQube
Dockerで。
docker run -p 9000:9000 --rm sonarqube:9.9.1-community
- http://localhost:9000 ひらく。
admin/admin
でログイン- パスワード設定画面になるのでパスワードを
hoge
に変更
通常はTokenを作成するが、ここではSonarQubeの設定も揮発するコンテナで動かしてるのでやらない。
他の形
https://www.sonarsource.com/products/sonarqube/downloads/
Docker使わないならダウンロードして解凍してでもできる。けど「とりあえず触る」にはちょっと面倒だよね。
手元に入れるの面倒ならオープンソースならSonarCloudが無料で使えるけど、このブログのタイトルから外れるので書かない。
アプリケーション(ない場合)
https://start.spring.io とかで spring-boot-starter-web
の pom.xml
とってきて準備。
▼pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.6</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>dev.irof.suburi</groupId> <artifactId>spring-maven-sonar</artifactId> <version>0.0.1-SNAPSHOT</version> <name>すぶり</name> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
SpringBootである必要は何一つない。
見ての通り(本エントリはMavenある程度わかる人を想定しています)、SonarQubeに関する記述は pom.xml
にはありません。
なのでもし手元に手頃なMavenプロジェクトあるならそのまま使えばいいです。
以降は ./mvnw
使ってるけど、パス通ってるMaven使うなら mvn
でもいいし、 mvn wrapper:wrapper
叩いてからでもいい。
実行
./mvnw clean package sonar:sonar -Dsonar.login=admin -Dsonar.password=hoge
さっきパスワードを hoge
に変えたから sonar.password
はそれにしてる。他のにしてたら変えてね。
トークン使ってみたり別ユーザー作ってみたりしていじってみましょう。
説明
sonar-maven-plugin は特別扱いされてるので、 pom.xml
に何も書かなくても sonar:sonar
で実行できちゃいます。
とはいえ設定書きたいとかバージョン制御したいとかあると思うんで、本番では書いた方がいいかもですね。
sonar.login
/ sonar.password
とかは前述の通り。SonarQubeのURLはデフォルト http://localhost:9000 なので何も書いてないだけです。
この辺りは設定したくなるはずなので、ドキュメント読んで settings.xml
に書くとか、実行シェルに書くとかしていきませう。
ちなみにプラグインのソースは https://github.com/SonarSource/sonar-scanner-maven にあります。
なお clean package
とかしなくても sonar:sonar
だけでスキャンはしてくれます。
でも普通はテストの通ったものをスキャンすると思うし、テスト結果もSonarQubeにのっけときたいと思うし。
確認
前述の pom.xml
を使ったなら http://localhost:9000/projects に「すぶり」ってプロジェクトができてるはず。
この名前は /project/name
になる。
あとは好きにどうぞ。
もう一歩: カバレッジ
たとえばカバレッジをSonarQubeに取り込みたいなら、 jacoco-maven-plugin
入れてやってください。
... <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.8</version> <executions> <execution> <id>default-prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <!-- verify --> <id>default-report</id> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
prepare-agent
がテストとか実行する前にJaCoCoのagentを設定するもの。 phase
は指定しなくてもいい感じの場所( initialize
)に入ります。
report
がSonarQubeも取り込める形のJaCoCoのレポート(XML)を出力するものです。これ実行してあげないとSonarQubeのカバレッジは永久に0%と表示されます。
<execution>
で <phase>
を書かない場合、 jacoco:report
は verify
で実行されます。
先に挙げたコマンドだと package
までしか実行してないので、JaCoCoのレポートは出力されません。
てことでこう。
./mvnw clean verify sonar:sonar -Dsonar.login=admin -Dsonar.password=hoge
<execution>
書かずに jacoco:report
を手で実行してあげてもいいですけどね。
./mvnw clean package jacoco:report sonar:sonar -Dsonar.login=admin -Dsonar.password=hoge
分けるならこうでも。
./mvnw clean package ./mvnw jacoco:report ./mvnw sonar:sonar -Dsonar.login=admin -Dsonar.password=hoge
test
でJaCoCoのファイル target/jacoco.exec
が出来て、それを元に target/site/jacoco/*
を作るのが jacoco:report
です。
うまく取り込めてると sonar:sonar
の時に以下のように Importing 1 report(s)
が出ます。
[INFO] Sensor JaCoCo XML Report Importer [jacoco] [INFO] 'sonar.coverage.jacoco.xmlReportPaths' is not defined. Using default locations: target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml,build/reports/jacoco/test/jacocoTestReport.xml [INFO] Importing 1 report(s). Turn your logs in debug mode in order to see the exhaustive list. [INFO] Sensor JaCoCo XML Report Importer [jacoco] (done) | time=7ms
ダメだったらこんな風に出るはず。
[INFO] Sensor JaCoCo XML Report Importer [jacoco] [INFO] 'sonar.coverage.jacoco.xmlReportPaths' is not defined. Using default locations: target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml,build/reports/jacoco/test/jacocoTestReport.xml [INFO] No report imported, no coverage information will be imported by JaCoCo XML Report Importer [INFO] Sensor JaCoCo XML Report Importer [jacoco] (done) | time=1ms
少し前のSonarQubeは jacoco.exec
を読んだのだけど、XML読むようにちょっと前に変わった。まぁ気持ちはわかる。
注意
- SonarQubeのフッタに警告表示されてるけど、組み込みデータベース使ってるんでコンテナ落としたらデータ消えます。あくまで実験用。
- SonarQubeのプロジェクトページ開いたら警告表示されてるけど、
sonar.password
はそのうち使えなくなるらしいです。なのでここでは9.9.1-community
とバージョン固定してます。「これから使っていくためにとりあえず触る」目的ならlts
とかlatest
とか使う方がいいと思うよ。
併せて読む必要がない
JaCoCoで検索してもこの記事しかなかった。あんま書いてないのね私。SonarQubeは長年の付き合いなのに一記事も書いてなかった……。