decadence

個人のメモ帳

Playのsbt-pluginに含まれる様々なtrait

ScalaのWebアプリケーションフレームワークであるPlay2、基板を支えるsbt-pluginに含まれる色々なtraitを見る。なおPlayのPluginとは別物なので注意。

PlayRun, PlaySettingsあたり非常に興味深い。playのsbt設定や、runを実行した時にどのような処理が行われるのかが分かる。

以下で触れるのは2.3.xブランチのこのcommit上の話、ただコードを読むだけ

trait PlayImport

jdbc, anormなどのcomponent

  def component(id: String) = "com.typesafe.play" %% id % play.core.PlayVersion.current

saclaテンプレートでデフォルトで読み込まれるimport

val defaultScalaTemplateImports = Seq(
  "models._",
  "controllers._",
  "play.api.i18n._",
  "play.api.mvc._",
  "play.api.data._",
  "views.%format%._")

emojiLogs...テストの結果を絵文字で表示してくれる

object PlayKeysにて、以下のようなSettingKeyやTaskKeyなどの宣言 playVersion, playDefaultPort, playRunHooks, playRoutesImports, playGenerateReverseRouter, playGenerateRefReverseRouter, playNamespaceReverseRouter, playMonitoredFiles, playWatchService

PlaySettings

  • self type annotation
    • this: PlayCommands with PlayPositionMapper with PlayRun with PlaySourceGenerators

PlayImportやsbtに宣言されたSettingKeyのPlay用実装が主な利用 PlayRunやPlayCommandsで定義された値とか使ってる

  • defaultScalaSettings
    • TwirlのtemplateImports
    • defaultScalaTemplateImports
  • closureCompilerSettings
    • JavascriptCompiler: minとかするやつ
    • LessCompiler
    • CoffeescriptCompiler
  • manageClasspath
  • defaultSettings: 色々入ってる
    • Typesafe Releases Repositoryをresolverに追加
    • target, source, conf, scalaSource, javaSourceなどのディレクトリ指定
    • TwirlCompileTemplates時のsourceDirectoriesをCompile時とTest時で別にしてる
    • javaOptionに-encoding utf8追加
    • libraryDependenciesに以下のものを追加
      • "com.typesafe.play" %% "play" % play.core.PlayVersion.current
      • "com.typesafe.play" %% "play-test" % play.core.PlayVersion.current
      • "com.typesafe.play" %% "play-docs" % play.core.PlayVersion.current
    • parallelExection in Text := false: sbtタスクの並列実行
    • fork in Test := true: テストconfiguration時にはJVMを別に起動
    • Specs2とJUnit別にtestOptionsを追加
      • Specs2: sequential=true, junitxml=console
      • JUnit: --ignore-runners=org.specs2.runner.JUnitRunner
    • ReverseRouterを生成させるフラグなどの設定
    • watchSources(監視対象)にconfディレクトリやapp(ただしapp/assetsは除く)など追加
    • CompileコンフィギュレーションrunplayDefaultRunTaskを追加
      • val playDefaultRunTask = playRunTask(playRunHooks, playDependencyClasspath, playDependencyClassLoader, playReloaderClasspath, playReloaderClassLoader, playAssetsClassLoader)
    • shellPromptplayPrompt(プロジェクト名など出す)を設定
    • Compile/TestコンフィギュレーションcompilePlayCommands.PostCompileっての入れてる
      • この中でテンプレート云々とかしてたりする?(後で見る)
    • playRunHooks := Nil
    • playInteractionMode := play.PlayConsoleInteractionMode
    • assetsPrefix := "public/"
    • devSettings := Nil
    • TwirlKeys.templateImports ++= defaultTemplateImports
    • mainClass in (Compile, run) := Some("play.core.server.NettyServer")
      • 知らんかった...

PlayCommands

  • 継承
    • extends PlayAssetsCompiler with PlayEclipse with PlayInternalKeys
  • self type annotation
    • PlayReloader

色んなコマンドやタスクの実装がある

  • PostCompile
    • timestampはcacheDir/play_instrumentation(target/scala-x.x.x/cache/root/compile/play_instructionとか)
    • javaクラスやテンプレートクラスのenhanceが必要かを編集時刻などを見て取得
    • analysis.apis.internal(sourceFile).compilation.startTimeコンパイルもしてる気がする?(ここのanalysiscompile in scopesourceFile*.java*.template.scala)
    • javaクラスのenhance
      • PropertiesEnhancer.generateAccessors, PropertiesEnhancerrewriteAccess
    • テンプレートクラスのenhance
      • PropertiesEnhancer.rewriteAccess
    • ebeanEnhancement
    • compile時にはtarget/scala_2.x.x/*_managed以下にrouteなどの自動生成されたクラスなどをコピーする
    • enhanceされたクラスは編集時刻などの更新を行う
  • idea, playPrompt, h2-browser, classpath, sh(Processに与えてJvmIOで処理してる), playCommoClassloaderTask(普通にclasspassをURLClassLoaderに渡して作ってる, /* important here, don't depend of the sbt classLoader! */), playMonitoredFilesTask, computeDependencies(libraryDependencies間違えるとよく出るエラーはこのへん), ``
  • RequireJsの処理とかある(使ってないしあまり興味無い)

