Skip to content

Commit 2d28bc8

Browse files
committed
ALTERNATIVE: Spark/Scala build infra
Working for: * CI - without any change to CI * Local testing (just run `./gradlew check`) * Release / Maven publications - without any additional changes * BOM On top: * Chosing the right, compatible Java runtime version for Spark tests * Support for Scala * Support "non Spark" Scala projects
1 parent eae3141 commit 2d28bc8

File tree

21 files changed

+1234
-54
lines changed

21 files changed

+1234
-54
lines changed

build-logic/src/main/kotlin/polaris-java.gradle.kts

Lines changed: 65 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919

2020
import com.diffplug.spotless.FormatterFunc
21+
import gradle.kotlin.dsl.accessors._610c9322e0f6ac91715e46c62199c37f.spotless
2122
import java.io.Serializable
2223
import net.ltgt.gradle.errorprone.errorprone
2324
import org.gradle.api.tasks.compile.JavaCompile
@@ -39,20 +40,25 @@ apply<PublishingHelperPlugin>()
3940

4041
tasks.withType(JavaCompile::class.java).configureEach {
4142
options.compilerArgs.addAll(listOf("-Xlint:unchecked", "-Xlint:deprecation"))
42-
options.errorprone.disableAllWarnings = true
43-
options.errorprone.disableWarningsInGeneratedCode = true
44-
options.errorprone.excludedPaths =
45-
".*/${project.layout.buildDirectory.get().asFile.relativeTo(projectDir)}/generated/.*"
46-
options.errorprone.error(
47-
"DefaultCharset",
48-
"FallThrough",
49-
"MissingCasesInEnumSwitch",
50-
"MissingOverride",
51-
"ModifiedButNotUsed",
52-
"OrphanedFormatString",
53-
"PatternMatchingInstanceof",
54-
"StringCaseLocaleUsage",
55-
)
43+
44+
if (project.extra.has("reused-project-dir")) {
45+
options.errorprone.disableAllChecks = true
46+
} else {
47+
options.errorprone.disableAllWarnings = true
48+
options.errorprone.disableWarningsInGeneratedCode = true
49+
options.errorprone.excludedPaths =
50+
".*/${project.layout.buildDirectory.get().asFile.relativeTo(projectDir)}/generated/.*"
51+
options.errorprone.error(
52+
"DefaultCharset",
53+
"FallThrough",
54+
"MissingCasesInEnumSwitch",
55+
"MissingOverride",
56+
"ModifiedButNotUsed",
57+
"OrphanedFormatString",
58+
"PatternMatchingInstanceof",
59+
"StringCaseLocaleUsage",
60+
)
61+
}
5662
}
5763

5864
tasks.register("compileAll").configure {
@@ -150,38 +156,52 @@ tasks.withType(Jar::class).configureEach {
150156
}
151157
}
152158

