[Editor] Improve CLI handling

This commit is contained in:
2025-07-11 12:11:36 +02:00
parent c21570f912
commit e1bca324c1
8 changed files with 188 additions and 127 deletions

View File

@@ -1,6 +1,7 @@
package com.bartlomiejpluta.base.editor package com.bartlomiejpluta.base.editor
import com.bartlomiejpluta.base.editor.cli.model.CLIArgs import com.bartlomiejpluta.base.editor.cli.model.CLIArgs
import com.bartlomiejpluta.base.editor.cli.runner.CLIRunner
import com.bartlomiejpluta.base.editor.code.build.exception.BuildException 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.BuildPipelineService
import com.bartlomiejpluta.base.editor.command.service.DefaultUndoRedoService import com.bartlomiejpluta.base.editor.command.service.DefaultUndoRedoService
@@ -8,80 +9,67 @@ import com.bartlomiejpluta.base.editor.main.controller.MainController
import com.bartlomiejpluta.base.editor.main.view.MainView import com.bartlomiejpluta.base.editor.main.view.MainView
import com.bartlomiejpluta.base.editor.project.context.ProjectContext import com.bartlomiejpluta.base.editor.project.context.ProjectContext
import com.xenomachina.argparser.ArgParser import com.xenomachina.argparser.ArgParser
import com.xenomachina.argparser.mainBody
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.CommandLineRunner import org.springframework.boot.CommandLineRunner
import org.springframework.boot.SpringApplication import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.ConfigurableApplicationContext import org.springframework.context.ConfigurableApplicationContext
import tornadofx.App import tornadofx.*
import tornadofx.DIContainer
import tornadofx.FX
import tornadofx.launch
import java.io.File import java.io.File
import javax.annotation.PostConstruct
import kotlin.reflect.KClass import kotlin.reflect.KClass
@SpringBootApplication @SpringBootApplication
open class EditorApp : App(MainView::class) { open class EditorApp : App(MainView::class) {
private lateinit var context: ConfigurableApplicationContext private lateinit var context: ConfigurableApplicationContext
@Autowired @Autowired
private lateinit var mainController: MainController private lateinit var mainController: MainController
override fun init() { @Autowired
this.context = SpringApplication.run(this.javaClass) private lateinit var cliRunner: CLIRunner
context.autowireCapableBeanFactory.autowireBean(this)
FX.dicontainer = object : DIContainer { override fun init() {
override fun <T : Any> getInstance(type: KClass<T>): T = context.getBean(type.java) this.context = SpringApplication.run(this.javaClass)
override fun <T : Any> getInstance(type: KClass<T>, name: String): T = context.getBean(name, type.java) context.autowireCapableBeanFactory.autowireBean(this)
}
}
override fun stop() { FX.dicontainer = object : DIContainer {
super.stop() override fun <T : Any> getInstance(type: KClass<T>): T = context.getBean(type.java)
context.close() override fun <T : Any> getInstance(type: KClass<T>, name: String): T = context.getBean(name, type.java)
mainController.clearResources() }
} }
override fun onBeforeShow(view: UIComponent) {
super.onBeforeShow(view)
cliRunner.run(*parameters.raw.toTypedArray())
}
override fun stop() {
super.stop()
context.close()
mainController.clearResources()
}
} }
@SpringBootApplication @SpringBootApplication
open class CLIApp( open class CLIApp(
@Autowired private val context: ProjectContext, @Autowired private val cliRunner: CLIRunner
@Autowired private val pipelineService: BuildPipelineService
) : CommandLineRunner { ) : CommandLineRunner {
override fun run(vararg args: String) { override fun run(vararg args: String) {
ArgParser(args).parseInto(::CLIArgs).run { cliRunner.run(*args)
project?.let { context.open(it) } }
if (build) {
pipelineService.initStreams(System.out, System.err)
val startTime = System.currentTimeMillis()
try {
context.project?.let { project ->
output?.let { project.changeBuildDirectory(it) }
pipelineService.runPipeline(project)
}
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<String>) { fun main(args: Array<String>) {
ArgParser(args).parseInto(::CLIArgs).run { mainBody {
if (headless) { ArgParser(args).parseInto(::CLIArgs).run {
if (headless) {
SpringApplication.run(CLIApp::class.java, *args) SpringApplication.run(CLIApp::class.java, *args)
} else { } else {
launch<EditorApp>(args) launch<EditorApp>(args)
} }
} }
}
} }

View File

@@ -5,8 +5,9 @@ import com.xenomachina.argparser.default
import java.io.File import java.io.File
class CLIArgs(parser: ArgParser) { class CLIArgs(parser: ArgParser) {
val headless by parser.flagging("-H", "--headless", help = "run editor in headless mode (without GUI)").default(false) val headless by parser.flagging("-H", "--headless", help = "run editor in headless mode (without GUI)")
val project by parser.storing("-p", "--project", help = "project file") { File(this) }.default(null) .default(false)
val build by parser.flagging("-b", "--build", help = "build project").default(false) val project by parser.storing("-p", "--project", help = "project file") { File(this) }.default(null)
val output by parser.storing("-o", "--output", help = "define build output folder") { File(this) }.default(null) val build by parser.flagging("-b", "--build", help = "build project").default(false)
val output by parser.storing("-o", "--output", help = "override build output path") { File(this) }.default(null)
} }

View File

@@ -0,0 +1,51 @@
package com.bartlomiejpluta.base.editor.cli.runner
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.project.context.ProjectContext
import com.bartlomiejpluta.base.editor.project.model.Project
import com.xenomachina.argparser.ArgParser
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import java.io.File
@Component
class CLIRunner(
@Autowired private val context: ProjectContext,
@Autowired private val pipelineService: BuildPipelineService
) {
fun run(vararg args: String) = ArgParser(args).parseInto(::CLIArgs).run {
handleOpenProject(project)
handleChangeOutput(output)
handleRunBuild(build)
}
private fun handleOpenProject(project: File?) = project?.let {
context.open(it)
}
private fun handleChangeOutput(output: File?) = output?.let {
context.project?.changeBuildDirectory(it)
}
private fun handleRunBuild(build: Boolean) {
if (!build) {
return
}
pipelineService.initStreams(System.out, System.err)
val startTime = System.currentTimeMillis()
try {
context.project?.let { project -> pipelineService.runPipeline(project) }
println("Build completed")
} catch (e: BuildException) {
System.err.println("Build failed")
} finally {
val buildingTime = (System.currentTimeMillis() - startTime) / 1000.0
println("Finished in [${buildingTime}s]")
}
}
}

View File

@@ -5,6 +5,19 @@ import java.io.PrintStream
import java.io.PrintWriter import java.io.PrintWriter
interface JarPackager { interface JarPackager {
fun pack(sourceDirectory: File, targetJar: File, root: String = ".", stdout: PrintStream = System.out, stderr: PrintStream = System.err) fun pack(
fun copy(file: File, targetJar: File, root: String = ".", stdout: PrintStream = System.out, stderr: PrintStream = System.err) sourceDirectory: File,
targetJar: File,
root: String = ".",
stdout: PrintStream = System.out,
stderr: PrintStream = System.err
)
fun copy(
file: File,
targetJar: File,
root: String = ".",
stdout: PrintStream = System.out,
stderr: PrintStream = System.err
)
} }

View File

@@ -19,92 +19,92 @@ import java.io.PrintStream
@Component @Component
open class BuildPipelineService { open class BuildPipelineService {
@Autowired @Autowired
private lateinit var dependenciesProvider: DependenciesProvider private lateinit var dependenciesProvider: DependenciesProvider
@Autowired @Autowired
private lateinit var generators: List<CodeGenerator> private lateinit var generators: List<CodeGenerator>
@Autowired @Autowired
private lateinit var compiler: Compiler private lateinit var compiler: Compiler
@Autowired @Autowired
private lateinit var packager: JarPackager private lateinit var packager: JarPackager
@Autowired @Autowired
private lateinit var engineProvider: GameEngineProvider private lateinit var engineProvider: GameEngineProvider
@Autowired @Autowired
private lateinit var assetSerializer: AssetSerializer private lateinit var assetSerializer: AssetSerializer
@Autowired @Autowired
private lateinit var projectAssembler: ProjectAssembler private lateinit var projectAssembler: ProjectAssembler
@Autowired @Autowired
private lateinit var databaseAssembler: DatabaseAssembler private lateinit var databaseAssembler: DatabaseAssembler
@Autowired @Autowired
private lateinit var projectContext: ProjectContext private lateinit var projectContext: ProjectContext
private lateinit var stdout: OutputStream private lateinit var stdout: OutputStream
private lateinit var stderr: OutputStream private lateinit var stderr: OutputStream
private var out: PrintStream = System.out private var out: PrintStream = System.out
private var err: PrintStream = System.err private var err: PrintStream = System.err
fun initStreams(stdout: OutputStream, stderr: OutputStream) { fun initStreams(stdout: OutputStream, stderr: OutputStream) {
this.stdout = stdout this.stdout = stdout
this.stderr = stderr this.stderr = stderr
this.out = PrintStream(stdout) this.out = PrintStream(stdout)
this.err = PrintStream(stderr) this.err = PrintStream(stderr)
} }
fun runPipeline(project: Project) { fun runPipeline(project: Project) {
prepareBuildDirectory(project) prepareBuildDirectory(project)
val outputFile = project.buildOutputJarFile val outputFile = project.buildOutputJarFile
out.println("Providing compile-time dependencies...") out.println("Providing compile-time dependencies...")
val dependencies = dependenciesProvider.provideDependenciesTo(project.buildDependenciesDirectory) val dependencies = dependenciesProvider.provideDependenciesTo(project.buildDependenciesDirectory)
out.println("Generating sources...") out.println("Generating sources...")
generators.forEach(CodeGenerator::generate) generators.forEach(CodeGenerator::generate)
out.println("Compiling sources...") out.println("Compiling sources...")
compiler.compile( compiler.compile(
arrayOf(project.codeFSNode, FileSystemNode(project.buildGeneratedCodeDirectory)), arrayOf(project.codeFSNode, FileSystemNode(project.buildGeneratedCodeDirectory)),
project.buildClassesDirectory, project.buildClassesDirectory,
dependencies.toTypedArray(), dependencies.toTypedArray(),
out, out,
err err
) )
out.println("Assembling game engine...") out.println("Assembling game engine...")
engineProvider.provideBaseGameEngine(outputFile, out, err) engineProvider.provideBaseGameEngine(outputFile, out, err)
out.println("Linking compilation units...") out.println("Linking compilation units...")
packager.pack(project.buildClassesDirectory, outputFile, "BOOT-INF/classes") packager.pack(project.buildClassesDirectory, outputFile, "BOOT-INF/classes")
out.println("Serializing project assets...") out.println("Serializing project assets...")
assetSerializer.serializeAssets(project) assetSerializer.serializeAssets(project)
out.println("Assembling project assets...") out.println("Assembling project assets...")
projectAssembler.assembly(project, outputFile, out, err) projectAssembler.assembly(project, outputFile, out, err)
out.println("Assembling database...") out.println("Assembling database...")
databaseAssembler.assembly(project, outputFile, out, err) databaseAssembler.assembly(project, outputFile, out, err)
} }
private fun prepareBuildDirectory(project: Project) { private fun prepareBuildDirectory(project: Project) {
project.buildDirectory.deleteRecursively() project.buildDirectory.deleteRecursively()
project.buildClassesDirectory.mkdirs() project.buildClassesDirectory.mkdirs()
project.buildOutDirectory.mkdirs() project.buildOutDirectory.mkdirs()
project.buildDependenciesDirectory.mkdirs() project.buildDependenciesDirectory.mkdirs()
project.buildGeneratedCodeDirectory.mkdirs() project.buildGeneratedCodeDirectory.mkdirs()
} }
fun clean() { fun clean() {
projectContext.project?.apply { buildDirectory.deleteRecursively() } projectContext.project?.apply { buildDirectory.deleteRecursively() }
out.println("Cleaning done") out.println("Cleaning done")
} }
} }

View File

@@ -26,6 +26,7 @@ class DefaultBuildService : BuildService {
@Autowired @Autowired
private lateinit var pipelineService: BuildPipelineService private lateinit var pipelineService: BuildPipelineService
@Autowired @Autowired
private lateinit var projectContext: ProjectContext private lateinit var projectContext: ProjectContext
@@ -38,7 +39,12 @@ class DefaultBuildService : BuildService {
private var err: PrintStream = System.err private var err: PrintStream = System.err
override fun initStreams(stdout: OutputStream, stderr: OutputStream) { override fun initStreams(stdout: OutputStream, stderr: OutputStream) {
pipelineService.initStreams(stdout, stderr) pipelineService.initStreams(stdout, stderr)
this.stdout = stdout
this.stderr = stderr
this.out = PrintStream(stdout)
this.err = PrintStream(stderr)
} }
override val isRunningProperty = false.toProperty() override val isRunningProperty = false.toProperty()

View File

@@ -47,7 +47,8 @@ class Project {
val widgets = observableListOf<WidgetAsset>() val widgets = observableListOf<WidgetAsset>()
val sounds = observableListOf<SoundAsset>() val sounds = observableListOf<SoundAsset>()
val assetLists = listOf(maps, tileSets, autoTiles, images, characterSets, animations, iconSets, fonts, widgets, sounds) val assetLists =
listOf(maps, tileSets, autoTiles, images, characterSets, animations, iconSets, fonts, widgets, sounds)
val mapsDirectoryProperty = SimpleObjectProperty<File>() val mapsDirectoryProperty = SimpleObjectProperty<File>()
var mapsDirectory by mapsDirectoryProperty var mapsDirectory by mapsDirectoryProperty
@@ -131,7 +132,8 @@ class Project {
var buildAssetsDir by buildAssetsDirProperty var buildAssetsDir by buildAssetsDirProperty
private set private set
val binaryProjectFileProperty = createObjectBinding({ File(buildAssetsDir, BINARY_PROJECT_FILE) }, buildAssetsDirProperty) val binaryProjectFileProperty =
createObjectBinding({ File(buildAssetsDir, BINARY_PROJECT_FILE) }, buildAssetsDirProperty)
val binaryProjectFile by binaryProjectFileProperty val binaryProjectFile by binaryProjectFileProperty
val buildAssetsMapsDirProperty = createObjectBinding({ File(buildAssetsDir, MAPS_DIR) }, buildAssetsDirProperty) val buildAssetsMapsDirProperty = createObjectBinding({ File(buildAssetsDir, MAPS_DIR) }, buildAssetsDirProperty)