Skip to content

Commit

Permalink
Merge pull request #23926 Update Scala target computation
Browse files Browse the repository at this point in the history
Only use release when an explicit toolchain is configured.

Fixes #23905

Co-authored-by: Louis Jacomet <louis@gradle.com>
  • Loading branch information
bot-gradle and ljacomet committed Feb 17, 2023
2 parents fa74f94 + b52aab7 commit 0fed2c8
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 8 deletions.
4 changes: 3 additions & 1 deletion subprojects/docs/src/docs/release/notes.md
Expand Up @@ -3,7 +3,9 @@ The Gradle team is excited to announce Gradle @version@.
This is the first patch release for Gradle 8.0.

It fixes the following issues:
* TODO
* [#21551](https://github.com/gradle/gradle/issues/21551) Document integration of Scala plugin with toolchains and problems with `target` flag
* [#23888](https://github.com/gradle/gradle/issues/23888) `--no-rebuild` suddenly gone without new deprecation cycle and without the reason for its undeprecation being void
* [#23905](https://github.com/gradle/gradle/issues/23905) Gradle 8.0 fails Scala build with isBlank not found in String class error

We recommend users upgrade to @version@ instead of 8.0.

Expand Down
31 changes: 31 additions & 0 deletions subprojects/docs/src/docs/userguide/jvm/scala_plugin.adoc
Expand Up @@ -228,6 +228,37 @@ The Scala plugin also modifies some source set properties:
| `allSource` | Adds all source files found in the Scala source directories.
|===

[[sec:scala_target]]
== Target bytecode level and Java APIs version

When running the Scala compile task, Gradle will always add a parameter to configure the Java target for the Scala compiler that is derived from the Gradle configuration:

* When using toolchains, the `-release` option, or `target` for older Scala versions, is selected, with a version matching the Java language level of the toolchain configured.
* When not using toolchains, Gradle will always pass a `target` flag -- with exact value dependent on the Scala version -- to compile to Java 8 bytecode.

[NOTE]
====
This means that using toolchains with a recent Java version and an old Scala version can result in failures because Scala only supported Java 8 bytecode for some time.
The solution is then to either use the right Java version in the toolchain or explicitly downgrade the target when needed.
====

The following table explains the values computed by Gradle:

.Scala target parameter based on project configuration
[%header%autowidth,compact]
|===
| Scala version | Toolchain in use | Parameter value
.2+| version < `2.13.1` | yes | `-target:jvm-1.<java_version>`
| no | `-target:jvm-1.8`
.2+| `2.13.1` \<= version < `2.13.9` | yes | `-target:<java_version>`
| no | `-target:8`
.2+| `2.13.9` \<= version < `3.0` | yes | `-release:<java_version>`
| no | `-target:8`
.2+| `3.0` \<= version | yes | `-release:<java_version>`
| no | `-Xtarget:8`
|===

Setting any of these flags explicitly, or using flags containing `java-output-version`, on link:{groovyDslPath}/org.gradle.language.scala.tasks.BaseScalaCompileOptions.html#org.gradle.language.scala.tasks.BaseScalaCompileOptions:additionalParameters[`ScalaCompile.scalaCompileOptions.additionalParameters`] disables that logic in favor of the explicit flag.

[[sec:scala_compiling_in_external_process]]
== Compiling in external process
Expand Down
Expand Up @@ -693,6 +693,13 @@ In case no toolchains are explicitly configured, the toolchain corresponding to

Similarly, tasks from the Groovy and Scala plugins also rely on toolchains to determine on which JVM they are executed.

==== Scala compilation target

With the toolchain changes described above, Scala compilation tasks are now always provided with a `target` or `release` parameter.
The exact parameter and value depend on toolchain usage, or not, and Scala version.

See the <<scala_plugin#sec:scala_target,Scala plugin documentation>> for details.

[[changes_7.6]]
== Upgrading from 7.5 and earlier

Expand Down
Expand Up @@ -1402,6 +1402,9 @@ private void validate(String output, String displayName) {
} else if (line.matches(".*w: .* is deprecated\\..*")) {
// A kotlinc warning, ignore
i++;
} else if (line.matches("\\[Warn] :.* is deprecated: .*")) {
// A scalac warning, ignore
i++;
} else if (isDeprecationMessageInHelpDescription(line)) {
i++;
} else if (removeFirstExpectedDeprecationWarning(line)) {
Expand Down
Expand Up @@ -22,11 +22,13 @@ import java.util.regex.Pattern

/**
* Removes Scala Zinc compilation time logging from the build-init Scala samples output.
* Also removes the potential warning around using `-target` instead of `-release`.
* */
class ZincScalaCompilerOutputNormalizer implements OutputNormalizer {

private static final PATTERN = Pattern.compile(
"Scala Compiler interface compilation took ([0-9]+ hrs )?([0-9]+ mins )?[0-9.]+ secs\n\n",
"(Scala Compiler interface compilation took ([0-9]+ hrs )?([0-9]+ mins )?[0-9.]+ secs\n\n)" +
"|(\\[Warn] : -target is deprecated: Use -release instead to compile against the correct platform API.\none warning found\n\n)",
Pattern.MULTILINE
)

Expand Down
Expand Up @@ -67,6 +67,43 @@ Scala Compiler interface compilation took 1 hrs 20 mins 41.884 secs
> Task :app:check
> Task :app:build
BUILD SUCCESSFUL in 0s
7 actionable tasks: 7 executed
""".trim()

when:
String normalized = outputNormalizer.normalize(input, executionMetadata)

then:
normalized == expected
}

def "successfully normalizes Scala compiler warning"() {
given:
String input = """
> Task :app:compileJava NO-SOURCE
> Task :app:compileScala
[Warn] : -target is deprecated: Use -release instead to compile against the correct platform API.
one warning found
> Task :app:processResources NO-SOURCE
> Task :app:classes
> Task :app:jar
> Task :app:startScripts
> Task :app:distTar
> Task :app:distZip
> Task :app:assemble
> Task :app:compileTestJava NO-SOURCE
> Task :app:compileTestScala
[Warn] : -target is deprecated: Use -release instead to compile against the correct platform API.
one warning found
> Task :app:processTestResources NO-SOURCE
> Task :app:testClasses
> Task :app:test
> Task :app:check
> Task :app:build
BUILD SUCCESSFUL in 0s
7 actionable tasks: 7 executed
""".trim()
Expand Down
Expand Up @@ -81,6 +81,7 @@ class ScalaPluginIntegrationTest extends MultiVersionIntegrationSpec {
"""

expect:
executer.noDeprecationChecks()
succeeds(":a:classes", "--parallel")
true
}
Expand Down
Expand Up @@ -22,6 +22,8 @@ import org.gradle.integtests.fixtures.ScalaCoverage
import org.gradle.integtests.fixtures.TargetCoverage
import org.gradle.integtests.fixtures.jvm.JavaToolchainFixture
import org.gradle.internal.jvm.Jvm
import org.gradle.util.Requires
import org.gradle.util.TestPrecondition

import static org.gradle.scala.ScalaCompilationFixture.scalaDependency

Expand Down Expand Up @@ -96,4 +98,25 @@ class Person {
"immutable list" | "[].asImmutable()"
}

@Requires(TestPrecondition.JDK11_OR_LATER)
def "can compile sources using later JDK APIs"() {
file("src/main/scala/App.scala") << """
object App {
def main(args: Array[String]): Unit = {
var x : String = "Test String"
// isBlank is from JDK 11
println(x.isBlank)
}
}
"""

when:
run ":compileScala"

then:
executedAndNotSkipped(":compileScala")

JavaVersion.forClass(scalaClassFile("App.class").bytes) == JavaVersion.VERSION_1_8
}

}
Expand Up @@ -79,10 +79,30 @@ private static boolean hasTargetDefiningParameter(List<String> additionalParamet
return additionalParameters.stream().anyMatch(s -> TARGET_DEFINING_PARAMETERS.stream().anyMatch(s::startsWith));
}

/**
* Computes parameter to specify how Scala should handle Java APIs and produced bytecode version.
* <p>
* The exact result depends on the Scala version in use and if the toolchain is user specified or not.
*
* @param scalaVersion The detected scala version
* @param javaToolchain The toolchain used to run compilation
* @return a Scala compiler parameter
*/
private static String determineTargetParameter(VersionNumber scalaVersion, JavaToolchain javaToolchain) {
int effectiveTarget = javaToolchain.isFallbackToolchain() ? FALLBACK_JVM_TARGET : javaToolchain.getLanguageVersion().asInt();
if (scalaVersion.compareTo(RELEASE_REPLACES_TARGET_SINCE_VERSION) >= 0) {
return String.format("-release:%s", effectiveTarget);
boolean explicitToolchain = !javaToolchain.isFallbackToolchain();
int effectiveTarget = !explicitToolchain ? FALLBACK_JVM_TARGET : javaToolchain.getLanguageVersion().asInt();
if (scalaVersion.compareTo(VersionNumber.parse("3.0.0")) >= 0) {
if (explicitToolchain) {
return String.format("-release:%s", effectiveTarget);
} else {
return String.format("-Xtarget:%s", effectiveTarget);
}
} else if (scalaVersion.compareTo(RELEASE_REPLACES_TARGET_SINCE_VERSION) >= 0) {
if (explicitToolchain) {
return String.format("-release:%s", effectiveTarget);
} else {
return String.format("-target:%s", effectiveTarget);
}
} else if (scalaVersion.compareTo(PLAIN_TARGET_FORMAT_SINCE_VERSION) >= 0) {
return String.format("-target:%s", effectiveTarget);
} else {
Expand Down
Expand Up @@ -81,9 +81,9 @@ class ScalaCompileOptionsConfigurerTest extends Specification {
17 | false | '2.13.10' | '-release:17'
17 | false | '3.2.1' | '-release:17'

17 | true | '2.13.9' | '-release:8'
17 | true | '2.13.10' | '-release:8'
17 | true | '3.2.1' | '-release:8'
17 | true | '2.13.9' | '-target:8'
17 | true | '2.13.10' | '-target:8'
17 | true | '3.2.1' | '-Xtarget:8'

toolchain = fallbackToolchain ? "$javaToolchain (fallback)" : javaToolchain
}
Expand Down

0 comments on commit 0fed2c8

Please sign in to comment.