Add CiCommand.kt, CiConfig.kt, and CiRunner.kt for CI daemon implementation
All checks were successful
SteamWarCI Build successful
All checks were successful
SteamWarCI Build successful
This commit is contained in:
162
src/main/kotlin/commands/ci/CiRunner.kt
Normal file
162
src/main/kotlin/commands/ci/CiRunner.kt
Normal file
@@ -0,0 +1,162 @@
|
||||
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")
|
||||
|
||||
val ciConfig = config.getCiConfig()
|
||||
val freshClone = !File(config.wdir).exists()
|
||||
|
||||
if (freshClone) {
|
||||
File(config.udir).mkdirs()
|
||||
runCommand("git clone -n ${config.repopath}", "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/sh", "-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) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user