経緯
GAEのスタンドアロンなSDKが非推奨になり、Google Cloud SDKへの移行が必須になりました。
2019 年 7 月 30 日以降、スタンドアロンの App Engine SDK は非推奨になりました。2020 年 8 月 30 日以降は、ダウンロードできなくなります。
参考: https://cloud.google.com/appengine/docs/standard/java/sdk-gcloud-migration?hl=ja
sbtで使っていたGAE用のプラグインsbt-appengineがスタンドアロンSDKに依存しているのでGoogleさんが標準で用意しているMavenに移行してみた時の記録。
XMLからYAMLへのファイル形式の移行
Google Cloud SDKではqueue.xml
などの設定系ファイルのXML形式が非サポートになり、queue.yaml
などYAML形式に変換しないとダメな様です。
gcloud
コマンドで変換ができるのでこれを使います。
https://cloud.google.com/appengine/docs/standard/java/sdk-gcloud-migration?hl=ja#migrating_xml_to_yaml_file_formats
gcloud beta app migrate-config queue-xml-to-yaml src/main/webapp/WEB-INF/queue.xml
gcloud beta app migrate-config datastore-indexes-xml-to-yaml src/main/webapp/WEB-INF/datastore-indexes.xml
gcloud beta app migrate-config cron-xml-to-yaml src/main/webapp/WEB-INF/cron.xml
など。
テストでqueue.xml
を読んでる場合はここもsetQueueXmlPath
からsetQueueYamlPath
に変更する必要があります。
(1.9.80以降で追加されたメソッドの様です。参考: https://stackoverflow.com/questions/59406695/appengine-unit-test-non-default-queues-in-queue-yaml )
- new LocalTaskQueueTestConfig().setQueueXmlPath("src/main/webapp/WEB-INF/queue.xml")
+ new LocalTaskQueueTestConfig().setQueueYamlPath("src/main/webapp/WEB-INF/queue.yaml")
build.sbtからpom.xmlへの移行
pom.xmlのテンプレを生成する
Mavenのarchetypeが用意されているので公式ドキュメントに従ってMavenプロジェクトを生成します。
mvn archetype:generate -Dappengine-version=1.9.81 -Dfilter=com.google.appengine.archetypes:
すると出てくる 2: remote -> com.google.appengine.archetypes:appengine-skeleton-archetype (A skeleton application with Google App Engine)
を選びます。
※ドキュメント記載のappengine-versionが古かった(1.9.59)ので現時点での最新(1.9.81)を指定しました
artifactId
で指定したサブディレクトリにpom.xml
が生成されるのでプロジェクトのルート(build.sbt
と同じところ)にコピーします。
Scala Maven Pluginを追加
https://docs.scala-lang.org/tutorials/scala-with-maven.html を参考に、
src/main/scala
以下の*.scala
ファイルをコンパイルする様に<plugins>
以下に追加します。
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>4.4.0</version>
<configuration>
<recompileMode>incremental</recompileMode>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<properties>
以下に使用するScalaのバージョンを指定できる様にしておいて、
<scala.version>2.10.4</scala.version>
該当バージョンのscala-libraryを<dependencies>
以下に追加します。
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
build.sbtからdependencyの移動
続けてbuild.sbt
で設定していたライブラリ依存関係の定義をpom.xml
に移していきます。
sbt makePom
するとtarget
ディレクトリ以下にpom.xml
に変換してくれるので、そこから必要なdependencyをコピペするのが楽です。
例えば
libraryDependencies += "org.json4s" %% "json4s-jackson" % "3.1.0"
はこんな感じ↓になります。
<dependency>
<groupId>org.json4s</groupId>
<artifactId>json4s-jackson_2.10</artifactId>
<version>3.1.0</version>
</dependency>
Twirlのコンパイルができる様にする
テンプレートエンジンとしてTwirl
を使っている場合はTwirl Maven Pluginを追加する必要があります。
<plugins>
以下に↓を追加します。
<plugin>
<groupId>com.jakewharton.twirl</groupId>
<artifactId>twirl-maven-plugin</artifactId>
<version>1.2.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
標準のコンパイラプラグインをスキップする
Scala Maven Pluginが*.java
ファイルもコンパイルしてくれるので、標準のMaven Compiler Pluginの方は<configuration><skipMain>true</skipMain></configuration>
を追加してコンパイルをスキップさせます。
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<skipMain>true</skipMain>
</configuration>
</plugin>
Specsのテストが実行される様にする
※めっちゃ古いプロジェクトなので未だにSpecs(Specs2ではなく)のテストが残っていました
若干ソースコードの修正も必要です。とは言ってもextendするのをorg.specs.Specification
からorg.specs.SpecificationWithJUnit
に変更するだけです。
(あと、object
ではなくclass
にする必要があります)
-import org.specs.Specification
+import org.specs.SpecificationWithJUnit
-object ExampleSpec extends Specification {
+class ExampleSpec extends SpecificationWithJUnit {
Surefire Pluginの<configuration>
を↓の様に足してSpecs
のテストが実行される様にします。
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20</version>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
<argLine>-Xmx512m</argLine>
<includes>
<include>**/*Spec.java</include>
</includes>
</configuration>
</plugin>
MavenでGAE開発サーバーを起動する
sbt appengineDevServer
に該当するのはMavenでは
mvn appengine:run
になります。
また、
sbt console
は
mvn scala:console
になります。
応用編
hot reloadingに対応する
sbt-appengineの場合は
sbt ~appengineDevServer
でソースコードの変更を検出して自動でGAEの開発サーバーを再起動してくれましたがMavenのPluginではやってくれません。
https://github.com/fizzed/maven-plugins に含まれるWatcher Pluginを使うとソースコードの変更に応じてMavenの任意のゴールを実行してくれるのでこれを使います。
変更を監視したいディレクトリを<watches>
以下で設定し、実行したいゴールを<goals>
以下で指定します。
ゴールは複数書けるので、Twirlで*.scala
を生成してからコンパイル、は↓みたいに設定できます。
<plugin>
<groupId>com.fizzed</groupId>
<artifactId>fizzed-watcher-maven-plugin</artifactId>
<version>1.0.6</version>
<configuration>
<touchFile>${project.build.directory}/${project.build.finalName}/WEB-INF/appengine-web.xml</touchFile>
<watches>
<watch>
<!-- *.java混在してない場合はここ不要 -->
<directory>${project.basedir}/src/main/java</directory>
</watch>
<watch>
<directory>${project.basedir}/src/main/scala</directory>
</watch>
<watch>
<directory>${project.basedir}/src/main/twirl</directory>
</watch>
</watches>
<goals>
<goal>twirl:compile</goal>
<goal>scala:compile</goal>
</goals>
</configuration>
</plugin>
また、GAEの開発サーバーは、appengine-web.xml
の変更を検出して再起動してくれるので<touchFile>
でappengine-web.xml
のタイムスタンプを書き換える様にします。
参考: https://cloud.google.com/appengine/docs/standard/java/maven-reference?hl=ja#appenginerun
サーバーの稼働中は、appengine-web.xml が変更されていないかどうか継続的に確認します。変更されている場合、サーバーがアプリケーションを再度読み込みます
以上の設定をして、別プロセスで
mvn fizzed-watcher:run
を起動しておくとhot reloadingぽいことが実現できます。
Dockerを使用する場合
例:
docker run --rm -ti \
-v $HOME/.m2:/root/.m2 -v $HOME/.sbt:/root/.sbt \
-v `pwd`:/app -p 8080:8080 $YOUR_IMAGE \
mvn package appengine:run -DcloudSdkHome=/root/google-cloud-sdk
Mavenレポジトリのキャッシュ
-v $HOME/.m2:/root/.m2
dependencyのjarたちを落としてくるのが非常に遅いので、ホストのMavenレポジトリキャッシュのディレクトリをコンテナにマウントするとビルド時間を節約できます。
~/.sbt
のキャッシュ
-v $HOME/.sbt:/root/.sbt
sbtは使っていませんが、Scala Maven Pluginで使われている https://github.com/sbt/zinc が~/.sbt/
以下のキャッシュを使う様なのでここもマウントしています。
インストール済みCloud SDKを使う
-DcloudSdkHome=/root/google-cloud-sdk
何も指定しないとGoogle Cloud SDKが既にインストール済みでもPluginが別途ダウンロードしてしまいます。
Dockerのイメージに入れておけば、cloudSdkHome
でインストール済みのCloud SDKのパスを指定してやるとそっちを使ってくれるのでだいぶ時間を短縮できます。
これはDockerに限った話では無いので、-DcloudSdkHome=$(dirname $(dirname $(which gcloud)))
とかしておくとローカル環境でもインストール済みCloud SDKを使ってくれて便利です。