refactor(partnerctl-init): extract source build/install flow into reusable support helper

This commit is contained in:
2026-05-03 21:50:25 +08:00
parent 6c74de2126
commit adab925e7d
2 changed files with 117 additions and 80 deletions

View File

@@ -1,100 +1,42 @@
package work.slhaf.partner.ctl.commands.init package work.slhaf.partner.ctl.commands.init
import work.slhaf.partner.ctl.support.CommandInterrupted import work.slhaf.partner.ctl.support.SourceBuildInstallSpec
import work.slhaf.partner.ctl.support.inheritCommand import work.slhaf.partner.ctl.support.buildAndInstallFromSource
import work.slhaf.partner.ctl.support.runCommand
import work.slhaf.partner.ctl.ui.Prompt import work.slhaf.partner.ctl.ui.Prompt
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.StandardCopyOption import java.nio.file.Paths
import kotlin.io.path.isDirectory import kotlin.io.path.isDirectory
import kotlin.io.path.name import kotlin.io.path.name
private const val PARTNER_REPO_URL = "https://gitea.slhaf.work/slhaf/Partner.git"
fun buildFromSource(home: Path, prompt: Prompt) { fun buildFromSource(home: Path, prompt: Prompt) {
checkTool() buildAndInstallFromSource(
home = home,
val tempDir = Files.createTempDirectory("partnerctl-build-") prompt = prompt,
val sourceDir = tempDir.resolve("Partner") spec = SourceBuildInstallSpec(
val repoUrl = "https://gitea.slhaf.work/slhaf/Partner.git" displayName = "Partner runtime",
val targetJar = home.resolve("resource").resolve("partner-core.jar") repoUrl = PARTNER_REPO_URL,
sourceDirName = "Partner",
try { buildCommand = listOf("mvn", "-pl", "Partner-Core", "-am", "package", "-DskipTests=true"),
prompt.info("Cloning Partner source from $repoUrl") artifactDirectory = Paths.get("Partner-Core", "target"),
val cloneExitCode = inheritCommand( artifactSelector = ::findLargestJar,
command = listOf("git", "clone", "--depth", "1", repoUrl, sourceDir.toString()), installRelativePath = Paths.get("resource", "partner-core.jar"),
),
) )
if (cloneExitCode != 0) {
throw CommandInterrupted("Failed to clone Partner source from $repoUrl")
}
prompt.info("Building Partner-Core and required modules")
val buildExitCode = inheritCommand(
command = listOf("mvn", "-pl", "Partner-Core", "-am", "package", "-DskipTests=true"),
workingDirectory = sourceDir,
)
if (buildExitCode != 0) {
throw CommandInterrupted("Failed to build Partner runtime. Please make sure Maven is using JDK 21.")
}
val builtJar = findPartnerCoreJar(sourceDir)
Files.createDirectories(targetJar.parent)
Files.copy(builtJar, targetJar, StandardCopyOption.REPLACE_EXISTING)
prompt.success("Partner runtime installed at $targetJar")
} finally {
tempDir.toFile().deleteRecursively()
}
} }
private fun checkTool() { private fun findLargestJar(directory: Path): Path? {
if (!directory.isDirectory()) return null
fun buildToolLackedMessage(lack: Map<String, String>): String { return Files.list(directory).use { stream ->
return buildString {
appendLine("Missing required build tools:")
appendLine()
lack.forEach { (tool, reason) ->
appendLine("$tool:")
appendLine(" $reason")
}
appendLine()
appendLine("Install the missing tools, then rerun:")
appendLine(" partnerctl init")
}.trimEnd()
}
val lack = mutableMapOf<String, String>()
if (runCommand(listOf("java", "--version")).exitCode != 0) {
lack["java"] = "Required to run Maven and Partner runtime. Command failed: java --version"
}
if (runCommand(listOf("javac", "--version")).exitCode != 0) {
lack["javac"] =
"Required to compile Partner from source. Install a JDK, not just a JRE. Command failed: javac --version"
}
if (runCommand(listOf("git", "--version")).exitCode != 0) {
lack["git"] = "Required to clone Partner source. Command failed: git --version"
}
if (runCommand(listOf("mvn", "--version")).exitCode != 0) {
lack["mvn"] = "Required to build Partner from source. Command failed: mvn --version"
}
if (lack.isNotEmpty()) {
throw CommandInterrupted(buildToolLackedMessage(lack))
}
}
private fun findPartnerCoreJar(sourceDir: Path): Path {
val targetDir = sourceDir.resolve("Partner-Core").resolve("target")
if (!targetDir.isDirectory()) {
throw CommandInterrupted("Partner-Core target directory does not exist: $targetDir")
}
return Files.list(targetDir).use { stream ->
stream stream
.filter { it.name.endsWith(".jar") } .filter { it.name.endsWith(".jar") }
.filter { !it.name.startsWith("original-") } .filter { !it.name.startsWith("original-") }
.filter { !it.name.endsWith("-sources.jar") } .filter { !it.name.endsWith("-sources.jar") }
.filter { !it.name.endsWith("-javadoc.jar") } .filter { !it.name.endsWith("-javadoc.jar") }
.max(Comparator.comparingLong { Files.size(it) }) .max(Comparator.comparingLong { Files.size(it) })
.orElseThrow { CommandInterrupted("Could not find built Partner Core jar in $targetDir") } .orElse(null)
} }
} }

