ブラウザに保存されないサイトのパスワードをCSVファイルからインポートする

パスワードを自動保存しない設定はしていないが、なぜかパスワードを保存するか聞かれないサイトがあった。

FirefoxGoogle ChromeMicrosoft Edgeなど、ブラウザを変えて試してみたが、いずれも同様。

パスワード入力欄が type="password" になっていないのかと思ったらそんなこともなく、原因は特定できなかった。

不便なので、どうにかパスワードを保存する方法はないかと調べたところ、各ブラウザでパスワードをCSVファイルからインポートする方法があったのでメモ。

Google Chrome

Googleアカウントにパスワードをインポートすることで、間接的にGoogle Chromeにパスワードを保存できる。

support.google.com

1行目にヘッダとして url,username,password を記載し、2行目以降にパスワードを自動入力したいページのURL、ユーザー名、パスワードを入力したCSVファイルでインポートできる。

url,username,password
https://example.com/login.html,example,123456

手順は以下。

  1. Google アカウントにログインしてパスワードマネージャーを開く
  2. 右上の歯車アイコンをクリックして設定画面を開く
  3. 「インポート」ボタンをクリックしてファイル選択モーダルを開く
  4. 「ファイルを選択」からCSVファイルを選択し、右下の「インポート」をクリックすると完了

Firefox

Firefox 92.0時点ではデフォルトで無効化されているが、FriefoxでもCSVファイルインポートによるパスワード保存が可能。

support.mozilla.org

まず、以下の手順で有効する必要がある。

  1. アドレスバーに about:config を入力し、Firefoxの設定エディタを開く
  2. signon.management.page.fileImport.enabled を検索し、画面右の矢印アイコンをクリックして true に変更

実際のインポートは以下の手順。

  1. メニューボタンをクリック
  2. 「パスワード」をクリックし、Firefox Lockwiseを開く
  3. 右上の三点リーダーをクリックし、「ファイルからインポート」をクリック
    • signon.management.page.fileImport.enabledtrue にしていないと、「ファイルからインポート」は表示されない
  4. CSVファイルを選択して完了

CSVファイルの形式について、ヘルプページには

インポートする .csv ファイルは、以下のように構成されている必要があります:
"url","username","password","httpRealm","formActionOrigin","guid","timeCreated","timeLastUsed","timePasswordChanged"

との記載があるが、実際はGoogleアカウントの場合と同様、 url,username,password だけでインポートで可能。

Microsoft Edge

設定画面からCSVファイルをアップロードして、パスワードをインポートできる。

公式のヘルプページのようなものは見つからず、どんな形式のCSVファイルをアップロードすればいいかもわからなかったが、試してみたところGoogleアカウントと同じ、 url,username,password 形式のCSVをインポートできた。

インポートは以下の手順。

  1. 設定を開く
  2. プロファイルのパスワードをクリック
  3. 「保存されたパスワード」右の3点リーダをクリックし、「パスワードのインポート」をクリック 
  4. インポート元として「パスワード CSV ファイル」が選択された「ブラウザ データのインポート」が開くので、「ファイルの選択」からCSVファイルを選択

振り返り

すべて同じ形式のCSVファイルでインポートできた。

自動保存できないサイトなので、パスワード自動入力が効くか不安だったが、確認できた限りではどのブラウザでも自動入力できている。

根本原因として、どうしてパスワード保存を聞かれないのかがわかればなぁ。

Gradleのユニットテストを早くするためにやったこと

プロジェクト管理ツールにGradleを使ったSpring Bootプロジェクトのユニットテストを、GitLab CIのパイプラインで実行しているが、完了するのに45分程度かかっていた。

その後のデプロイなど含めると、トータル1時間程度かかってしまう。

短縮できないか調べてみたのでメモ。

問題

Gradleを使ったSpring Bootプロジェクトのユニットテストが遅く、GitLab CIパイプラインでの実行に平均45分程度かかっている。

ユニットテストにはSpock 2 + JUnit 5を使用し、DBは application.properties にてH2DBをインメモリで実行するよう設定している。

調査

当初はGradleのメモリを増やせばよいかと思い、 ./gradlew test 前に export GRADLE_OPTS="-Dorg.gradle.daemon=false -XX:MaxRAMPercentage=75" を設定してみたが、まったく改善されなかった。

Gradleとは別に、ユニットテスト用のJVMが起動しているのかと思い、Test DSLを見てみると、以下の記載を見つけた。

Test are always run in (one or more) separate JVMs

テストは1つ以上のJVMで実行されるとある。

Gradleのtestタスクのプロパティ

テストの並列処理に関連しそうなプロパティは以下。

プロパティ名 説明
maxParallelForks int 並列実行するテストプロセス数。
指定した数または max-workers の小さい値まで、テストプロセスを作成して並列実行する。
デフォルト 1
forkEvery long 1つのテストプロセスで実行する、テストクラスの最大数。
1 以上の値を指定すると、指定した数のテストクラスを実行した後、テストプロセスの再起動が行われる。
0 の場合はテストプロセスの再起動は行われない。
デフォルト 0L
maxHeapSize String プロセスの最大ヒープサイズ。 512m1g など、単位付きで指定。
-Xmx に展開される模様。
デフォルト null
minHeapSize String プロセスの最小ヒープサイズ。指定方法は maxHeapSize と同様。
-Xms に展開される模様。
デフォルト null

jvmArgs もプロパティに存在するが、そちらに最大/最小ヒープサイズは含めず、 maxHeapSize および minHeapSize を利用する模様。

テストプロセスの生成について

「テストは1つ以上のJVMで実行される」とあるが、このJVMmaxParallelForks で作成されるテストプロセスとイコールなのかが読み取れなかった。

