From 922961fef9d70fbf4873c54211e561c0fbafefe9 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Wed, 19 Mar 2025 11:06:48 +0100 Subject: [PATCH] Scala build infra Adds build-infra related work for Scala projects, like #1208 --- .../src/main/kotlin/polaris-java.gradle.kts | 108 ++++++++++-------- .../src/main/kotlin/polaris-root.gradle.kts | 2 +- .../src/main/kotlin/polaris-scala.gradle.kts | 66 +++++++++++ build-logic/src/main/kotlin/util.kt | 42 +++++++ codestyle/scalafmt.conf | 42 +++++++ gradle/libs.versions.toml | 1 + 6 files changed, 214 insertions(+), 47 deletions(-) create mode 100644 build-logic/src/main/kotlin/polaris-scala.gradle.kts create mode 100644 build-logic/src/main/kotlin/util.kt create mode 100644 codestyle/scalafmt.conf diff --git a/build-logic/src/main/kotlin/polaris-java.gradle.kts b/build-logic/src/main/kotlin/polaris-java.gradle.kts index e3b176707..dda284419 100644 --- a/build-logic/src/main/kotlin/polaris-java.gradle.kts +++ b/build-logic/src/main/kotlin/polaris-java.gradle.kts @@ -39,20 +39,25 @@ apply() tasks.withType(JavaCompile::class.java).configureEach { options.compilerArgs.addAll(listOf("-Xlint:unchecked", "-Xlint:deprecation")) - options.errorprone.disableAllWarnings = true - options.errorprone.disableWarningsInGeneratedCode = true - options.errorprone.excludedPaths = - ".*/${project.layout.buildDirectory.get().asFile.relativeTo(projectDir)}/generated/.*" - options.errorprone.error( - "DefaultCharset", - "FallThrough", - "MissingCasesInEnumSwitch", - "MissingOverride", - "ModifiedButNotUsed", - "OrphanedFormatString", - "PatternMatchingInstanceof", - "StringCaseLocaleUsage", - ) + + if (project.extra.has("reused-project-dir")) { + options.errorprone.disableAllChecks = true + } else { + options.errorprone.disableAllWarnings = true + options.errorprone.disableWarningsInGeneratedCode = true + options.errorprone.excludedPaths = + ".*/${project.layout.buildDirectory.get().asFile.relativeTo(projectDir)}/generated/.*" + options.errorprone.error( + "DefaultCharset", + "FallThrough", + "MissingCasesInEnumSwitch", + "MissingOverride", + "ModifiedButNotUsed", + "OrphanedFormatString", + "PatternMatchingInstanceof", + "StringCaseLocaleUsage", + ) + } } tasks.register("compileAll").configure { @@ -150,42 +155,53 @@ tasks.withType(Jar::class).configureEach { } } -spotless { - java { - target("src/*/java/**/*.java") - googleJavaFormat() - licenseHeaderFile(rootProject.file("codestyle/copyright-header-java.txt")) - endWithNewline() - custom( - "disallowWildcardImports", - object : Serializable, FormatterFunc { - override fun apply(text: String): String { - val regex = "~/import .*\\.\\*;/".toRegex() - if (regex.matches(text)) { - throw GradleException("Wildcard imports disallowed - ${regex.findAll(text)}") +if (!project.extra.has("reused-project-dir") && !gradle.ideSyncActive()) { + spotless { + java { + target("src/*/java/**/*.java") + googleJavaFormat() + licenseHeaderFile(rootProject.file("codestyle/copyright-header-java.txt")) + endWithNewline() + custom( + "disallowWildcardImports", + object : Serializable, FormatterFunc { + override fun apply(text: String): String { + val regex = "~/import .*\\.\\*;/".toRegex() + if (regex.matches(text)) { + throw GradleException("Wildcard imports disallowed - ${regex.findAll(text)}") + } + return text } - return text - } - }, - ) - toggleOffOn() - } - kotlinGradle { - ktfmt().googleStyle() - licenseHeaderFile(rootProject.file("codestyle/copyright-header-java.txt"), "$") - target("*.gradle.kts") - } - format("xml") { - target("src/**/*.xml", "src/**/*.xsd") - targetExclude("codestyle/copyright-header.xml") - eclipseWtp(com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep.XML) - .configFile(rootProject.file("codestyle/org.eclipse.wst.xml.core.prefs")) - // getting the license-header delimiter right is a bit tricky. - // licenseHeaderFile(rootProject.file("codestyle/copyright-header.xml"), '<^[!?].*$') + }, + ) + toggleOffOn() + } + scala { + scalafmt(requiredDependencyVersion("scalafmt")) + .configFile(rootProject.file("codestyle/scalafmt.conf").toString()) + licenseHeaderFile( + rootProject.file("codestyle/copyright-header-java.txt"), + "^(package|import) .*$", + ) + target("src/**/scala/**/*.scala") + } + kotlinGradle { + ktfmt().googleStyle() + licenseHeaderFile(rootProject.file("codestyle/copyright-header-java.txt"), "$") + target("*.gradle.kts") + } + format("xml") { + target("src/**/*.xml", "src/**/*.xsd") + targetExclude("codestyle/copyright-header.xml") + eclipseWtp(com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep.XML) + .configFile(rootProject.file("codestyle/org.eclipse.wst.xml.core.prefs")) + // getting the license-header delimiter right is a bit tricky. + // licenseHeaderFile(rootProject.file("codestyle/copyright-header.xml"), '<^[!?].*$') + } } } -dependencies { errorprone(versionCatalogs.named("libs").findLibrary("errorprone").get()) } +dependencies { errorprone(requiredDependency("errorprone")) } java { withJavadocJar() diff --git a/build-logic/src/main/kotlin/polaris-root.gradle.kts b/build-logic/src/main/kotlin/polaris-root.gradle.kts index 96faa07b8..58fabdd95 100644 --- a/build-logic/src/main/kotlin/polaris-root.gradle.kts +++ b/build-logic/src/main/kotlin/polaris-root.gradle.kts @@ -41,7 +41,7 @@ spotless { } } -if (System.getProperty("idea.sync.active").toBoolean()) { +if (gradle.ideSyncActive()) { idea { module { isDownloadJavadoc = false // was 'true', but didn't work diff --git a/build-logic/src/main/kotlin/polaris-scala.gradle.kts b/build-logic/src/main/kotlin/polaris-scala.gradle.kts new file mode 100644 index 000000000..f69cd4b0e --- /dev/null +++ b/build-logic/src/main/kotlin/polaris-scala.gradle.kts @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.scala.ScalaCompile +import org.gradle.api.tasks.scala.ScalaDoc +import org.gradle.kotlin.dsl.* +import org.gradle.language.scala.tasks.KeepAliveMode + +plugins { + id("polaris-server") + scala +} + +tasks.withType().configureEach { + options.release = 21 + scalaCompileOptions.additionalParameters.add("-release:21") + sourceCompatibility = "21" + targetCompatibility = "21" +} + +tasks.withType().configureEach { + scalaCompileOptions.keepAliveMode = KeepAliveMode.DAEMON + scalaCompileOptions.encoding = "UTF-8" +} + +val scaladoc = tasks.named("scaladoc") +val scaladocJar = tasks.register("scaladocJar") + +scaladocJar.configure { + dependsOn(scaladoc) + val baseJar = tasks.getByName("jar") + from(scaladoc.get().destinationDir) + destinationDirectory = baseJar.destinationDirectory + archiveClassifier = "scaladoc" +} + +tasks.named("assemble").configure { dependsOn(scaladocJar) } + +configure { + publications { + withType(MavenPublication::class.java) { + if (name == "maven") { + artifact(scaladocJar) + } + } + } +} diff --git a/build-logic/src/main/kotlin/util.kt b/build-logic/src/main/kotlin/util.kt new file mode 100644 index 000000000..354b2da6c --- /dev/null +++ b/build-logic/src/main/kotlin/util.kt @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.lang.IllegalStateException +import org.gradle.api.Project +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.api.invocation.Gradle +import org.gradle.kotlin.dsl.getByType + +fun Gradle.ideSyncActive(): Boolean = + System.getProperty("idea.sync.active").toBoolean() || + System.getProperty("eclipse.product") != null || + startParameter.taskNames.any { it.startsWith("eclipse") } + +fun Project.requiredDependencyVersion(dependencyName: String): String = + requiredDependency(dependencyName).version!! + +fun Project.requiredDependency(dependencyName: String): MinimalExternalModuleDependency { + val versionCatalog = extensions.getByType().named("libs") + val dependency = + versionCatalog.findLibrary(dependencyName).orElseThrow { + IllegalStateException("No library '$dependencyName' defined in version catalog 'libs'") + } + return dependency.get() +} diff --git a/codestyle/scalafmt.conf b/codestyle/scalafmt.conf new file mode 100644 index 000000000..d02567b9a --- /dev/null +++ b/codestyle/scalafmt.conf @@ -0,0 +1,42 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +version = 3.9.4 +runner.dialect = scala213 + +maxColumn = 100 + +preset = default +align.preset = some + +assumeStandardLibraryStripMargin = true +align.stripMargin = true + +rewrite.rules = [ + AvoidInfix + RedundantBraces + RedundantParens + SortModifiers + PreferCurlyFors + Imports +] + +rewrite.imports.sort = original +docstrings.style = Asterisk +docstrings.wrap = fold diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4e569e27d..6ac392f5e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -84,6 +84,7 @@ prometheus-metrics-exporter-servlet-jakarta = { module = "io.prometheus:promethe quarkus-bom = { module = "io.quarkus.platform:quarkus-bom", version.ref = "quarkus" } scala212-lang-library = { module = "org.scala-lang:scala-library", version.ref = "scala212" } scala212-lang-reflect = { module = "org.scala-lang:scala-reflect", version.ref = "scala212" } +scalafmt = { module = "org.scalameta:scalafmt-core_2.13", version = "3.9.4" } s3mock-testcontainers = { module = "com.adobe.testing:s3mock-testcontainers", version = "3.12.0" } slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } smallrye-common-annotation = { module = "io.smallrye.common:smallrye-common-annotation", version = "2.10.0" }