View File

@@ -0,0 +1,95 @@
package work.slhaf.partner.ctl.support
import work.slhaf.partner.ctl.ui.Prompt
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
data class SourceBuildInstallSpec(
val displayName: String,
val repoUrl: String,
val sourceDirName: String,
val buildCommand: List<String>,
val artifactDirectory: Path,
val artifactSelector: (Path) -> Path?,
val installRelativePath: Path,
)
fun buildAndInstallFromSource(
home: Path,
prompt: Prompt,
spec: SourceBuildInstallSpec,
) {
require(spec.sourceDirName.isNotBlank()) { "sourceDirName must not be blank" }
require(spec.buildCommand.isNotEmpty()) { "buildCommand must not be empty" }
checkTool()
val tempDir = Files.createTempDirectory("partnerctl-build-")
val sourceDir = tempDir.resolve(spec.sourceDirName)
val targetPath = home.resolve(spec.installRelativePath)
try {
prompt.info("Cloning ${spec.displayName} source from ${spec.repoUrl}")
val cloneExitCode = inheritCommand(
command = listOf("git", "clone", "--depth", "1", spec.repoUrl, sourceDir.toString()),
)
if (cloneExitCode != 0) {
throw CommandInterrupted("Failed to clone ${spec.displayName} source from ${spec.repoUrl}")
}
prompt.info("Building ${spec.displayName}")
val buildExitCode = inheritCommand(
command = spec.buildCommand,
workingDirectory = sourceDir,
)
if (buildExitCode != 0) {
throw CommandInterrupted("Failed to build ${spec.displayName}.")
}
val artifactDir = sourceDir.resolve(spec.artifactDirectory)
val artifact = spec.artifactSelector(artifactDir)
?: throw CommandInterrupted("Could not find built ${spec.displayName} artifact in $artifactDir")
Files.createDirectories(targetPath.parent)
Files.copy(artifact, targetPath, StandardCopyOption.REPLACE_EXISTING)
prompt.success("${spec.displayName} installed at $targetPath")
} finally {
runCatching { tempDir.toFile().deleteRecursively() }
}
}
private fun checkTool() {
fun buildToolLackedMessage(lack: Map<String, String>): String {
return buildString {
appendLine("Missing required build tools:")
appendLine()
lack.forEach { (tool, reason) ->
appendLine("$tool:")
appendLine(" $reason")
}
appendLine()
appendLine("Install the missing tools, then rerun:")
appendLine(" partnerctl init")
}.trimEnd()
}
val lack = mutableMapOf<String, String>()
if (runCommand(listOf("java", "--version")).exitCode != 0) {
lack["java"] = "Required to run Maven. Command failed: java --version"
}
if (runCommand(listOf("javac", "--version")).exitCode != 0) {
lack["javac"] =
"Required to compile Partner from source. Install a JDK, not just a JRE. Command failed: javac --version"
}
if (runCommand(listOf("git", "--version")).exitCode != 0) {
lack["git"] = "Required to clone Partner source. Command failed: git --version"
}
if (runCommand(listOf("mvn", "--version")).exitCode != 0) {
lack["mvn"] = "Required to build Partner from source. Command failed: mvn --version"
}
if (lack.isNotEmpty()) {
throw CommandInterrupted(buildToolLackedMessage(lack))
}
}