試しにPC上で maxParallelForks をCPU論理プロセッサ数、 maxHeapSize を物理メモリ容量の半分に設定したところ、テスト開始時にスレッド生成に失敗したことから、テストプロセスはそれぞれ個別のJVMとして起動される模様。

また、インメモリで起動しているH2DBは、 application.propertiesspring.datasource.url が同じ値でも、各テストプロセス内で固有のデータを保持することが確認できた。JVMごとにメモリ領域が分かれていると思われる。

max-workers について

maxParallelForks の上限になっている max-workers は、Gradleの最大ワーカー数。

ビルド環境によると、デフォルトではCPUプロセッサ数。おそらく java.lang.Runtime#availableProcessors() が使われている。

プロパティ org.gradle.workers.max=... を指定したり、Gradleコマンド実行時に --max-workers=... で設定可能だが、明示的に値を設定するより、未設定にしてデフォルト値を使うのがよさそう。

forkEverymaxHeapSize について

マルチコアプロセッサの場合、 maxParallelForks に大きな値を設定し、 forkEvery0LmaxHeapSize を未設定とした場合、実行時にフォークされた各JVMがメモリを食いつぶし、OutOfMemoryError が発生した。

forkEvery を適当な値に設定し、定期的にテストプロセス(JVM)を再起動させるか、 maxHeapSize を指定してヒープサイズに上限を設ける必要がある。

現状確認

現時点の値を確認すると、以下のようになっていた。

  • maxParallelForks: 4
  • forkEvery: 1
  • maxHeapSize, minHeapSize: 未設定

GitLab CI RunnerではAWS EC2でvCPU 16のインスタンスを使用しているため、 maxParallelForks が少なく設定されている。また、 forkEvery が1になっており、1テストクラスごとにテストプロセスの再起動が行われる状態だった。

対応

公式のテスト実行に関する記述をもとに、パラメータを調整する。

1点、公式では forkEvery の使用が記載されているが、適切な値設定ができなかったため、 maxHeapSize を指定している。

// CIパイプラインからの実行時に環境変数を設定し、ローカル実行かパイプライン実行かを判定する
// CI/CDにはGitLab CIを利用しており、その場合CIがtrueに設定されるので、それを判定に利用
// https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
final boolean onPipeline = System.env.CI != null

test {
  // 中略

  if (onPipeline) {
    maxParallelForks = Runtime.runtime.availableProcessors()
    forkEvery = 0L
    maxHeapSize = "1g"
  }
}

maxHeapSize には、当初 free した際の空きメモリから、Gradleが利用するメモリ容量を引いた値を availableProcessors() で割った値を渡していたが、それだとメモリ不足でスレッド生成に失敗した。

H2DBが別途メモリを使用しているのか、スレッド生成にOS側でもメモリを使うのかなど推測したが、詳細は不明。

計算値から512MB~768MB程度引いた値で設定すると実行できたため、取り急ぎ切りのいい1GBで設定した。

結果

テストの失敗が多発したが、原因のほとんどは、データ件数不整合、主キー重複、外部キー制約違反。これまでは1テストクラスごとにJVMが再起動し、それに合わせてH2DBのデータもクリアされていたが、再起動しないよう変更し、データ不整合が生じていた。

テストデータ作成は抽象クラスにまとめていたため、そのクラスの cleanup メソッドに、全テーブルのデータを削除するよう処理を追加することで解消。

データ削除処理のコスト増があるものの、テスト実行時間は45分から10分と大幅に減少した。

振り返り

並列実行時にインメモリのH2DBが各テストで共有されると面倒だなと思っていたので、各テストプロセスで独立して動いてくれたのはありがたかった。

また、現状Gradle Wrapperのダウンロードや、依存Jarのダウンロードが都度実行されており、約4分程度かかっているため、このあたりをGitLab CIのキャッシュ機能で改善できそう。

SonarQube CE v7.7以降の解析結果をViolationsツールでGitHubのPRやGitLabのMRに連携する

前回の続き。

前回の記事ではSpotBugsやPMD/CPDによる解析結果をマージリクエスト(GitHubなどのプルリクエストに相当、以下MR)連携したが、業務ではSonarQube CEで静的解析をしている。

SonarQube CEの場合、v7.6まではプレビューモードで解析することで、各種連携を行うことができたが、v7.7で該当機能が廃止された。

無償のCEでは連携はできなくなったと思っており、各種プラグインでもバージョンをv7.6まで下げる必要があると思っていたが、SonarQubeの解析結果をjqで加工して、Violationsツールを用いて連携することができたのでメモ。

環境

SonarQube CE 8.3, jq 1.6, violation-comments-to-gitlab-gradle-plugin 1.14.3。

問題

SonarQubeのプルリクエスト連携機能は存在するが、有償のDeveloper Edition以上で利用できる機能のため、無償のCommunity Editionでは利用できない。

SonarQube CEとGitHubのPRやGitLabのMR連携の方法を調べると、sonar-githubSonar GitLab Pluginが出てくるが、どちらもSonarQubeのバージョンがv7.7未満の必要がある。これは、解析結果の取得にプレビューモード(結果をパブリッシュせず、分析のみ実行する)を利用していたが、そのオプションがv7.7で廃止されたため。

現在利用しているSonarQubeは古めのv8.3だが、v7.6まで下げると対応するJavaバージョンが8まで下がってしまう。また、有償版にする予定はなし。

対応

SonarQubeのAPIで問題を取得し、v7.6の解析結果と同じフォーマットに整形してやれば、連携できるのではないかと考えた。

ちょうど前後してViolationツールを発見したが、対応する静的解析ツールにSonarQubeも含まれている。

