Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add KtlintModule object #3961

Merged
merged 4 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ object Deps {
val koverCli = ivy"org.jetbrains.kotlinx:kover-cli:$koverVersion"
val koverJvmAgent = ivy"org.jetbrains.kotlinx:kover-jvm-agent:$koverVersion"
val ktfmt = ivy"com.facebook:ktfmt:0.52"
val ktlint = ivy"com.pinterest.ktlint:ktlint-core:0.49.1"
val sbtTestInterface = ivy"com.github.sbt:junit-interface:0.13.2"

def all = Seq(
Expand All @@ -222,6 +223,7 @@ object Deps {
koverCli,
koverJvmAgent,
ktfmt,
ktlint,
sbtTestInterface,
)
}
Expand Down
3 changes: 3 additions & 0 deletions example/kotlinlib/linting/2-ktlint/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ error: ...src/example/FooWrong.kt:6:28: Missing newline before ")" (standard:par
> ./mill ktlint --format true

> ./mill ktlint # after fixing the violations, ktlint no longer errors

> ./mill mill.kotlinlib.ktlint.KtlintModule/ # alternatively, use external module to check/format

*/
1 change: 1 addition & 0 deletions kotlinlib/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ object `package` extends RootModule with build.MillPublishScalaModule with Build
BuildInfo.Value("kotlinVersion", build.Deps.kotlinVersion, "Version of Kotlin"),
BuildInfo.Value("koverVersion", build.Deps.RuntimeDeps.koverVersion, "Version of Kover."),
BuildInfo.Value("ktfmtVersion", build.Deps.RuntimeDeps.ktfmt.version, "Version of Ktfmt."),
BuildInfo.Value("ktlintVersion", build.Deps.RuntimeDeps.ktlint.version, "Version of ktlint."),
BuildInfo.Value("detektVersion", build.Deps.RuntimeDeps.detektCli.version, "Version of Detekt."),
BuildInfo.Value("dokkaVersion", build.Deps.RuntimeDeps.dokkaVersion, "Version of Dokka."),
BuildInfo.Value("kotlinxHtmlJvmDep", Dep.unparse(build.Deps.RuntimeDeps.kotlinxHtmlJvm).get, "kotlinx-html-jvm dependency (used for Dokka)"),
Expand Down
127 changes: 88 additions & 39 deletions kotlinlib/src/mill/kotlinlib/ktlint/KtlintModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,21 @@ package mill.kotlinlib.ktlint

import mill._
import mill.api.{Loose, PathRef}
import mill.kotlinlib.{DepSyntax, KotlinModule}
import mill.define.{Discover, ExternalModule}
import mill.javalib.JavaModule
import mill.kotlinlib.DepSyntax
import mill.util.Jvm

/**
* Performs formatting checks on Kotlin source files using [[https://pinterest.github.io/ktlint/latest/install/integrations/ Ktlint]].
*/
trait KtlintModule extends KotlinModule {
trait KtlintModule extends JavaModule {

/**
* Runs [[https://pinterest.github.io/ktlint/latest/install/integrations/ Ktlint]]
*/
def ktlint(@mainargs.arg ktlintArgs: KtlintArgs): Command[Unit] = T.command {
val exitCode = ktlint0(ktlintArgs.format)()

ktlintHandleErrors(ktlintArgs.check, exitCode)
}

private def ktlint0(format: Boolean) = T.task {

T.log.info("running ktlint ...")

val configArgument = ktlintConfig() match {
case Some(path) => Seq("--editorconfig", path.path.toString())
case None => Seq.empty
}
val formatArgument = if (format) Seq("-F") else Seq.empty
val args = ktlintOptions() ++ configArgument ++ formatArgument

Jvm.callSubprocess(
mainClass = "com.pinterest.ktlint.Main",
classPath = ktlintClasspath().map(_.path),
mainArgs = args,
workingDir = millSourcePath, // allow passing relative paths for sources like src/a/b
streamOut = true,
check = false
).exitCode
}

private def ktlintHandleErrors(check: Boolean, exitCode: Int)(implicit ctx: mill.api.Ctx) = {

if (exitCode == 0) {} // do nothing
else {
if (check) {
throw new RuntimeException(s"ktlint exited abnormally with exit code = $exitCode")
} else {
T.log.error(s"ktlint exited abnormally with exit code = $exitCode")
}
}
def ktlint(@mainargs.arg ktlintArgs: KtlintArgs): Command[Unit] = Task.Command {
KtlintModule.ktlintAction(ktlintArgs, ktlintConfig(), ktlintOptions(), ktlintClasspath())
}

/**
Expand Down Expand Up @@ -82,3 +49,85 @@ trait KtlintModule extends KotlinModule {
Seq.empty[String]
}
}

object KtlintModule extends ExternalModule with KtlintModule with TaskModule {
override def defaultCommandName(): String = "reformatAll"

lazy val millDiscover: Discover = Discover[this.type]

/**
* Reformats Kotlin source files.
*
* @param check if an exception should be raised when formatting errors are found
* - when set, files are not formatted
*/
def reformatAll(
check: mainargs.Flag = mainargs.Flag(value = false)
): Command[Unit] = Task.Command {
ktlintAction(
KtlintArgs(format = true, check = check.value),
ktlintConfig(),
ktlintOptions(),
ktlintClasspath()
)
}

/**
* Checks the Kotlin source files formatting without reformatting the files.
*
* @param check if an exception should be raised when formatting errors are found
* - when set, files are not formatted
*/
def checkFormatAll(
check: mainargs.Flag = mainargs.Flag(value = false)
): Command[Unit] = Task.Command {
ktlintAction(
KtlintArgs(format = false, check = check.value),
ktlintConfig(),
ktlintOptions(),
ktlintClasspath()
)
}

private def ktlintAction(
ktlintArgs: KtlintArgs,
config: Option[PathRef],
options: Seq[String],
classPath: Loose.Agg[PathRef]
)(implicit ctx: api.Ctx): Unit = {
if (ktlintArgs.check) {
ctx.log.info("checking format in kotlin sources ...")
} else {
ctx.log.info("formatting kotlin sources ...")
}

val configArgument = config match {
case Some(path) => Seq("--editorconfig", path.path.toString())
case None => Seq.empty
}
val formatArgument = if (ktlintArgs.format) Seq("--format") else Seq.empty

val args = Seq.newBuilder[String]
args ++= options
args ++= configArgument
args ++= formatArgument

val exitCode = Jvm.callSubprocess(
mainClass = "com.pinterest.ktlint.Main",
classPath = classPath.map(_.path),
mainArgs = args.result(),
workingDir = millSourcePath, // allow passing relative paths for sources like src/a/b
streamOut = true,
check = false
).exitCode

if (exitCode == 0) {} // do nothing
else {
if (ktlintArgs.check) {
throw new RuntimeException(s"ktlint exited abnormally with exit code = $exitCode")
} else {
ctx.log.error(s"ktlint exited abnormally with exit code = $exitCode")
}
}
}
}
Loading