Files
CLI/src/main/kotlin/commands/ci/CiRunner.kt
Chaoscaot 83d52d7c62
All checks were successful
SteamWarCI Build successful
Test CI
2026-01-23 22:47:37 +01:00

167 lines
6.1 KiB
Kotlin

package de.steamwar.commands.ci
import java.io.File
import java.io.PrintStream
import java.nio.channels.FileChannel
import java.nio.file.Path
import java.nio.file.StandardOpenOption
class CiRunner(private val config: CiConfig) {
private var logFile: PrintStream? = null
fun run(logStream: PrintStream) {
this.logFile = logStream
try {
config.updateState("pending", "Waiting for previous builds")
// Use file lock to ensure exclusive CI access
FileChannel.open(
Path.of(System.getProperty("user.home"), ".cilock"),
StandardOpenOption.CREATE,
StandardOpenOption.READ,
StandardOpenOption.WRITE
).use { channel ->
val lock = channel.lock()
try {
ciMain()
} finally {
lock.release()
}
}
} catch (e: Exception) {
log("Error: ${e.message}")
e.printStackTrace(logStream)
config.updateState("failure", "Build failed")
throw e
}
}
private fun ciMain() {
config.updateState("pending", "Building project")
runCommand("pwd", "Could not get working directory", config.wdir)
val ciConfig = config.getCiConfig()
val freshClone = !File(config.wdir).exists()
if (freshClone) {
File(config.udir).mkdirs()
runCommand("git clone -n ${config.repopath} ${config.repo}", "Could not clone repository", config.udir)
} else {
runCommand("git fetch", "Could not fetch updates", config.wdir)
}
runCommand("git checkout ${config.commit}", "Could not checkout commit", config.wdir)
runCommand("git submodule update --init", "Could not init submodules", config.wdir)
if (freshClone && ciConfig.setup != null) {
for (cmd in ciConfig.setup) {
log("Setup: Executing $cmd")
runCommand(cmd, "Could not run setup command", config.wdir)
}
}
for (cmd in ciConfig.build) {
log("Build: Executing $cmd")
runCommand(cmd, "Could not run build command", config.wdir)
}
val onMaster = config.branch == "refs/heads/${config.getDefaultBranch()}"
val releaseBranchName = getReleaseBranchName()
val onReleaseBranch = releaseBranchName != null && ciConfig.releaseBranches
if (ciConfig.artifacts != null) {
log("Checking artifact existence")
for ((_, source) in ciConfig.artifacts) {
if (!File("${config.wdir}/$source").exists()) {
throw RuntimeException("Artifact $source missing")
}
}
if (onMaster) {
log("Deploying artifacts")
for ((target, source) in ciConfig.artifacts) {
log("Copying $source to $target")
val expandedTarget = expandPath(target)
runCommand("rm -f $expandedTarget", "Could not delete artifact", config.wdir)
runCommand("cp -r $source $expandedTarget", "Could not copy artifacts", config.wdir)
runCommand("chmod o-w $expandedTarget", "Could not change artifact perms", config.wdir)
}
} else if (onReleaseBranch) {
log("Deploying artifacts for release branch: $releaseBranchName")
for ((target, source) in ciConfig.artifacts) {
val expandedTarget = expandPath(target)
val targetFile = File(expandedTarget)
val parentDir = targetFile.parentFile?.absolutePath ?: expandedTarget.substringBeforeLast("/")
val fileName = targetFile.name
val releaseTargetDir = "$parentDir/$releaseBranchName"
val releaseTarget = "$releaseTargetDir/$fileName"
log("Copying $source to $releaseTarget")
runCommand("mkdir -p $releaseTargetDir", "Could not create release directory", config.wdir)
runCommand("rm -f $releaseTarget", "Could not delete artifact", config.wdir)
runCommand("cp -r $source $releaseTarget", "Could not copy artifacts", config.wdir)
runCommand("chmod o-w $releaseTarget", "Could not change artifact perms", config.wdir)
}
}
}
if (ciConfig.release != null && (onMaster || onReleaseBranch)) {
log("Running release commands" + if (onReleaseBranch) " for release branch: $releaseBranchName" else "")
for (cmd in ciConfig.release) {
log("Release: Executing $cmd")
runCommand(cmd, "Could not run release command", config.wdir)
}
}
config.updateState("success", "Build successful")
}
private fun runCommand(command: String, errorMessage: String, workingDir: String) {
log("Running: $command")
val processBuilder = ProcessBuilder("/bin/bash", "-c", command)
.directory(File(workingDir))
.redirectErrorStream(true)
val process = processBuilder.start()
// Read and log output
process.inputStream.bufferedReader().forEachLine { line ->
log(line)
}
val exitCode = process.waitFor()
if (exitCode != 0) {
log("Running in $workingDir")
throw RuntimeException("$errorMessage (exit code: $exitCode)")
}
}
private fun expandPath(path: String): String {
return if (path.startsWith("~")) {
path.replaceFirst("~", System.getProperty("user.home"))
} else {
path
}
}
private fun getReleaseBranchName(): String? {
val releaseBranchPrefix = "refs/heads/release/"
return if (config.branch.startsWith(releaseBranchPrefix)) {
config.branch.removePrefix(releaseBranchPrefix)
} else {
null
}
}
private fun log(message: String) {
println(message)
logFile?.println(message)
logFile?.flush()
}
}