READMEのSonarQubeのNotesを見ると、以下のような記載がある。

With mvn sonar:sonar -Dsonar.analysis.mode=preview -Dsonar.report.export.path=sonar-report.json. Removed in 7.7, see SONAR-11670 but can be retrieved with: curl --silent 'http://sonar-server/api/issues/search?componentKeys=unique-key&resolved=false' | jq -f sonar-report-builder.jq > sonar-report.json.

これがまさにやろうとしていた、「SonarQubeのAPIで問題を取得し、v7.6の解析結果と同じフォーマットに整形」だった。

使用例

SonarQubeの解析結果を整形し、GitLab CIからViolation CommentsのGitLab Gradle PluginプラグインでGitLabのMRにコメントする手順を記載。

Gradle以外のプロジェクト管理ツールや、GitHub連携も、利用するプラグインを変更すれば同様の方法で可能と思われる。

SonarQubeの解析結果の整形

記載されていたコマンドは、SonarQubeのAPIでissue(解析結果)を取得し、それを「sonar-report-builder.jq」というファイル内のフォーマットで整形して、「sonar-report.json」として出力するもの。

この「sonar-report-builder.jq」だが、SonarQubeコミュニティのこのスレッドに添付されている「sonar-report-builder.jq.txt」だと思われる。

以下内容。

# Convert sonarqube's issues search api json to sonar-report.json

{
  "version": "7.8",
  "users": [],
  "issues": [
    .issues[] | {
      "creationDate": .creationDate,
      "isNew": true,
      "status": "OPEN",
      "rule": .rule,
      "severity": .severity,
      "key": .key,
      "component": .component,
      "line": .textRange | .startLine,
      "startLine": .textRange | .startLine,
      "startOffset": .textRange | .startOffset,
      "endLine": .textRange | .endLine,
      "endOffset": .textRange | .endOffset,
      "message": .message
    }
  ],
  "components": [
    .components[] |
      if .qualifier == "TRK" then
        { "key": .key }
      else
        {
          "status": "CHANGED",
          "moduleKey": .key | split(":src")[0],
          "path": .path,
          "key": .key
        }
      end
  ]
}

このファイルを「config/sonar/sonar-report-builder.jq.txt」に保存し、「build/sonar-report.json」としてフォーマット変更後のファイルを出力するコマンドは以下。

curl --silent "https://${SONAR_HOSTNAME}/api/issues/search?componentKeys=${SONAR_PROJECT_KEY}&resolved=false" |
  jq -f config/sonar/sonar-report-builder.jq.txt > build/sonar-report.json

Violation Commentsプラグインの設定

前回の設定で記述した、 build.gradleの violationCommentsToGitLab タスクを変更する。

SonarQubeと解析内容が重複するSpotBugとPMD/CPDを除外し、SonarQubeの設定を追加。Checkstyleは、SonarQubeにプラグインを追加して、そちらに解析を任せる手もあるが、今回は別々に解析することとした。

task violationCommentsToGitLab(type: ViolationCommentsToGitLabTask) {
  // 前略

  violations = [
      ['SONAR', buildDir.absolutePath, '.*/sonar-report\\.json\$', 'SonarQube'],
      ['CHECKSTYLE', file("${buildDir}/reports/checkstyle").absolutePath, '.*/main\\.xml\$', 'Checkstyle'],
  ]

  // 後略
}

.gitlab-ci.yml設定

前回の設定に、 sonarqube タスクおよびSonarQubeのAPI実行を追加すればいい。

  script:
    - ./gradlew check sonarqube -x test
    - curl --silent "https://${SONAR_HOSTNAME}/api/issues/search?componentKeys=unique-key&resolved=false" | jq -f config/sonar/sonar-report-builder.jq.txt > build/sonar-report.json
    - |-
      ./gradlew violationCommentsToGitLab \
        -PgitLabUrl=${CI_SERVER_URL} \
        -PprojectId=${CI_PROJECT_ID} \
        -PmergeRequestIid=${CI_MERGE_REQUEST_IID} \
        -PgitLabApiToken=${VIOLATION_API_TOKEN}

これでSonarQubeの解析結果を、GitLabのMRにコメントすることができた。

注意点

今回利用したSonarQubeのissue検索APIでは、SonarQube上のすべてのissueが結果に含まれる。

MRのソースブランチとターゲットブランチ間の差分で、指摘としてコメントするかを判定しているため、ターゲットブランチにはmainなどの基底ブランチをマージしていないが、ソースブランチにはマージしている場合など、他ブランチで発生した差分がソースブランチに含まれる場合、それらにもコメントされる。

振り返り

何とかSonarQubeのバージョンを変えずにMR連携することができた。

SonarQubeのissue検索APIが廃止されることはないと思うので、しばらくはこの方法でCommunity EditionでもPR/MR連携ができそう。

APIも有償エディション限定になる可能性はあるかもしれないが。

静的解析結果をGitHubのPRやGitLabのMRにコメントするViolationsツールの紹介

静的解析ツールを導入しているが、事前に解析結果をチェックしてといってもなかなか潰しきれない。

GitサーバーとしてGitLabを使っているので、マージリクエスト(GitHubなどのプルリクエストに相当、以下MR)でコードレビューを行っているが、静的解析結果をMRのコメントとして連携できれば、コードレビューを依頼する前にある程度取り切れるかと思い、調査したところ、うまいことやってくれるツールがあったのでメモ。

環境

Gradle v6.6.1、GitLab CE v13.6.1。

ツールの概要

「Violations」および「Violation Comments」というツールがあった。

github.com

