diff --git a/PartnerCtl/src/main/java/work/slhaf/partner/ctl/commands/init/install.kt b/PartnerCtl/src/main/java/work/slhaf/partner/ctl/commands/init/install.kt index d1027364..02ff38f3 100644 --- a/PartnerCtl/src/main/java/work/slhaf/partner/ctl/commands/init/install.kt +++ b/PartnerCtl/src/main/java/work/slhaf/partner/ctl/commands/init/install.kt @@ -1,100 +1,42 @@ package work.slhaf.partner.ctl.commands.init -import work.slhaf.partner.ctl.support.CommandInterrupted -import work.slhaf.partner.ctl.support.inheritCommand -import work.slhaf.partner.ctl.support.runCommand +import work.slhaf.partner.ctl.support.SourceBuildInstallSpec +import work.slhaf.partner.ctl.support.buildAndInstallFromSource import work.slhaf.partner.ctl.ui.Prompt import java.nio.file.Files import java.nio.file.Path -import java.nio.file.StandardCopyOption +import java.nio.file.Paths import kotlin.io.path.isDirectory import kotlin.io.path.name +private const val PARTNER_REPO_URL = "https://gitea.slhaf.work/slhaf/Partner.git" + fun buildFromSource(home: Path, prompt: Prompt) { - checkTool() - - val tempDir = Files.createTempDirectory("partnerctl-build-") - val sourceDir = tempDir.resolve("Partner") - val repoUrl = "https://gitea.slhaf.work/slhaf/Partner.git" - val targetJar = home.resolve("resource").resolve("partner-core.jar") - - try { - prompt.info("Cloning Partner source from $repoUrl") - val cloneExitCode = inheritCommand( - command = listOf("git", "clone", "--depth", "1", repoUrl, sourceDir.toString()), - ) - 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() - } + buildAndInstallFromSource( + home = home, + prompt = prompt, + spec = SourceBuildInstallSpec( + displayName = "Partner runtime", + repoUrl = PARTNER_REPO_URL, + sourceDirName = "Partner", + buildCommand = listOf("mvn", "-pl", "Partner-Core", "-am", "package", "-DskipTests=true"), + artifactDirectory = Paths.get("Partner-Core", "target"), + artifactSelector = ::findLargestJar, + installRelativePath = Paths.get("resource", "partner-core.jar"), + ), + ) } -private fun checkTool() { +private fun findLargestJar(directory: Path): Path? { + if (!directory.isDirectory()) return null - fun buildToolLackedMessage(lack: Map): 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() - 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 -> + return Files.list(directory).use { stream -> stream .filter { it.name.endsWith(".jar") } .filter { !it.name.startsWith("original-") } .filter { !it.name.endsWith("-sources.jar") } .filter { !it.name.endsWith("-javadoc.jar") } .max(Comparator.comparingLong { Files.size(it) }) - .orElseThrow { CommandInterrupted("Could not find built Partner Core jar in $targetDir") } + .orElse(null) } } - diff --git a/PartnerCtl/src/main/java/work/slhaf/partner/ctl/support/source_build.kt b/PartnerCtl/src/main/java/work/slhaf/partner/ctl/support/source_build.kt new file mode 100644 index 00000000..3dd161bf --- /dev/null +++ b/PartnerCtl/src/main/java/work/slhaf/partner/ctl/support/source_build.kt @@ -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, + 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 { + 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() + 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)) + } +} +