[Editor] Improve CLI handling

This commit is contained in:
2025-07-11 12:11:36 +02:00
parent c21570f912
commit efb137f2a1
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 = "define build output folder") { 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) {
prepareBuildDirectory(project)
val outputFile = project.buildOutputJarFile fun runPipeline(project: Project) {
prepareBuildDirectory(project)
out.println("Providing compile-time dependencies...") val outputFile = project.buildOutputJarFile
val dependencies = dependenciesProvider.provideDependenciesTo(project.buildDependenciesDirectory)
out.println("Generating sources...") out.println("Providing compile-time dependencies...")
generators.forEach(CodeGenerator::generate) val dependencies = dependenciesProvider.provideDependenciesTo(project.buildDependenciesDirectory)
out.println("Compiling sources...") out.println("Generating sources...")
compiler.compile( generators.forEach(CodeGenerator::generate)
arrayOf(project.codeFSNode, FileSystemNode(project.buildGeneratedCodeDirectory)),
project.buildClassesDirectory,
dependencies.toTypedArray(),
out,
err
)
out.println("Assembling game engine...") out.println("Compiling sources...")
engineProvider.provideBaseGameEngine(outputFile, out, err) compiler.compile(
arrayOf(project.codeFSNode, FileSystemNode(project.buildGeneratedCodeDirectory)),
project.buildClassesDirectory,
dependencies.toTypedArray(),
out,
err
)
out.println("Linking compilation units...") out.println("Assembling game engine...")
packager.pack(project.buildClassesDirectory, outputFile, "BOOT-INF/classes") engineProvider.provideBaseGameEngine(outputFile, out, err)
out.println("Serializing project assets...") out.println("Linking compilation units...")
assetSerializer.serializeAssets(project) packager.pack(project.buildClassesDirectory, outputFile, "BOOT-INF/classes")
out.println("Assembling project assets...") out.println("Serializing project assets...")
projectAssembler.assembly(project, outputFile, out, err) assetSerializer.serializeAssets(project)
out.println("Assembling database...") out.println("Assembling project assets...")
databaseAssembler.assembly(project, outputFile, out, err) projectAssembler.assembly(project, outputFile, out, err)
}
private fun prepareBuildDirectory(project: Project) { out.println("Assembling database...")
project.buildDirectory.deleteRecursively() databaseAssembler.assembly(project, outputFile, out, err)
}
project.buildClassesDirectory.mkdirs() private fun prepareBuildDirectory(project: Project) {
project.buildOutDirectory.mkdirs() project.buildDirectory.deleteRecursively()
project.buildDependenciesDirectory.mkdirs()
project.buildGeneratedCodeDirectory.mkdirs()
}
fun clean() { project.buildClassesDirectory.mkdirs()
projectContext.project?.apply { buildDirectory.deleteRecursively() } project.buildOutDirectory.mkdirs()
out.println("Cleaning done") project.buildDependenciesDirectory.mkdirs()
} project.buildGeneratedCodeDirectory.mkdirs()
}
fun clean() {
projectContext.project?.apply { buildDirectory.deleteRecursively() }
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

@@ -26,7 +26,7 @@ class DefaultProjectAssembler : ProjectAssembler {
private fun tryToAssembly(project: Project, targetJar: File, stdout: PrintStream, stderr: PrintStream) { private fun tryToAssembly(project: Project, targetJar: File, stdout: PrintStream, stderr: PrintStream) {
stdout.println("[$TAG] Assembling project") stdout.println("[$TAG] Assembling project")
packager.copy(project.binaryProjectFile, targetJar, "BOOT-INF/classes/project", stdout, stderr) packager.copy(project.binaryProjectFile, targetJar, "BOOT-INF/classes/project", stdout, stderr)
stdout.println("[$TAG] Assembling maps") stdout.println("[$TAG] Assembling maps")
packager.pack(project.buildAssetsMapsDir, targetJar, "BOOT-INF/classes/project/maps", stdout, stderr) packager.pack(project.buildAssetsMapsDir, targetJar, "BOOT-INF/classes/project/maps", stdout, stderr)

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)