153-
spotless {
154-
java {
155-
target("src/main/java/**/*.java", "src/testFixtures/java/**/*.java", "src/test/java/**/*.java")
156-
googleJavaFormat()
157-
licenseHeaderFile(rootProject.file("codestyle/copyright-header-java.txt"))
158-
endWithNewline()
159-
custom(
160-
"disallowWildcardImports",
161-
object : Serializable, FormatterFunc {
162-
override fun apply(text: String): String {
163-
val regex = "~/import .*\\.\\*;/".toRegex()
164-
if (regex.matches(text)) {
165-
throw GradleException("Wildcard imports disallowed - ${regex.findAll(text)}")
159+
if (!project.extra.has("reused-project-dir") && !gradle.ideSyncActive()) {
160+
spotless {
161+
java {
162+
target(
163+
"src/main/java/**/*.java",
164+
"src/testFixtures/java/**/*.java",
165+
"src/test/java/**/*.java",
166+
)
167+
googleJavaFormat()
168+
licenseHeaderFile(rootProject.file("codestyle/copyright-header-java.txt"))
169+
endWithNewline()
170+
custom(
171+
"disallowWildcardImports",
172+
object : Serializable, FormatterFunc {
173+
override fun apply(text: String): String {
174+
val regex = "~/import .*\\.\\*;/".toRegex()
175+
if (regex.matches(text)) {
176+
throw GradleException("Wildcard imports disallowed - ${regex.findAll(text)}")
177+
}
178+
return text
166179
}
167-
return text
168-
}
169-
},
170-
)
171-
toggleOffOn()
172-
}
173-
kotlinGradle {
174-
ktfmt().googleStyle()
175-
licenseHeaderFile(rootProject.file("codestyle/copyright-header-java.txt"), "$")
176-
target("*.gradle.kts")
177-
}
178-
format("xml") {
179-
target("src/**/*.xml", "src/**/*.xsd")
180-
targetExclude("codestyle/copyright-header.xml")
181-
eclipseWtp(com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep.XML)
182-
.configFile(rootProject.file("codestyle/org.eclipse.wst.xml.core.prefs"))
183-
// getting the license-header delimiter right is a bit tricky.
184-
// licenseHeaderFile(rootProject.file("codestyle/copyright-header.xml"), '<^[!?].*$')
180+
},
181+
)
182+
toggleOffOn()
183+
}
184+
scala {
185+
scalafmt()
186+
licenseHeaderFile(
187+
rootProject.file("codestyle/copyright-header-java.txt"),
188+
"^(package|import) .*$",
189+
)
190+
target("src/**/scala/**")
191+
}
192+
kotlinGradle {
193+
ktfmt().googleStyle()
194+
licenseHeaderFile(rootProject.file("codestyle/copyright-header-java.txt"), "$")
195+
target("*.gradle.kts")
196+
}
197+
format("xml") {
198+
target("src/**/*.xml", "src/**/*.xsd")
199+
targetExclude("codestyle/copyright-header.xml")
200+
eclipseWtp(com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep.XML)
201+
.configFile(rootProject.file("codestyle/org.eclipse.wst.xml.core.prefs"))
202+
// getting the license-header delimiter right is a bit tricky.
203+
// licenseHeaderFile(rootProject.file("codestyle/copyright-header.xml"), '<^[!?].*$')
204+
}
185205
}
186206
}
187207

build-logic/src/main/kotlin/polaris-root.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ spotless {
4141
}
4242
}
4343

44-
if (System.getProperty("idea.sync.active").toBoolean()) {
44+
if (gradle.ideSyncActive()) {
4545
idea {
4646
module {
4747
isDownloadJavadoc = false // was 'true', but didn't work
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
plugins {
21+
id("polaris-client")
22+
scala
23+
}
24+
25+
tasks.withType<ScalaCompile>().configureEach {
26+
options.release = 11
27+
scalaCompileOptions.additionalParameters.add("-release:11")
28+
sourceCompatibility = "11"
29+
targetCompatibility = "11"
30+
}
31+
32+
tasks.withType<ScalaCompile>().configureEach {
33+
scalaCompileOptions.keepAliveMode = KeepAliveMode.DAEMON
34+
scalaCompileOptions.encoding = "UTF-8"
35+
}
36+
37+
val scaladoc = tasks.named<ScalaDoc>("scaladoc")
38+
val scaladocJar = tasks.register<Jar>("scaladocJar")
39+
40+
scaladocJar.configure {
41+
dependsOn(scaladoc)
42+
val baseJar = tasks.getByName<Jar>("jar")
43+
from(scaladoc.get().destinationDir)
44+
destinationDirectory = baseJar.destinationDirectory
45+
archiveClassifier = "scaladoc"
46+
}
47+
48+
tasks.named("assemble").configure { dependsOn(scaladocJar) }
49+
50+
configure<PublishingExtension> {
51+
publications {
52+
withType(MavenPublication::class.java) {
53+
if (name == "maven") {
54+
artifact(scaladocJar)
55+
}
56+
}
57+
}
58+
}
59+
60+
val versions = sparkScalaVersionsForProject()
61+
62+
scala { scalaVersion.set(versions.scalaFullVersion) }
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import java.lang.IllegalArgumentException
21+
import org.gradle.api.Project
22+
import org.gradle.api.artifacts.Dependency
23+
import org.gradle.api.artifacts.ExternalModuleDependency
24+
import org.gradle.api.artifacts.ModuleDependency
25+
import org.gradle.api.tasks.testing.Test
26+
import org.gradle.kotlin.dsl.exclude
27+
28+
fun Project.prepareSparkScalaProject(): SparkScalaVersions {
29+
val versions = sparkScalaVersionsForProject()
30+
31+
forceJavaVersionForTests(versions.runtimeJavaVersion)
32+
addSparkJvmOptions()
33+
return versions
34+
}
35+
36+
fun Project.prepareScalaProject(): SparkScalaVersions {
37+
return sparkScalaVersionsForProject()
38+
}
39+
40+
/** Resolves the Spark and Scala major/minor versions from the Gradle project name. */
41+
fun Project.sparkScalaVersionsForProject(): SparkScalaVersions {
42+
val matchScala = ".*_(\\d+[.]\\d+)".toRegex().matchEntire(project.name)!!
43+
val matchSpark = ".*-(\\d+[.]\\d+)_\\d+[.]\\d+".toRegex().matchEntire(project.name)
44+
45+
val sparkMajorMinorVersion = if (matchSpark != null) matchSpark.groups[1]!!.value else ""
46+
val scalaMajorMinorVersion = matchScala.groups[1]!!.value
47+
48+
project.layout.buildDirectory.set(layout.buildDirectory.dir(scalaMajorMinorVersion).get())
49+
50+
return SparkScalaVersions(
51+
sparkMajorMinorVersion,
52+
scalaMajorMinorVersion,
53+
if (sparkMajorMinorVersion.isNotBlank())
54+
resolveFullSparkVersion(sparkMajorMinorVersion, scalaMajorMinorVersion)
55+
else "",
56+
resolveFullScalaVersion(scalaMajorMinorVersion),
57+
if (sparkMajorMinorVersion.isNotBlank()) javaVersionForSpark(sparkMajorMinorVersion) else 0,
58+
)
59+
}
60+
61+
/**
62+
* Apply the `sparkFullVersion` as a `strictly` version constraint and apply [withSparkExcludes] on
63+
* the current [Dependency].
64+
*/
65+
fun ModuleDependency.sparkDependencyAndExcludes(sparkScala: SparkScalaVersions): ModuleDependency {
66+
val dep = this as ExternalModuleDependency
67+
dep.version { strictly(sparkScala.sparkFullVersion) }
68+
return this.withSparkExcludes()
69+
}
70+
71+
/** Apply a bunch of common dependency-exclusion to the current Spark [Dependency]. */
72+
fun ModuleDependency.withSparkExcludes(): ModuleDependency {
73+
return this.exclude("commons-logging", "commons-logging")
74+
.exclude("log4j", "log4j")
75+
.exclude("org.slf4j", "slf4j-log4j12")
76+
.exclude("org.slf4j", "slf4j-reload4j")
77+
.exclude("org.eclipse.jetty", "jetty-util")
78+
.exclude("org.apache.avro", "avro")
79+
.exclude("org.apache.arrow", "arrow-vector")
80+
.exclude("org.apache.logging.log4j", "log4j-slf4j2-impl")
81+
}
82+
83+
/**
84+
* Resolve the full Spark version for the given major/minor Spark + Scala versions from the
85+
* `spark*-sql-scala*` dependency.
86+
*/
87+
fun Project.resolveFullSparkVersion(
88+
sparkMajorMinorVersion: String,
89+
scalaMajorMinorVersion: String,
90+
): String {
91+
val dotRegex = "[.]".toRegex()
92+
val sparkVerTrimmed = sparkMajorMinorVersion.replace(dotRegex, "")
93+
val scalaVerTrimmed = scalaMajorMinorVersion.replace(dotRegex, "")
94+
return requiredDependencyPreferredVersion("spark$sparkVerTrimmed-sql-scala$scalaVerTrimmed")
95+
}
96+
97+
/**
98+
* Resolve the full Scala version for the given major/minor Scala version from the
99+
* `scala*-lang-library` dependency.
100+
*/
101+
fun Project.resolveFullScalaVersion(scalaMajorMinorVersion: String): String {
102+
val dotRegex = "[.]".toRegex()
103+
val scalaVerTrimmed = scalaMajorMinorVersion.replace(dotRegex, "")
104+
return requiredDependencyPreferredVersion("scala$scalaVerTrimmed-lang-library")
105+
}
106+
107+
/**
108+
* Get the Java LTS version, that is lower than or equal to the currently running Java version, for
109+
* a given Spark version to be used in tests.
110+
*/
111+
fun javaVersionForSpark(sparkVersion: String): Int {
112+
return when (sparkVersion) {
113+
"3.4",
114+
"3.5" -> 21
115+
else ->
116+
throw IllegalArgumentException("Do not know which Java version Spark $sparkVersion supports")
117+
}
118+
}
119+
120+
class SparkScalaVersions(
121+
/** Spark major/minor version. */
122+
val sparkMajorMinorVersion: String,
123+
/** Scala major/minor version. */
124+
val scalaMajorMinorVersion: String,
125+
/** Full Spark version, including the patch version, for dependencies. */
126+
val sparkFullVersion: String,
127+
/** Full Scala version, including the patch version, for dependencies. */
128+
val scalaFullVersion: String,
129+
/** Java runtime version to be used in tests. */
130+
val runtimeJavaVersion: Int,
131+
)
132+
133+
/**
134+
* Adds the JPMS options required for Spark to run on Java 17, taken from the
135+
* `DEFAULT_MODULE_OPTIONS` constant in `org.apache.spark.launcher.JavaModuleOptions`.
136+
*/
137+
fun Project.addSparkJvmOptions() {
138+
tasks.withType(Test::class.java).configureEach {
139+
jvmArgs =
140+
jvmArgs +
141+
listOf(
142+
"-XX:+IgnoreUnrecognizedVMOptions",
143+
"--add-opens=java.base/java.lang=ALL-UNNAMED",
144+
"--add-opens=java.base/java.lang.invoke=ALL-UNNAMED",
145+
"--add-opens=java.base/java.lang.reflect=ALL-UNNAMED",
146+
"--add-opens=java.base/java.io=ALL-UNNAMED",
147+
"--add-opens=java.base/java.net=ALL-UNNAMED",
148+
"--add-opens=java.base/java.nio=ALL-UNNAMED",
149+
"--add-opens=java.base/java.util=ALL-UNNAMED",
150+
"--add-opens=java.base/java.util.concurrent=ALL-UNNAMED",
151+
"--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED",
152+
"--add-opens=java.base/sun.nio.ch=ALL-UNNAMED",
153+
"--add-opens=java.base/sun.nio.cs=ALL-UNNAMED",
154+
"--add-opens=java.base/sun.security.action=ALL-UNNAMED",
155+
"--add-opens=java.base/sun.util.calendar=ALL-UNNAMED",
156+
"--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED",
157+
"-Djdk.reflect.useDirectMethodHandle=false",
158+
// Required for Java 23+ if Hadoop stuff is used
159+
"-Djava.security.manager=allow",
160+
)
161+
}
162+
}

0 commit comments

Comments
 (0)