diff --git a/.github/workflows/cli_release.yml b/.github/workflows/cli_release.yml index 0d65bcc..d576281 100644 --- a/.github/workflows/cli_release.yml +++ b/.github/workflows/cli_release.yml @@ -17,8 +17,7 @@ jobs: with: java-version: '17' distribution: 'temurin' - cache: 'gradle' - - name: Setup and execute Gradle 'test' task + - name: Setup Gradle uses: gradle/gradle-build-action@v2 with: gradle-version: 8.3 @@ -28,4 +27,8 @@ jobs: - name: Release uses: softprops/action-gh-release@v1 with: + tag_name: dev files: cli/build/distributions/*.zip + +permissions: + contents: write diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..5c7305a --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,5 @@ +# Changes for gradle-kdiff plugin + +## 2023-10-10 / 0.1.0 + +- Initial release diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..e1543f7 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("com.lovelysystems.gradle") version ("1.12.0") +} + +lovely { + gitProject() +} + +subprojects { + version = rootProject.version +} diff --git a/cli/build.gradle.kts b/cli/build.gradle.kts new file mode 100644 index 0000000..32e3412 --- /dev/null +++ b/cli/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + application + kotlin("jvm") version "1.9.0" + id("com.github.johnrengelman.shadow") version "8.1.1" +} + +application { + applicationName = "kdiff" +} + +tasks.shadowDistZip { + archiveBaseName.set("kdiff") + archiveVersion.set(project.version.toString()) + archiveClassifier.set("") +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.github.ajalt.clikt:clikt:4.2.1") + implementation("io.github.java-diff-utils:java-diff-utils:4.12") +} + +application { + mainClass = "at.mibe.kdiff.ApplicationKt" +} diff --git a/cli/src/main/kotlin/at/mibe/kdiff/Application.kt b/cli/src/main/kotlin/at/mibe/kdiff/Application.kt new file mode 100644 index 0000000..6859340 --- /dev/null +++ b/cli/src/main/kotlin/at/mibe/kdiff/Application.kt @@ -0,0 +1,3 @@ +package at.mibe.kdiff + +fun main(args: Array) = KDiffCommand().main(args) diff --git a/cli/src/main/kotlin/at/mibe/kdiff/KDiffCommand.kt b/cli/src/main/kotlin/at/mibe/kdiff/KDiffCommand.kt new file mode 100644 index 0000000..e2e40e1 --- /dev/null +++ b/cli/src/main/kotlin/at/mibe/kdiff/KDiffCommand.kt @@ -0,0 +1,102 @@ +package at.mibe.kdiff + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.arguments.help +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.help +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.types.file +import com.github.ajalt.clikt.parameters.types.path +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.TimeUnit +import kotlin.io.path.Path +import kotlin.io.path.div +import kotlin.io.path.pathString + +class KDiffCommand : CliktCommand(name = "kdiff") { + + private val kpath: Path by argument("path").path(mustExist = true).help("The path to the Kustomization file") + private val remoteBranch: String by option("-b", "--branch").help("Remote branch to diff against").default("master") + private val remoteDirOverride: String by option("-r", "--remote-dir").help("Root directory of the remote branch to diff against") + .default("") + private val kustomize: File by option("--kustomize").file(mustExist = true).help("Path to the kustomize executable") + .default(File("kustomize")) + + override fun run() { + // clone the git repo + val remoteRepoDir = cloneGitRepo(gitOriginUrl(), Path("/tmp/kdiff")) + checkoutBranch(remoteRepoDir, remoteBranch) + + val remoteRepoExecDir = if (remoteDirOverride.isNotEmpty()) { + remoteRepoDir / remoteDirOverride + } else { + remoteRepoDir + } + + // run kustomize on the cloned remote branch + // suppress error output because we don't care about the exit code + val branch1Output = execCmd(kustomize.path, "build", (remoteRepoExecDir / kpath).pathString) { "" } + + // run kustomize on the local branch + // suppress error output because we don't care about the exit code + val branch2Output = execCmd(kustomize.path, "build", kpath.pathString) { "" } + + val diffs = findTextDifferences(branch1Output, branch2Output) + if (diffs.deltas.isNotEmpty()) { + val inlineDiff = generateInlineDiff(branch1Output, diffs) + println(inlineDiff) + } else { + println("No differences found.") + } + } + + private fun cloneGitRepo(originUrl: String, dest: Path): Path { + // Store target directory into a variable to avoid project reference in the configuration cache + val repoPath = dest / Path(originUrl) + Files.createDirectories(repoPath) + + execCmd("git", "clone", originUrl, ".", dir = repoPath.toFile()) { "Git clone failed" } + return repoPath + } + + private fun checkoutBranch(repoPath: Path, branch: String) { + execCmd("git", "checkout", branch, dir = repoPath.toFile()) + execCmd("git", "pull", "origin", branch, dir = repoPath.toFile()) + } + + private fun gitOriginUrl(): String = execCmd("git", "remote", "get-url", "origin") + + private fun execCmd( + vararg args: String, + dir: File? = null, + onError: ((String) -> String)? = null + ): String { + val cmd = args.toList() + val stdoutFile = kotlin.io.path.createTempFile().toFile() + val stderrFile = kotlin.io.path.createTempFile().toFile() + + try { + val proc = ProcessBuilder(cmd) + .directory(dir) + .redirectOutput(stdoutFile) + .redirectError(stderrFile) + .start() + proc.waitFor(10, TimeUnit.SECONDS) + val stdout = stdoutFile.readText().trim() + val stderr = stderrFile.readText().trim() + return if (proc.exitValue() == 0) { + stdout + } else { + onError?.invoke(stderr) ?: throw RuntimeException( + "command failed: ${cmd.joinToString(" ")}\n$stderr" + ) + } + } finally { + stderrFile.delete() + stdoutFile.delete() + } + } +} diff --git a/plugin/src/main/kotlin/at/mibe/gradle/kdiff/PrettyPrintDiff.kt b/cli/src/main/kotlin/at/mibe/kdiff/PrettyPrintDiff.kt similarity index 89% rename from plugin/src/main/kotlin/at/mibe/gradle/kdiff/PrettyPrintDiff.kt rename to cli/src/main/kotlin/at/mibe/kdiff/PrettyPrintDiff.kt index 8f29cd6..7532e24 100644 --- a/plugin/src/main/kotlin/at/mibe/gradle/kdiff/PrettyPrintDiff.kt +++ b/cli/src/main/kotlin/at/mibe/kdiff/PrettyPrintDiff.kt @@ -1,4 +1,4 @@ -package at.mibe.gradle.kdiff +package at.mibe.kdiff import com.github.difflib.DiffUtils import com.github.difflib.patch.Patch @@ -31,7 +31,8 @@ fun generateInlineDiff(text1: String, patch: Patch): String { // Process the inserted lines (green color) for (i in delta.target.position until delta.target.position + delta.target.size()) { - val insertedLine = "\u001B[32m+${delta.target.lines[i - delta.target.position]}\u001B[0m" // Green for insertions + val insertedLine = + "\u001B[32m+${delta.target.lines[i - delta.target.position]}\u001B[0m" // Green for insertions highlightedLines.add(insertedLine) } diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index c409ff7..016361d 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -3,7 +3,7 @@ plugins { `java-gradle-plugin` // Apply the Kotlin JVM plugin to add support for Kotlin. - id("org.jetbrains.kotlin.jvm") version "1.9.0" + kotlin("jvm") version "1.9.0" id("de.undercouch.download") version "5.5.0" } @@ -15,7 +15,6 @@ repositories { dependencies { implementation("de.undercouch:gradle-download-task:5.5.0") - implementation("io.github.java-diff-utils:java-diff-utils:4.12") // Use the Kotlin JUnit 5 integration. testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") diff --git a/plugin/src/main/kotlin/at/mibe/gradle/kdiff/GradleKDiffPlugin.kt b/plugin/src/main/kotlin/at/mibe/gradle/kdiff/GradleKDiffPlugin.kt index 3de11ad..2812a18 100644 --- a/plugin/src/main/kotlin/at/mibe/gradle/kdiff/GradleKDiffPlugin.kt +++ b/plugin/src/main/kotlin/at/mibe/gradle/kdiff/GradleKDiffPlugin.kt @@ -1,35 +1,80 @@ package at.mibe.gradle.kdiff import de.undercouch.gradle.tasks.download.Download -import org.gradle.api.Project import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.file.RelativePath +import org.gradle.api.tasks.Copy import org.gradle.api.tasks.Exec +@Suppress("unused") class GradleKDiffPlugin : Plugin { + + companion object { + const val KDIFF_GROUP = "kdiff" + } + override fun apply(project: Project) { + val kDiffLocation = project.layout.buildDirectory.dir("kdiff").get() + // Apply the third-party plugin project.apply { action -> action.plugin("de.undercouch.download") } project.tasks.register("kDiffVersion") { + it.group = KDIFF_GROUP + it.description = "Prints the kDiff version" + it.doLast { println("GradleKDiffPlugin version: ${project.version}") } } - project.tasks.register("downloadKustomize", Download::class.java) { + val downloadKustomize = project.tasks.register("downloadKustomize", Download::class.java) { + it.group = KDIFF_GROUP + it.description = "Download kustomize CLI" + it.src("https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh") - it.dest(project.layout.buildDirectory) + it.dest(kDiffLocation.dir("bin")) it.overwrite(false) } project.tasks.register("installKustomize", Exec::class.java) { - it.dependsOn("downloadKustomize") - it.workingDir(project.layout.buildDirectory) + it.group = KDIFF_GROUP + it.description = "Install kustomize CLI" + + it.onlyIf { !kDiffLocation.asFile.resolve("bin/kustomize").exists() } + it.dependsOn(downloadKustomize) + + it.workingDir(kDiffLocation.dir("bin")) + // only install kustomize if it is not already installed it.commandLine("bash", "install_kustomize.sh") } - project.tasks.register("kDiff", KDiffTask::class.java) + val downloadKDiff = project.tasks.register("downloadKDiff", Download::class.java) { + it.group = KDIFF_GROUP + it.description = "Download kDiff CLI" + + it.src("https://github.com/mikethebeer/gradle-kdiff/releases/download/${project.version}/kdiff-${project.version}.zip") + it.dest(project.layout.buildDirectory.file("kdiff.zip")) + it.overwrite(false) + } + + project.tasks.register("installKDiff", Copy::class.java) { + it.group = KDIFF_GROUP + it.description = "Install kDiff CLI" + + it.dependsOn(downloadKDiff) + + it.from(project.zipTree(downloadKDiff.get().dest)) { f -> + f.include("kdiff-*/**") + f.eachFile { cd -> + cd.relativePath = RelativePath(true, *cd.relativePath.segments.drop(1).toTypedArray()) + } + f.includeEmptyDirs = false + } + it.into(kDiffLocation) + } } } diff --git a/plugin/src/main/kotlin/at/mibe/gradle/kdiff/KDiffTask.kt b/plugin/src/main/kotlin/at/mibe/gradle/kdiff/KDiffTask.kt deleted file mode 100644 index 5db47c8..0000000 --- a/plugin/src/main/kotlin/at/mibe/gradle/kdiff/KDiffTask.kt +++ /dev/null @@ -1,93 +0,0 @@ -package at.mibe.gradle.kdiff - -import org.gradle.api.DefaultTask -import org.gradle.api.tasks.TaskAction -import java.io.File -import java.nio.file.Files -import java.util.concurrent.TimeUnit -import kotlin.io.path.Path -import kotlin.io.path.div - -abstract class KDiffTask : DefaultTask() { - - private val executable = project.layout.buildDirectory.file("kustomize").get().asFile.absolutePath ?: "kustomize" - private val diffBranch = project.properties["kbranch"] as? String ?: "master" - private val remoteDirOverride = project.properties["kremotedir"] as? String ?: "" - - @TaskAction - fun kDiff() { - val kpath = project.properties["kpath"] as? String ?: "." - val userDir = File(System.getProperty("user.dir")) - - // clone the git repo - val remoteRepoDir = cloneGitRepo(gitOriginUrl()) - checkoutBranch(remoteRepoDir, diffBranch) - - val remoteRepoExecDir = if (remoteDirOverride.isNotEmpty()) { - File(remoteRepoDir, remoteDirOverride) - } else { - remoteRepoDir - } - - val branch1Output = execCmd(remoteRepoExecDir, executable, "build", kpath) { "" } - val branch2Output = execCmd(userDir, executable, "build", kpath) { "" } - - val diffs = findTextDifferences(branch1Output, branch2Output) - if (diffs.deltas.isNotEmpty()) { - val inlineDiff = generateInlineDiff(branch1Output, diffs) - println(inlineDiff) - } else { - println("No differences found.") - } - } - - private fun cloneGitRepo(originUrl: String): File { - // Store target directory into a variable to avoid project reference in the configuration cache - val dir = project.layout.buildDirectory.asFile.get() - - val repoPath = dir.toPath() / Path(originUrl) - Files.createDirectories(repoPath) - - execCmd(repoPath.toFile(), "git", "clone", originUrl, ".") { "Git clone failed" } - return repoPath.toFile() - } - - private fun checkoutBranch(repoPath: File, branch: String) { - execCmd(repoPath, "git", "checkout", branch) - execCmd(repoPath, "git", "pull", "origin", branch) - } - - private fun gitOriginUrl(): String = execCmd(project.rootDir, "git", "remote", "get-url", "origin") - - private fun execCmd( - dir: File, - vararg args: String, - ignoreExit: Boolean = false, - onError: ((String) -> String)? = null - ): String { - val cmd = args.toList() - val stdoutFile = kotlin.io.path.createTempFile().toFile() - val stderrFile = kotlin.io.path.createTempFile().toFile() - - try { - val proc = ProcessBuilder(cmd) - .directory(dir) - .redirectOutput(stdoutFile) - .redirectError(stderrFile) - .start() - proc.waitFor(10, TimeUnit.SECONDS) - val stdout = stdoutFile.readText().trim() - val stderr = stderrFile.readText().trim() - return if (proc.exitValue() == 0 || ignoreExit) { - stdout - } else { - onError?.invoke(stderr) ?: throw RuntimeException( - "command failed: ${cmd.joinToString(" ")}\n$stderr" - ) - } - } finally { - stderrFile.delete() - stdoutFile.delete() - } - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index ce2a303..620d40c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,2 @@ rootProject.name = "gradle-kdiff" -include("plugin") +include("plugin", "cli")