上記リンクの「Violations Lib」というJavaライブラリがあり、そこからGitHubやBitbucketのプルリクエスト、およびGitLabのマージリクエストへのコメント連携用に「Violation Comments to ~ Lib」がサポートされている。

さらに、「Violation Comments」を実行するためのコマンドラインツールの「Violation Comments to ~ Command Line」や、Maven/Gradle/Jenkinsプラグインの「Violation Comments to ~ Plugin」が用意されている。

利用可能な静的解析ツールの詳細は、上記リンク先参照。

JavaやGroovy、Kotlin、ScalaといったJVM言語だけでなく、C++、Go、JavaScript、TypeScript、PHPPythonRuby、またAnsibleLintやCloudFormation Linterなどにも対応している。

使用例

今回は、Javaプロジェクトに対し、GitLab CIからGradleプラグインで、SpotBugs、PMD/CPD、Checkstyleの解析結果を連携してみる。

build.gradle

プラグインの追加

プラグイン追加方法は以下の通り。

記述は以下のようになる。PMDとCheckstyleは、コアプラグインなのでバージョン指定不要っぽい。

// plugins DSLの場合
plugins {
  id 'com.github.spotbugs' version '4.7.2'
  id 'pmd'
  id 'de.aaschmid.cpd' version '3.2'
  id 'checkstyle'
  id 'se.bjurr.violations.violation-comments-to-gitlab-gradle-plugin' version '1.41.3'
}

// legacy plugin applicationの場合
buildscript {
  repositories {
    maven {
      url 'https://plugins.gradle.org/m2/'
    }
  }

  dependencies {
    classpath 'gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.7.2'
    classpath 'se.bjurr.violations:violation-comments-to-gitlab-gradle-plugin:1.41.3'
    classpath 'de.aaschmid:gradle-cpd-plugin:3.2'
  }
}

apply plugin: 'com.github.spotbugs'
apply plugin: 'pmd'
apply plugin: 'de.aaschmid.cpd'
apply plugin: 'checkstyle'
apply plugin: 'se.bjurr.violations.violation-comments-to-gitlab-gradle-plugin'
静的解析ツールプラグインの設定

プラグインの設定方法を元に、設定していく。

  • SpotBugs
    • 2021/8/15時点で、READMEのConfigure SpotBugs Pluginが古いっぽい。値の型が org.gradle.api.provider.Property になっているので、代入ではなく set メソッドを使わないと警告が出る模様。
  • PMD
  • CPD
  • Checkstyle

記述は以下のようになる。

spotbugs {
  toolVersion.set('4.3.0')
  ignoreFailures.set(true)
  showStackTraces.set(false)
}

pmd {
  toolVersion = '6.36.0'
  ignoreFailures = true
  consoleOutput = false

  // ルールセットはとりあえず全部入り
  ruleSets = [
      'category/java/bestpractices.xml',
      'category/java/codestyle.xml',
      'category/java/design.xml',
      'category/java/documentation.xml',
      'category/java/errorprone.xml',
      'category/java/multithreading.xml',
      'category/java/performance.xml',
      'category/java/security.xml',
  ]
}

cpd {
  // 解析結果ファイルの文字コード。未設定だとOSのデフォルトエンコーディングになるため、WindowsでCP932になるのを抑制するために指定
  encoding = 'UTF-8'
  language = 'java'
  ignoreFailures = true
  ignoreLiterals = true // リテラル値の違いを無視
  ignoreIdentifiers = true // 変数名の違いを無視
  ignoreAnnotations = true // アノテーションを無視
}

checkstyle {
  toolVersion = '8.45.1'
  configFile = file('config/checkstyle/google_checks.xml')
  ignoreFailures = true
  showViolations = false
}

// 解析時間の短縮のため、ユニットテストに対しては解析対象から除外する
spotbugsTest.enabled = false
pmdTest.enabled = false
cpdCheck { source = sourceSets.main.allJava }
checkstyleTest.enabled = false
Violation Commentsプラグインの設定

GradleプラグインページからはGitLab Gradle Pluginへリンクしているが、設定できるパラメーターの詳細はGitLab Command Lineを見たほうがいい。

記述は以下のようになる。type: import se.bjurr.violations.comments.gitlab.plugin.gradle.ViolationCommentsToGitLabTask のタスクを宣言し、設定していく。

import se.bjurr.violations.comments.gitlab.plugin.gradle.ViolationCommentsToGitLabTask

task violationCommentsToGitLab(type: ViolationCommentsToGitLabTask) {
  // GitLabのURL、プロジェクトID、MRのインターナルID、コメント用ユーザーのAPIトークンはCIから引数で渡す
  gitLabUrl = project.findProperty("gitLabUrl")
  projectId = project.findProperty("projectId")
  mergeRequestIid = project.findProperty("mergeRequestIid")
  apiToken = project.findProperty("gitLabApiToken")

  apiTokenPrivate = true
  createCommentWithAllSingleFileComments = false // 問題をまとめたコメントをしない
  createSingleFileComments = true // ディスカッションとしてコメントする
  minSeverity = "INFO" // コメントする最小の重大度

  // 解析ツールの設定を配列で指定する
  // 1. READMEで指定された解析ツールの種類
  // 2. 解析結果の検索ディレクトリ
  // 3. 解析結果ファイル検索用正規表現
  // 4. コメントに表示する解析ツール名(レポーター)
  // ディレクトリは絶対パスで指定しないと、Windows環境ではファイルの検索ができなかった。該当部分のソースがおかしかった気がする。
  // また、ディレクトリ指定とファイル検索の正規表現が、組み合わせによって検索できないことが多々あり、この記述に落ち着いた。
  violations = [
      ['FINDBUGS', file("${buildDir}/reports/spotbugs").absolutePath, '.*/main\\.xml\$', 'Spotbugs'],
      ['PMD', file("${buildDir}/reports/pmd").absolutePath, '.*/main\\.xml\$', 'PMD'],
      ['CPD', file("${buildDir}/reports/cpd").absolutePath, '.*/cpdCheck\\.xml\$', 'CPD'],
      ['CHECKSTYLE', file("${buildDir}/reports/checkstyle").absolutePath, '.*/main\\.xml\$', 'Checkstyle'],
  ]
}
コメント形式のカスタマイズ

