decadence

個人のメモ帳

sbtのworkflow、shellの起動とか

sbt起動時、xsbt.bootの話の続き

sbtのWorkflow、Stateを新しいものにしつつ回すって話

SBT in Actionでいう"Figure 10.2 - Workflow of sbt’s command engine"の話(どっかに図落ちてないかな)

現時点の最新版のコミット見てる

xsbti.AppMain

public MainResult run(AppConfiguration configuration);だけ持つインタフェース

継承してるのは以下の3つ。各々、初期状態のStateを用意し、MainLoop.runLogged(state)へ渡す。初期状態のStateを用意する際は、def initialState(configuration: xsbti.AppConfiguration, initialDefinitions: Seq[Command], preCommands: Seq[String]): Stateを用いる

初期状態のStateは以下のようになる

  • sbt.xMain
    • configuration: Launchの時点で、設定ファイルから読み込んだやつ
    • initialDefinition
      • defaults(めっちゃたくさんコマンドを使えるようにするコマンド), early(他のコマンドより早く他のコマンドを実行させるやつ)
    • preCommands: 以下の3つが順含まれてる
      • defaultsを実行して、多くのコマンドを使えるようにする
      • ~/.sbtrc, .sbtrcを読み込むやつ
      • bootコマンド ← なるほど
        • def DefaultBootCommands: Seq[String] = LoadProject :: (IfLast + " " + Shell) :: Nil
        • なんのことはなく、プロジェクトを読み込んで(LoadProject=reload)sbt shellを起動する(Shell=shell)
        • IfLastにより、shellコマンドが最後に実行される
  • sbt.ScriptMain:
    • configuration: Launchの時点で、設定ファイルから読み込んだやつ
    • initialDefinition
      • BuiltinCommands.ScriptCommands:
        • Seq(ignore, exit, Script.command, setLogLevel, early, act, nop)
        • Script.commandってのがscriptってコマンド
        • sbtの引数に与えられたファイルを実行して設定に追加するやつ
    • preCommands
      • 上記scriptコマンドのみが含まれる
  • sbt.ConsoleMain
    • configuration: Launchの時点で、設定ファイルから読み込んだやつ
    • initialDefinition
      • BuiltinCommands.ConsoleCommands:
        • Seq(ignore, exit, IvyConsole.command, setLogLevel, early, act, nop)
        • IvyConsole.commandってのがivy-consoleってコマンド
        • 引数からivyの依存(外部urlとかjarとか)取得して、libraryDependenciesの設定とかに追加、その後でconsole-quickだけ実行するようなStateを返す
    • preCommands
      • 上記ivy-consoleコマンドのみが含まれる

MainLoop.runManaged(state)

  • sbt.TrapExitで囲ってMainLoop.runLogged(state)
    • System.exitが呼ばれた際にラップするもの
    • try TrapExit.installManager
      • SecurityManagerをTrapExitのインスンタンスなものに更新
    • finally TrapExit.uninstallManager(previous)で元に戻す

MainLoop.runLogged(state)

  • shutdownHookを設定
    • jline.TerminalFactory.get().restore()
  • runLoggedLoop(state, state.globalLogging.backing): 再帰するやつ
    • Run loop that evaluates remaining commands and manages changes to global logging configuration.
    • runAndClearLast
      • ↓ な感じ
      • Command.processを実行して、引数にはstate.remainingCommandsのheadとremainingCommandshistoryをいじったものを与え、その結果を返す
        • ちなみにremainingCommands: Seq[String]
      • remainingCommandsが無い場合には、Return(Exit(0))を返す
      • 正常系なら、上の2つのみがremainingCommandsが無くなるまで実行されて終了する
        • shellコマンドでsbt shellに入ったり、コマンドの実行中にremainingCommandsが増えたりもする
/** Runs the next sequence of commands that doesn't require global logging changes.*/
@tailrec def run(state: State): RunNext =
  state.next match {
    case State.Continue       => run(next(state))
    case State.ClearGlobalLog => new ClearGlobalLog(state.continue)
    case State.KeepLastLog    => new KeepGlobalLog(state.continue)
    case ret: State.Return    => new Return(ret.result)
}

def next(state: State): State =
  ErrorHandling.wideConvert { state.process(Command.process) } match {
    case Right(s)                  => s
    case Left(t: xsbti.FullReload) => throw t
    case Left(t)                   => handleException(t, state)
  }
}
implicit def stateOps(s: State): StateOps = new StateOps {
  def process(f: (String, State) => State): State =
    s.remainingCommands match {
      case Seq() => exit(true)
      case Seq(x, xs @ _*) =>
        log.debug(s"> $x")
        f(x, s.copy(remainingCommands = xs, history = x :: s.history))
    }
    ...

sbt.State.Nextの話と、Command.processの話あたり