From ca04c2531f775f61d0c13a3f90d9431bdb3cddb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Fri, 11 Jul 2025 00:19:18 +0200 Subject: [PATCH] Create scaffolding of editor's CLI interface --- editor/build.gradle | 3 + .../com/bartlomiejpluta/base/editor/App.kt | 76 +++++++-- .../base/editor/cli/model/CLIArgs.kt | 11 ++ .../build/pipeline/BuildPipelineService.kt | 12 -- .../pipeline/DefaultBuildPipelineService.kt | 156 ------------------ .../editor/code/view/build/BuildLogsView.kt | 4 +- .../base/editor/main/view/MainMenuView.kt | 4 +- .../runner/app/DefaultApplicationRunner.kt | 4 +- gradle.lock | 20 +++ gradle.properties | 1 + 10 files changed, 100 insertions(+), 191 deletions(-) create mode 100644 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/cli/model/CLIArgs.kt delete mode 100644 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/pipeline/BuildPipelineService.kt delete mode 100644 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/pipeline/DefaultBuildPipelineService.kt diff --git a/editor/build.gradle b/editor/build.gradle index 5c23dd47..98c354e6 100644 --- a/editor/build.gradle +++ b/editor/build.gradle @@ -37,6 +37,9 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib" + // CLI arg parser + implementation "com.xenomachina:kotlin-argparser:${kotlinArgParserVersion}" + // JSON Proto implementation "com.google.protobuf:protobuf-java-util:${protobufVersion}" diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/App.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/App.kt index 12366346..5627e10a 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/App.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/App.kt @@ -1,8 +1,15 @@ package com.bartlomiejpluta.base.editor +import com.bartlomiejpluta.base.editor.cli.model.CLIArgs +import com.bartlomiejpluta.base.editor.code.build.exception.BuildException +import com.bartlomiejpluta.base.editor.code.build.pipeline.BuildPipelineService +import com.bartlomiejpluta.base.editor.code.build.pipeline.BuildService import com.bartlomiejpluta.base.editor.main.controller.MainController import com.bartlomiejpluta.base.editor.main.view.MainView +import com.bartlomiejpluta.base.editor.project.context.ProjectContext +import com.xenomachina.argparser.ArgParser import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.CommandLineRunner import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.context.ConfigurableApplicationContext @@ -14,28 +21,63 @@ import kotlin.reflect.KClass @SpringBootApplication open class EditorApp : App(MainView::class) { - private lateinit var context: ConfigurableApplicationContext + private lateinit var context: ConfigurableApplicationContext - @Autowired - private lateinit var mainController: MainController + @Autowired + private lateinit var mainController: MainController - override fun init() { - this.context = SpringApplication.run(this.javaClass) - context.autowireCapableBeanFactory.autowireBean(this) + override fun init() { + this.context = SpringApplication.run(this.javaClass) + context.autowireCapableBeanFactory.autowireBean(this) - FX.dicontainer = object : DIContainer { - override fun getInstance(type: KClass): T = context.getBean(type.java) - override fun getInstance(type: KClass, name: String): T = context.getBean(name, type.java) - } - } + FX.dicontainer = object : DIContainer { + override fun getInstance(type: KClass): T = context.getBean(type.java) + override fun getInstance(type: KClass, name: String): T = context.getBean(name, type.java) + } + } - override fun stop() { - super.stop() - context.close() - mainController.clearResources() - } + override fun stop() { + super.stop() + context.close() + mainController.clearResources() + } +} + +@SpringBootApplication +open class CLIApp( + @Autowired private val context: ProjectContext, + @Autowired private val pipelineService: BuildPipelineService +) : CommandLineRunner { + override fun run(vararg args: String) { + ArgParser(args).parseInto(::CLIArgs).run { + project.takeIf { it != null }.let { + context.open(it!!) + } + + build.takeIf { it }.let { + pipelineService.initStreams(System.out, System.err) + val startTime = System.currentTimeMillis() + + try { + context.project?.let { pipelineService.runPipeline(it) } + println("Build completed") + } catch (e: BuildException) { + System.err.println("Build failed") + } finally { + val buildingTime = (System.currentTimeMillis() - startTime) / 1000.0 + println("Finished in [${buildingTime}s]") + } + } + } + } } fun main(args: Array) { - launch(args) + ArgParser(args).parseInto(::CLIArgs).run { + if (headless) { + SpringApplication.run(CLIApp::class.java, *args) + } else { + launch(args) + } + } } \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/cli/model/CLIArgs.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/cli/model/CLIArgs.kt new file mode 100644 index 00000000..7ca3a1a1 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/cli/model/CLIArgs.kt @@ -0,0 +1,11 @@ +package com.bartlomiejpluta.base.editor.cli.model + +import com.xenomachina.argparser.ArgParser +import com.xenomachina.argparser.default +import java.io.File + +class CLIArgs(parser: ArgParser) { + val headless by parser.flagging("-H", "--headless", help = "run editor in headless mode (without GUI)").default(false) + val project by parser.storing("-p", "--project", help = "project file") { File(this) }.default(null) + val build by parser.flagging("-b", "--build", help = "build project").default(false) +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/pipeline/BuildPipelineService.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/pipeline/BuildPipelineService.kt deleted file mode 100644 index ccb01e3c..00000000 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/pipeline/BuildPipelineService.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.bartlomiejpluta.base.editor.code.build.pipeline - -import javafx.beans.property.BooleanProperty -import javafx.concurrent.Task -import java.io.OutputStream - -interface BuildPipelineService { - fun initStreams(stdout: OutputStream, stderr: OutputStream) - val isRunningProperty: BooleanProperty - fun build(): Task - fun clean() -} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/pipeline/DefaultBuildPipelineService.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/pipeline/DefaultBuildPipelineService.kt deleted file mode 100644 index dea9ff28..00000000 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/pipeline/DefaultBuildPipelineService.kt +++ /dev/null @@ -1,156 +0,0 @@ -package com.bartlomiejpluta.base.editor.code.build.pipeline - -import com.bartlomiejpluta.base.editor.code.build.asset.AssetSerializer -import com.bartlomiejpluta.base.editor.code.build.compiler.Compiler -import com.bartlomiejpluta.base.editor.code.build.database.DatabaseAssembler -import com.bartlomiejpluta.base.editor.code.build.exception.BuildException -import com.bartlomiejpluta.base.editor.code.build.game.GameEngineProvider -import com.bartlomiejpluta.base.editor.code.build.generator.CodeGenerator -import com.bartlomiejpluta.base.editor.code.build.packager.JarPackager -import com.bartlomiejpluta.base.editor.code.build.project.ProjectAssembler -import com.bartlomiejpluta.base.editor.code.dependency.DependenciesProvider -import com.bartlomiejpluta.base.editor.event.ClearBuildLogsEvent -import com.bartlomiejpluta.base.editor.file.model.FileSystemNode -import com.bartlomiejpluta.base.editor.project.context.ProjectContext -import com.bartlomiejpluta.base.editor.project.model.Project -import javafx.beans.property.SimpleObjectProperty -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Component -import tornadofx.* -import tornadofx.FX.Companion.eventbus -import java.io.OutputStream -import java.io.PrintStream - -@Component -class DefaultBuildPipelineService : BuildPipelineService { - - @Autowired - private lateinit var dependenciesProvider: DependenciesProvider - - @Autowired - private lateinit var generators: List - - @Autowired - private lateinit var compiler: Compiler - - @Autowired - private lateinit var packager: JarPackager - - @Autowired - private lateinit var engineProvider: GameEngineProvider - - @Autowired - private lateinit var assetSerializer: AssetSerializer - - @Autowired - private lateinit var projectAssembler: ProjectAssembler - - @Autowired - private lateinit var databaseAssembler: DatabaseAssembler - - @Autowired - private lateinit var projectContext: ProjectContext - - private val latchProperty = SimpleObjectProperty() - private var latch by latchProperty - - private lateinit var stdout: OutputStream - private lateinit var stderr: OutputStream - private lateinit var out: PrintStream - private lateinit var err: PrintStream - - override fun initStreams(stdout: OutputStream, stderr: OutputStream) { - this.stdout = stdout - this.stderr = stderr - this.out = PrintStream(stdout) - this.err = PrintStream(stderr) - } - - override val isRunningProperty = false.toProperty() - private var isRunning by isRunningProperty - - init { - latchProperty.addListener { _, _, latch -> - when (latch) { - null -> isRunning = false - else -> isRunningProperty.bind(latch.lockedProperty()) - } - } - } - - override fun build() = runAsync { - latch?.locked?.takeIf { it }?.let { - cancel() - return@runAsync false - } - - latch = Latch() - - val startTime = System.currentTimeMillis() - - try { - projectContext.project?.let(this@DefaultBuildPipelineService::runPipeline) - out.println("Build completed") - return@runAsync true - } catch (e: BuildException) { - err.println("Build failed") - } finally { - latch?.release() - val buildingTime = (System.currentTimeMillis() - startTime) / 1000.0 - out.println("Finished in [${buildingTime}s]") - } - - false - } - - private fun runPipeline(project: Project) { - eventbus.fire(ClearBuildLogsEvent) - prepareBuildDirectory(project) - - val outputFile = project.buildOutputJarFile - - out.println("Providing compile-time dependencies...") - val dependencies = dependenciesProvider.provideDependenciesTo(project.buildDependenciesDirectory) - - out.println("Generating sources...") - generators.forEach(CodeGenerator::generate) - - out.println("Compiling sources...") - compiler.compile( - arrayOf(project.codeFSNode, FileSystemNode(project.buildGeneratedCodeDirectory)), - project.buildClassesDirectory, - dependencies.toTypedArray(), - out, - err - ) - - out.println("Assembling game engine...") - engineProvider.provideBaseGameEngine(outputFile, out, err) - - out.println("Linking compilation units...") - packager.pack(project.buildClassesDirectory, outputFile, "BOOT-INF/classes") - - out.println("Serializing project assets...") - assetSerializer.serializeAssets(project) - - out.println("Assembling project assets...") - projectAssembler.assembly(project, outputFile, out, err) - - out.println("Assembling database...") - databaseAssembler.assembly(project, outputFile, out, err) - } - - private fun prepareBuildDirectory(project: Project) { - project.buildDirectory.deleteRecursively() - - project.buildClassesDirectory.mkdirs() - project.buildOutDirectory.mkdirs() - project.buildDependenciesDirectory.mkdirs() - project.buildGeneratedCodeDirectory.mkdirs() - } - - override fun clean() { - projectContext.project?.apply { buildDirectory.deleteRecursively() } - out.println("Cleaning done") - } -} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/build/BuildLogsView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/build/BuildLogsView.kt index 25ff4239..f574c71e 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/build/BuildLogsView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/build/BuildLogsView.kt @@ -1,13 +1,13 @@ package com.bartlomiejpluta.base.editor.code.view.build -import com.bartlomiejpluta.base.editor.code.build.pipeline.BuildPipelineService +import com.bartlomiejpluta.base.editor.code.build.pipeline.BuildService import com.bartlomiejpluta.base.editor.common.logs.component.LogsPane import com.bartlomiejpluta.base.editor.event.ClearBuildLogsEvent import org.kordamp.ikonli.javafx.FontIcon import tornadofx.* class BuildLogsView : View() { - private val pipelineService: BuildPipelineService by di() + private val pipelineService: BuildService by di() private val logsPane = LogsPane() diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/view/MainMenuView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/view/MainMenuView.kt index 71138d5b..4d912d28 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/view/MainMenuView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/view/MainMenuView.kt @@ -1,6 +1,6 @@ package com.bartlomiejpluta.base.editor.main.view -import com.bartlomiejpluta.base.editor.code.build.pipeline.BuildPipelineService +import com.bartlomiejpluta.base.editor.code.build.pipeline.BuildService import com.bartlomiejpluta.base.editor.main.controller.MainController import com.bartlomiejpluta.base.editor.process.runner.app.ApplicationRunner import com.bartlomiejpluta.base.editor.project.context.ProjectContext @@ -10,7 +10,7 @@ import tornadofx.* class MainMenuView : View() { private val mainController: MainController by di() private val projectContext: ProjectContext by di() - private val buildPipelineService: BuildPipelineService by di() + private val buildPipelineService: BuildService by di() private val applicationRunner: ApplicationRunner by di() override val root = menubar { diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/process/runner/app/DefaultApplicationRunner.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/process/runner/app/DefaultApplicationRunner.kt index 9d59d1fa..edfd68e4 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/process/runner/app/DefaultApplicationRunner.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/process/runner/app/DefaultApplicationRunner.kt @@ -1,6 +1,6 @@ package com.bartlomiejpluta.base.editor.process.runner.app -import com.bartlomiejpluta.base.editor.code.build.pipeline.BuildPipelineService +import com.bartlomiejpluta.base.editor.code.build.pipeline.BuildService import com.bartlomiejpluta.base.editor.event.ClearProcessLogsEvent import com.bartlomiejpluta.base.editor.process.runner.jar.JarRunner import com.bartlomiejpluta.base.editor.project.context.ProjectContext @@ -24,7 +24,7 @@ class DefaultApplicationRunner : ApplicationRunner { private lateinit var projectContext: ProjectContext @Autowired - private lateinit var pipelineService: BuildPipelineService + private lateinit var pipelineService: BuildService @Autowired private lateinit var jarRunner: JarRunner diff --git a/gradle.lock b/gradle.lock index 9b3114a9..5b5df398 100644 --- a/gradle.lock +++ b/gradle.lock @@ -457,6 +457,26 @@ "hash": "sha256-VKNPqFAqRryQ79tJJiYAWR+oC/mjT1pMeYMRrsFsqXc=" } }, + "com.xenomachina:kotlin-argparser:2.0.7": { + "kotlin-argparser-2.0.7.jar": { + "url": "https://repo.maven.apache.org/maven2/com/xenomachina/kotlin-argparser/2.0.7/kotlin-argparser-2.0.7.jar", + "hash": "sha256-/wMS6nwU/l7OjE5hf6hVpAj5NvtFPDxb49fVfnoUN1M=" + }, + "kotlin-argparser-2.0.7.pom": { + "url": "https://repo.maven.apache.org/maven2/com/xenomachina/kotlin-argparser/2.0.7/kotlin-argparser-2.0.7.pom", + "hash": "sha256-Oi3hHJvOk2WolEAvp3fmbPgolrHpB86tWShPAjrfhvI=" + } + }, + "com.xenomachina:xenocom:0.0.7": { + "xenocom-0.0.7.jar": { + "url": "https://repo.maven.apache.org/maven2/com/xenomachina/xenocom/0.0.7/xenocom-0.0.7.jar", + "hash": "sha256-xiQmUrnoLXzk6BHDz3YCWC+46NyRk7PLfzR14CCqaNE=" + }, + "xenocom-0.0.7.pom": { + "url": "https://repo.maven.apache.org/maven2/com/xenomachina/xenocom/0.0.7/xenocom-0.0.7.pom", + "hash": "sha256-tN/7gDFK2W32lbNKB6orVLVoGNLziKKoE/h1ExDqF2s=" + } + }, "com.zaxxer:HikariCP:5.0.0": { "HikariCP-5.0.0.jar": { "url": "https://repo.maven.apache.org/maven2/com/zaxxer/HikariCP/5.0.0/HikariCP-5.0.0.jar", diff --git a/gradle.properties b/gradle.properties index 3bf1fa34..40eafe32 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,3 +18,4 @@ apacheCommonsVersion=3.6.1 h2Version=1.4.200 hikariVersion=5.0.0 javaPoetVersion=1.13.0 +kotlinArgParserVersion=2.0.7