前述の設定だと、こちらのデフォルト形式でコメントされる。

PMDで System.out.println があった場合に、実際にコメントされるテキストは以下。

デフォルトのコメント

SourceにGradleプロジェクトのgroupやnameが設定されるが、ファイルパスが分かれば十分だし、縦に長いので調整したい。

コメントテンプレートは、 commentTemplate として mustacheテンプレートで記述可能。mustacheの実装としてはMustache.javaが使われる。

前述の task violationCommentsToGitLab 配下に、以下を追加。

task violationCommentsToGitLab(type: ViolationCommentsToGitLabTask) {
  // 中略

  // コメント形式のカスタマイズ、
  commentTemplate = """\
  **重大度**: {{violation.severity}}, **解析ツール**: {{violation.reporter}}{{#violation.rule}}, **ルール**: {{violation.rule}}{{/violation.rule}}

  **対象**: {{changedFile.filename}} \\# {{violation.startLine}}行目{{#violation.endLine}}~{{violation.endLine}}行目{{/violation.endLine}}

  **内容**: {{violation.message}}
  """.stripIndent()
}

すると、コメントされるテキストは以下のようになる。

テンプレート変更後のコメント

GitLab設定

コメント用のユーザーを作成し、ユーザー設定 > アクセストークンから、apiスコープのパーソナルアクセストークンを作成しておく。

.gitlab-ci.yml設定

GitLab CI/CD Variablesとして、先ほど作成したユーザーのアクセストークンを保存しておく。名前は VIOLATION_API_TOKEN とした。

マージリクエストに限定されたジョブに対し、以下のscriptを記述すると、MRの変更箇所に対して静的解析結果がコメントされる。

  script:
    - |-
      ./gradlew check violationCommentsToGitLab -x test \
        -PgitLabUrl=${CI_SERVER_URL} \
        -PprojectId=${CI_PROJECT_ID} \
        -PmergeRequestIid=${CI_MERGE_REQUEST_IID} \
        -PgitLabApiToken=${VIOLATION_API_TOKEN}

振り返り

最悪、自前でGitLab APIを叩いて実現しようと思っていたので、ものすごく助かった。

めちゃくちゃ便利だと思ったんだけど、2021/8/15時点で日本語の紹介を見つけられなかった。マイナーなのか、有償の機能とか使ってやっているのかな?

Java11にしてIntellijからGradleユニットテストを実行するとcompileTestGroovyでClassNotFoundExceptionが発生する

Java8からJava11にアップデートをしたところ、Intellijからテスト実行ができなくなった。

Git Bashから ./gradlew test したり、コマンドプロンプトから gradlew.bat test すると問題なく実行できるので、Intellij起因の問題だろうと思って調べたが、かなりしょっぱい原因だったのでメモ。

環境

Windows 10 Pro 64bit 21H1, Intellij IDEA Ultimate 2020.3.4, Java Openjdk 11.0.12.7, Gradle 6.6.1

問題

JDKのバージョンを8から11に変更し、Intellij IDEAからユニットテストを実行すると、以下の例外で compileTestGroovy タスクが失敗する。

[ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] FAILURE: Build failed with an exception.
[ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] 
[ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] * What went wrong:
[ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] Execution failed for task ':compileTestGroovy'.
[ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] > Failed to run Gradle Worker Daemon
[ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]    > Process 'Gradle Worker Daemon 1' finished with non-zero exit value 1

--debug オプションを付けて実行してみると、実際のエラーが発生している個所は以下の模様。

# 日本語
[ERROR] [system.err] エラー: メイン・クラスworker.org.gradle.process.internal.worker.GradleWorkerMainを検出およびロードできませんでした
[ERROR] [system.err] 原因: java.lang.ClassNotFoundException: worker.org.gradle.process.internal.worker.GradleWorkerMain

# 英語
[ERROR] [system.err] Error: Could not find or load main class worker.org.gradle.process.internal.worker.GradleWorkerMain
[ERROR] [system.err] Caused by: java.lang.ClassNotFoundException: worker.org.gradle.process.internal.worker.GradleWorkerMain

このエラーが発生するのはIntellijからの実行の場合のみで、直接 ./gradlew test などのコマンドを実行した場合は発生しない。

調査

gradle Intellij ClassNotFoundException GradleWorkerMain あたりで検索すると色々出てくる。

intellij idea - Gradle build failing after update - Stack Overflowを見て、./gradlew --stop.gradle の削除、また --refresh-dependencies オプション付きでの実行など行ったが効果なし。

続いて、Intellijのissueトラッカーを見てみると、issue214762およびissue193219にて、IntellijbootRun 時に引数を追加していたという情報を得た。

状況としては似ているので、Intellijから実行した場合と、直接コマンドで実行した場合でどのような引数が渡されているかを見比べてみた。

コマンド引数の違い

--debug オプションを付けて実行すると、Gradle Worker 起動時のコマンドが Starting process 'Gradle Worker Daemon 1'. Working directory: %GRADLE_USER_HOME%\workers Command: ... として出力される。

Java8にてIntellijから実行した際のコマンドは以下。長いので折り返しているが、実際は1行で出力される。

