167 lines
6.1 KiB
Kotlin
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()
|
|
}
|
|
}
|
|
|