enhanceって何してんの(play.core.enhancers.PropertiesEnhancer)

  • PropertiesEnhancer.generateAccessors
    • getDeclaredFiledsで取り出した定数などでない、publicなproperty field全てにpublicなgetter/setter生やしてく
  • PropertiesEnhancerrewriteAccess
    • アクセッサーを利用している箇所のgetter/setterの名前をgetとsetの後に続く1文字目のみ大文字となるような表記に書き換えてる?

PlayRun

  • 継承
    • extends PlayInternalKeys
  • self type annotation
    • PlayReloader

playDefaultRunTasktwiddleRunMonitorplayStartCommandがメイン

val playDefaultRunTask = playRunTask(playRunHooks, playDependencyClasspath, playDependencyClassLoader,
  playReloaderClasspath, playReloaderClassLoader, playAssetsClassLoader)
  • playRunTask(run in Compile <<= playDefaultRunTask)
    • devModeServerを以下のstartDevModeから用意
    • startDevMode: 評価された時に中身が順に実行される
      • 引数を踏まえてportやpropertiesを取得
      • /* We need to do a bit of classloader magic to run the Play application. There are seven classloaders: */
      • runHooks.run(_.beforeStarted())
      • serverとしてmainClass(play.core.server.NettyServer)mainDevHttpMode/mainDevOnlyHttpsModeからServerWithStopが起動
      • runHooks.run(_.afterStarted(server.mainAddress))
      • reloaderを持って、close()時に先ほどのserverが停止したりrunHooks.run(_.afterStopped())が実行されるPlayDevServerが返却される
    • ~のあるなしで分岐し、Console実行の場合:~ -> PlayInteractionMode.doWithoutEchoによる入力待ち、~なし -> PlayInteractionMode.waitForCancelによる入力待ち、非同期実行の場合は何もしないか引数の関数を実行するか
  • twiddleRunMonitor: ~runみたいなチルダ付けた時の動作
    • SourceModificationWatch.watchによるファイルの変更監視
    • Thread.sleepとかしつつ状態の変化を見てProject.runTask(compile in Compile, newState)を実行する
  • playStartCommand: startコマンド実行時の動作
    • 引数を踏まえてportやpropertiesを取得
    • Project.runTask(stage, state)を実行
    • 正しく動作した場合、実行バイナリをstadingDirectory以下から取得
    • 新しいThreadを立てて、その中で先ほどのバイナリを実行
  • その他playのasset関連の設定

PlayPositionMapper

コンパイルエラー時にソースの行番号を表示するためのマッパー

PlayReloader

  • self type
    • PlayCommands with PlayPositionMapper

PlayRunの開発サーバを支えるreloaderを提供 reloaderはplay.core.BuildLinkclosegetClassLoaderを実装したもので、play.core.ReloadableApplicationの引数などに利用される*1

play.core.BuildLinkって?

Interface used by the Play build plugin to communicate with an embedded Play server.

  • PlayWatchServiceに監視対象となるファイルを与えて、変更したかどうかのフラグを持つ
  • reloadは同期的に行われ、ファイルの変更があるなどを事前条件に以下の順に実行される(sbt runの場合を基に)
    • TaskKey[sbt.inc.Analysis]("play-reload")が実行 ((PlayCommandsのval playReloadTask = Def.task(playCompileEverything.value.reduceLeft(_ ++ _))))
    • 成功すれば、TaskKey[Classpath]("play-reloader-classpath")
    • 成功した時、クラスパス内のファイルに変更がある場合のみclassloaderを入れ替える (ファイルの変更はSourceModificationWatch内にてファイルの編集時刻の違いを見るなどして判定) (runTaskでは、sbtのAct.scopedKeyparerを利用して指定されたタスクを実行)
  • close時には、classloaderを破棄し上記で挙げたファイルの変更を監視するwatcherを止める

PlayInternalKeys

classpath関連のタスク、playCommonClassLoaderplayStopplayReloadplay-all-assets

PlayEclipse

  • self type
    • PlayCommands

com.typesafe.sbteclipse.coreのラッパー

PlayAssetsCompiler

  • AssetsCompiler
    • ファイルの最終編集時刻見て、変更のある場合に、変更のあるファイルのみコンパイルしてpublic以下に置く
    • ファイルの変更とかは、sbt.Sync使って管理してる
  • LessCompiler
  • JavascriptCompiler
  • CoffeescriptCompiler

PlaySourceGenerators

  • RoutesCompier使ってrouteファイルを生成
  • compileエラー表示

JvmIO, JvmLogger

別スレッドで何らかのプロセスを実行するためのラッパー 複数JVM立ち上げてなんかするためのPlayJvmコメントアウトされて存在してた

*1:TODO NettyServerとか含めて後で見る