%PATH_TO_JAVA8_BIN%\java.exe ^
  -Djava.security.manager=worker.org.gradle.process.internal.worker.child.BootstrapSecurityManager ^
  -Xmx512m ^
  -Dfile.encoding=UTF-8 ^
  -Duser.country=JP ^
  -Duser.language=ja ^
  -Duser.variant ^
  -cp %GRADLE_USER_HOME%\caches\6.6.1\workerMain\gradle-worker.jar ^
  worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Worker Daemon 1'

続いてJava11にてIntellijから実行した際のコマンド。

%PATH_TO_JAVA11_BIN%\java.exe ^
  --add-opens java.base/java.lang=ALL-UNNAMED ^
  --add-opens java.base/java.lang.invoke=ALL-UNNAMED ^
  --add-opens java.prefs/java.util.prefs=ALL-UNNAMED ^
  @C:\tools\idea_temp\gradle-worker-classpath%20桁の数値%txt ^
  -Xmx512m ^
  -Dfile.encoding=UTF-8 ^
  -Duser.country=JP ^
  -Duser.language=ja ^
  -Duser.variant ^
  worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Worker Daemon 1'

最後に、コマンドを直接実行した場合。

%PATH_TO_JAVA11_BIN%\java.exe ^
  --add-opens java.base/java.lang=ALL-UNNAMED ^
  --add-opens java.base/java.lang.invoke=ALL-UNNAMED ^
  --add-opens java.prefs/java.util.prefs=ALL-UNNAMED ^
  @%USERPROFILE%\AppData\Local\Temp\gradle-worker-classpath%20桁の数値%txt ^
  -Xmx512m ^
  -Dfile.encoding=windows-31j ^
  -Duser.country=JP ^
  -Duser.language=ja ^
  -Duser.variant ^
  worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Worker Daemon 1'

Java11にした場合、 java.security.manager が消え、モジュール関連のオプションが追加され、クラスパス指定が -cp から @argument files に変更されている。

@argument files 記法は、Java9から追加された。

従来のWindowsには、コマンドプロンプトの引数の文字列長に制限があるため、クラスパスが極端に長くなる場合、クラスパスだけを指定したマニフェストJARを作成し、それを読み込んでやる必要があった。Java9以降であれば、Javaコマンドのオプションをファイルに記述し、 @ファイルパス を指定することで読み込ませることができるようになっている。

実際のファイルの内容は、Intellijから実行した場合も、コマンドで実行した場合も変わらず、以下のような形式。先頭に gradle-worker.jar 、その後ろに依存JARのパスが含まれる。

-cp
%GRADLE_USER_HOME%\\caches\\6.6.1\\workerMain\\gradle-worker.jar;%GRADLE_USER_HOME%\\wrapper\\dists\\gradle-6.6.1-bin\\%25桁の英数字%\\gradle-6.6.1\\lib\\gradle-core-6.6.1.jar;...

Java11での実行の場合、Intellijからの実行では -Dfile.encoding=UTF-8 に対し、コマンドからの実行では -Dfile.encoding=windows-31j が指定されている。

@argument files で指定されたファイルの文字コードを見てみると、Intellijからの実行ではUTF-8、コマンドからの実行ではShift_JIS(CP932)になっていた。現時点では @argument files文字コードを指定する方法はなさそうなので、OSのデフォルトエンコーディングでファイルの内容を記述する必要があると思われる。だが、Intellijから実行した場合、Javaファイルなどのエンコーディングを指定する -Dfile.encoding と同じく、UTF-8で出力されてしまっていた。

さらに、実装環境は、Active Directoryにてユーザー名に漢字が使われているため、 GRADLE_USER_HOME のデフォルトである %USERPROFILE%\.gradle に漢字が含まれていた。漢字を含んだパスがUTF-8で書き出され、Shift_JISとして読み込まれたため、適切に解釈できていなかった模様。

試しに、Intellijから実行した場合のファイルをShift_JISに変換し、コマンドを手動で実行したところ、Gradle Workerが立ち上がった。

対応

GRADLE_USER_HOME に非ASCII文字が含まれなければUTF-8でもShift_JISでも読み込むことができるため、 GRADLE_USER_HOME を適当な半角英字のみのパスに設定したところ、解消した。

振り返り

原因が分かれば大したことはないが、IDE起因かGradle起因かJava起因か、また原因の特定に4時間くらいかかった。似たような問題が起こっている人もちらほらいるようだが、同じ原因の人もいるのでは。パスにマルチバイト文字は入れないのが無難だろう。

以前は bootRun するときにマニフェストJARを作るプラグインを入れたりしていたが、 @argument files のおかげでタスクの変更でできるようになった模様。

Java9以上でファイル生成をよしなにしてくれるプラグインもあった。後々使いたくなるかもしれないので、URLを残しておく。

github.com

Windowsサンドボックスのメモリを増やしたり、ホストからファイル共有する方法

Windows 10 ProやEnterpriseで利用できるWindowsサンドボックス、ちょっとしたツールのお試しに便利だが、いくつかのツールを組み合わせて使いたいときに、デフォルトのメモリ4GBでは足りないケースがあった。

メモリを増やしたりできないか調べたところ、簡単にできた。また、インストーラーなどのファイルをドラッグ&ドロップサンドボックスにコピーしていたが、ホストからファイルを共有したりもできたのでメモ。

Windowsサンドボックスの設定ファイル

Windows サンドボックス構成 - Windows security | Microsoft Docs に設定方法が記載されている。

XML形式で設定ファイルを記述し、拡張子を .wsb にすると、設定ファイルのダブルクリックでWindows サンドボックスが起動する。

コマンドプロンプトなどから、ファイル名を指定することで、CUIからでも実行可能。

設定ファイルから起動しない限り、Windows サンドボックスに設定は反映されない。

設定概要

ファイル全体を <Configuration></Configuration> タグで囲み、その中に設定タグを追加していく。

今回試したものだけ記載。

仮想GPUの有効化

デフォルトでは無効化されているが、 <vGPU>Enable</vGPU> で、仮想GPUが有効となる。

ネットワークの無効化

デフォルトでは有効化されているが、 <Networking>Disable</Networking> でネットワークを無効化できる。

サンドボックスへのフォルダ共有

MappedFolders タグの中に、 MappedFolder タグを記述することで、フォルダ単位でのファイル共有が可能。

MappedFolder タグ内に、 HostFolder タグでホストのフォルダパス、 SandboxFolderサンドボックス内のフォルダパスを指定。

デフォルトでは書き込み可能だが、 <ReadOnly>true</ReadOnly> を指定することで、読み取り専用にできる。

初期実行コマンドの指定

サンドボックス起動時に、実行するコマンドを指定できる。

LogonCommand タグで全体を囲み、 Command タグ内に実際のコマンドを記述。

メモリ容量指定

<MemoryInMB>メガバイト単位のメモリ容量</MemoryInMB> で、メモリ指定が可能。8GB指定の場合、 8192 となる。

設定ファイル例

仮想GPUを有効化し、メモリは8GB。Dドライブのフォルダをサンドボックス実行ユーザーのダウンロードフォルダに共有し、ダウンロードフォルダを起動時コマンドで開いている。

なお、サンドボックス実行ユーザーは WDAGUtilityAccount となる。

<Configuration>
  <MemoryInMB>8192</MemoryInMB>
  <vGPU>Enable</vGPU>
  <MappedFolders>
    <MappedFolder>
      <HostFolder>D:\installers</HostFolder>
      <SandboxFolder>C:\Users\WDAGUtilityAccount\Downloads\shared</SandboxFolder>
      <ReadOnly>true</ReadOnly>
    </MappedFolder>
  </MappedFolders>
  <LogonCommand>
    <Command>explorer.exe C:\users\WDAGUtilityAccount\Downloads</Command>
  </LogonCommand>
</Configuration>

振り返り

これくらいの設定が、テキストベースの設定ファイルでできるのはありがたい。

Switch版ビーチバレーガールしずく3の攻略メモ

唐突なゲーム記事。

最近Switchで配信された G-MODEアーカイブス36 ビーチバレーガールしずく3 世界大会編

ガラケー版が出た当時は、シナリオ分岐条件など、攻略方法などがまとまったサイトがあったような気がするが、今検索してみてもパッと出てこないので、当時の攻略メモをさらしてみる。

キャラクターについて

キャラクターのステータスについては、キーラが前作のラスボスだったからか強い。必殺スパイクをレシーブした時などの気絶からの回復速度が速く、パワースパイクをブロックしても、気絶回復が間に合う。

その他にも、ジャンプ高度、スパイクした時のボールの速度など、多少キャラクターによって違う気がする。ネット際のトスをジャンプ最高高度でスパイクすると、ボールアウトで失点するキャラとしないキャラがいるっぽい。くりすとキーラはジャンプが高い気がする。

とはいえほぼ誤差なので、キーラ以外のキャラクターについては、必殺スパイクの性能くらいしか差異がないとみてよさそう。

試合

7ポイントマッチで1失点までならSランク。3セットであれば、合計2失点まで可能だったと思う。

オート移動の精度が高いので、必殺スパイクを打たれた時の位置調整以外は基本オートでOK、トスもボタン連打でOK。

基本的な得点方法としては以下。

  1. 必殺スパイク
  2. 相手がブロックしてきたときにパワースパイク(相手がキーラ以外)
  3. 相手の通常スパイクをブロック

また、操作キャラクターによっては、相手2人がネット際にいる際にスパイクを打つと、頭上を越して得点できる。キーラとくりすで確認。

相手のレシーブミスやスパイク失敗(空ジャンプ)で得点できたりもするが、安定しない。

ブロックは、相手がスパイクを打とうとジャンプするタイミングより、多少遅くジャンプしたほうが得点につながりやすい気がする。また、相手コート中央あたりからのスパイクをブロックすると、失敗して失点しやすいので注意。

相手のパワースパイクをブロックしてしまうと、キャラクターがキーラでなければ失点につながるため、相手のゲージがない間はブロックして得点を狙い、ゲージが1本以上溜まっていたら必殺スパイク頼み。

パワースパイクは、相手キャラクターによって打ってくる・こないが決まっているような気がするが、詳細不明。

前作よりも必殺スパイクの安定度が高くなり、レシーブ失敗により確実に得点できるようになったので、コンビ技(ゲージ3本消費)は死に技。また、CPUが必殺技を打つとネットにかかったりするので、オプションから「ペアCPU設定」の「スパイク技」と「コンビ技」はOFFにしておく。

対必殺スパイク

基本的には落下位置がほぼ固定なので、覚ゲー。レシーブ自体はボタン連打で可能。

コンビ技を使われると、気絶時間が延長される。高威力必殺スパイク(画面外まで飛ばされるやつ)のコンビ技の場合、オート移動では復帰からのレシーブが間に合わない場合があるが、自分で移動すれば間に合うっぽい。

注意が必要なのは以下。

  • エスメラルダ: コート中央でボールが停止した後、前方・中央・後方の3か所どこかに落ちる。ランダムか法則性があるのか不明。中央で待機していると、中央および前方に落ちるときはオート移動+ボタン連打でレシーブできる模様。ここだけ運ゲーになる。
  • アリア: 前衛のキャラの足元から何か出てきて、気絶させられる。対応としては、操作キャラをネットに密着させておき、必殺スパイクのカットインが表示されたら左キーで後ろに下がると回避できる。そのままスパイクをレシーブ可能。
  • しずく・くりす: 着弾が早く、レシーブ可能な猶予期間が短いため、あらかじめ着弾点近くで待機しておかないとレシーブできない。

ストーリー攻略

しずくがメイン固定で、ストーリーによってパートナーが変わる。

しずくの必殺スパイクは、弾道が一度上にホップするので、コート中央当たりの多少低い位置からぶっ放してもネットにかかりにくく、使いやすい。

その他パートナーの性能(というか必殺スパイク)は以下。

  • 鈴: 必殺スパイクの弾道が低く、ネットにかかりやすい。ゲージはしずくに使わせる。
  • はるな: 必殺スパイクは大きく弧を描いて、コート後方に着弾する。相手CPUが使う分には弱いが、自分で使う分にはネットにかかることがないので使いやすい。
  • くりす: 必殺スパイクがしずくに近くて使いやすい。また、キャラクターのところで書いたが、ジャンプが高いかもしれない。

ということで、しずくを主に操作し、くりすペアの場合は適宜切り替えが安定する。

ストーリーのシナリオ分岐

大きな分岐が2つ、小さな分岐が1つ。分岐はいずれもリザルト表示後。スキップすると上の選択肢が選ばれた状態となる。

シナリオNo1~4までをSランククリアすると、シナリオNo4の「カスミ&かえで」戦後に、はるかに会うかの分岐。シナリオNo1~4までをSランククリアしていれば、シナリオセレクトでNo4をクリアするだけで選択可能。上の「会わない」で世界の頂点編、下の「会う」で謎の挑戦者編に分岐する。

謎の挑戦者編の3戦目、シナリオNo8「アリーナ&アリア」クリア後に、起きるか起きないかの分岐。上の「起きない」でシナリオNo9、下の「起きない」でシナリオNo23へ。シナリオNo23はクリア後、シナリオNo9に合流する。

謎の挑戦者編の5戦目(シナリオNo23を通った場合6戦目)、シナリオNo10の「くりす&はるな」戦後に、くりすを許すかの分岐。上の「くりすを許さない」で炎の友情編、下の「くりすを許す」で過去の清算編に分岐。

水着

全26種類。ストーリーの各シナリオクリアで24種類集まる。

残り2つはストーリーの全シナリオSランククリアと、ワールドツアーを全キャラクターでクリアで入手できる。

ワールドツアーのクリアについては、ペア選択のメインにしたキャラクターのみがクリア扱いとなる。クリア済みのキャラクターについては、選択画面に星マークがつくので判別可能。

キャラクター性能差から、ペアはキーラにすると安定しやすい。

日焼け

ペアのメインとなっているキャラクターで、ワールドツアーモードで2戦すると1段階入手。1種類の水着に対して「ほんのり」と「こんがり」の2段階。しずくの場合、ストーリーモードでも入手可能。

隠し着替えセリフ

特定の日焼け(こんがりのみ)と水着の組み合わせで、隠しセリフがある。コンプリートすると、「はるなのブログ」の最終ページの隠しコマンドが使用可能。

各キャラクターで日焼けを入手しなくても、ワールドツアーモードで日焼け入手済みのキャラクターをメイン、セリフ入手したいキャラクターをペアにして、試合開始前の着替えを行うことで入手可能。

以下、組み合わせ。水着名だと長いので、「日焼け名 + 水着のNo」で記載。

キャラクター セリフ1 セリフ2 セリフ3
しずく スクール水着 + 19 ベアトップ + 18 クイーン + 26
クノイチ + 4 スクール水着 + 11 リモートスーツ + 23
はるな フリフリビキニ + 1 ベアトップ + 25 クノイチ + 24
くりす ゴスロリ + 3 ビキニ + 4 バニー + 9
カスミ クノイチ + 14 ゴスロリ + 17 ホルターネック + 20
かえで バニー + 17 スクール水着 + 6 クノイチ + 10
エリーナ ホルターネック + 5 クイーン + 13 マイクロビキニ + 22
アリーナ ビキニ + 16 リモートスーツ + 12 フリフリビキニ + 8
林香 ゴスロリ + 7 バニー + 13 ビキニ + 20
エスメラルダ ベアトップ + 10 フリフリビキニ + 19 クイーン + 24
リア ホルターネック + 2 リモートスーツ + 11 ゴスロリ + 21
アリア リモートスーツ + 3 フリフリビキニ + 25 クノイチ + 17
キーラ マイクロビキニ + 15 スクール水着 + 4 ゴスロリ + 24

隠しコマンド補記

「*」や「#」を使っているため、Switch版で機能しないっぽい?

「0」キー連続押しは、Xボタンで代用できる模様。

2021/8/4 追記

アップデートにより、「*」や「#」に該当するキーが割り当てられた。

試着室ではXとY、タイトル画面では左と右。

振り返り

ガラケー版が配信終了する直前、au以外のガラケーだと買い切りでも携帯電話を解約するとプレイできなくなるがauでは解約後もプレイできるとかで、auの契約してダウンロードしてすぐ解約した、みたいなブログがあった気がする。

今のソシャゲとかも、サービス終了してプレイできなくなったら、数年後には攻略記事とか見れなくなるんだろうか。

当時、ガラケーで買い切りのゲームとか結構やっていたので、他のメーカーもG-MODEアーカイブスみたいなことやってくれないかな。

このシリーズでは ヘラクレスの栄光Ⅲ 神々の沈黙 がおすすめ。