From 7346e88d4a283146f831e4a94d4fe120006c297d Mon Sep 17 00:00:00 2001 From: Callum Rogers Date: Mon, 9 May 2022 17:00:58 +0100 Subject: [PATCH] More laziness to prevent Stackoverflow exceptions. (#2260) Make interactions with gradle java toolchains more lazy, so `StackOverflowError`s do not occur --- changelog/@unreleased/pr-2260.v2.yml | 6 ++ .../BaselineJavadocToolAdapter.java | 55 ++++++++++++++++ .../javaversions/ConfiguredJavaToolchain.java | 11 +++- .../FallbackGradleJavaToolchain.java | 51 --------------- .../JavaInstallationMetadataWrapper.java | 64 +++++++++++++++++++ .../plugins/javaversions/JavaToolchains.java | 23 ++++--- 6 files changed, 148 insertions(+), 62 deletions(-) create mode 100644 changelog/@unreleased/pr-2260.v2.yml create mode 100644 gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/BaselineJavadocToolAdapter.java delete mode 100644 gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/FallbackGradleJavaToolchain.java create mode 100644 gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaInstallationMetadataWrapper.java diff --git a/changelog/@unreleased/pr-2260.v2.yml b/changelog/@unreleased/pr-2260.v2.yml new file mode 100644 index 000000000..01f6dfc80 --- /dev/null +++ b/changelog/@unreleased/pr-2260.v2.yml @@ -0,0 +1,6 @@ +type: fix +fix: + description: Make interactions with gradle java toolchains more lazy, so `StackOverflowError`s + do not occur + links: + - https://github.com/palantir/gradle-baseline/pull/2260 diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/BaselineJavadocToolAdapter.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/BaselineJavadocToolAdapter.java new file mode 100644 index 000000000..3dd8feff1 --- /dev/null +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/BaselineJavadocToolAdapter.java @@ -0,0 +1,55 @@ +/* + * (c) Copyright 2022 Palantir Technologies Inc. All rights reserved. + * + * Licensed 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. + */ +package com.palantir.baseline.plugins.javaversions; + +// CHECKSTYLE:OFF + +import javax.inject.Inject; +import org.gradle.api.file.RegularFile; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.tasks.javadoc.internal.JavadocToolAdapter; +import org.gradle.jvm.toolchain.JavaInstallationMetadata; +import org.gradle.process.internal.ExecActionFactory; +// CHECKSTYLE:ON + +final class BaselineJavadocToolAdapter extends JavadocToolAdapter { + private final BaselineJavadocTool javadocTool; + + BaselineJavadocToolAdapter(ExecActionFactory execActionFactory, BaselineJavadocTool javadocTool) { + super(execActionFactory, null); + this.javadocTool = javadocTool; + } + + @Override + public JavaInstallationMetadata getMetadata() { + return javadocTool.getMetadata(); + } + + @Override + public RegularFile getExecutablePath() { + return javadocTool.getExecutablePath(); + } + + public static BaselineJavadocToolAdapter create(ObjectFactory objectFactory, BaselineJavadocTool javadocTool) { + return new BaselineJavadocToolAdapter( + objectFactory.newInstance(ExecActionFactoryGrabber.class).getExecActionFactory(), javadocTool); + } + + interface ExecActionFactoryGrabber { + @Inject + ExecActionFactory getExecActionFactory(); + } +} diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/ConfiguredJavaToolchain.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/ConfiguredJavaToolchain.java index 43c631df0..988d893e3 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/ConfiguredJavaToolchain.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/ConfiguredJavaToolchain.java @@ -16,6 +16,7 @@ package com.palantir.baseline.plugins.javaversions; +import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Provider; import org.gradle.jvm.toolchain.JavaCompiler; import org.gradle.jvm.toolchain.JavaInstallationMetadata; @@ -23,9 +24,11 @@ import org.gradle.jvm.toolchain.JavadocTool; final class ConfiguredJavaToolchain implements BaselineJavaToolchain { + private final ObjectFactory objectFactory; private final Provider javaInstallationMetadata; - ConfiguredJavaToolchain(Provider javaInstallationMetadata) { + ConfiguredJavaToolchain(ObjectFactory objectFactory, Provider javaInstallationMetadata) { + this.objectFactory = objectFactory; this.javaInstallationMetadata = javaInstallationMetadata; } @@ -36,7 +39,11 @@ public Provider javaCompiler() { @Override public Provider javadocTool() { - return javaInstallationMetadata.map(BaselineJavadocTool::new); + return javaInstallationMetadata + .map(BaselineJavadocTool::new) + // Gradle casts to the internal type JavadocToolAdapter in the Javadoc class - so unfortunately we have + // to use that instead of just returning the interface. + .map(javadocTool -> BaselineJavadocToolAdapter.create(objectFactory, javadocTool)); } @Override diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/FallbackGradleJavaToolchain.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/FallbackGradleJavaToolchain.java deleted file mode 100644 index 52a4dfd1d..000000000 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/FallbackGradleJavaToolchain.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * (c) Copyright 2022 Palantir Technologies Inc. All rights reserved. - * - * Licensed 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. - */ - -package com.palantir.baseline.plugins.javaversions; - -import org.gradle.api.Action; -import org.gradle.api.provider.Provider; -import org.gradle.jvm.toolchain.JavaCompiler; -import org.gradle.jvm.toolchain.JavaLauncher; -import org.gradle.jvm.toolchain.JavaToolchainService; -import org.gradle.jvm.toolchain.JavaToolchainSpec; -import org.gradle.jvm.toolchain.JavadocTool; - -public final class FallbackGradleJavaToolchain implements BaselineJavaToolchain { - private final JavaToolchainService javaToolchainService; - private final Action configureJavaToolchainSpec; - - public FallbackGradleJavaToolchain( - JavaToolchainService javaToolchainService, Action configureJavaToolchainSpec) { - this.javaToolchainService = javaToolchainService; - this.configureJavaToolchainSpec = configureJavaToolchainSpec; - } - - @Override - public Provider javaCompiler() { - return javaToolchainService.compilerFor(configureJavaToolchainSpec); - } - - @Override - public Provider javadocTool() { - return javaToolchainService.javadocToolFor(configureJavaToolchainSpec); - } - - @Override - public Provider javaLauncher() { - return javaToolchainService.launcherFor(configureJavaToolchainSpec); - } -} diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaInstallationMetadataWrapper.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaInstallationMetadataWrapper.java new file mode 100644 index 000000000..28c9c38dc --- /dev/null +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaInstallationMetadataWrapper.java @@ -0,0 +1,64 @@ +/* + * (c) Copyright 2022 Palantir Technologies Inc. All rights reserved. + * + * Licensed 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. + */ + +package com.palantir.baseline.plugins.javaversions; + +import org.gradle.api.file.Directory; +import org.gradle.api.provider.Provider; +import org.gradle.jvm.toolchain.JavaInstallationMetadata; +import org.gradle.jvm.toolchain.JavaLanguageVersion; + +/** + * The purpose of this class is to provide the JavaLanguageVersion, which we know upfront, without having to resolve an + * entire JDK, which often involves resolution, which causes Gradle to resolve the JavaCompiler/JavaLauncher etc to + * check if toolchains are enabled. That can cause a dependency cycle, which causes a StackOverflowException. + * This class breaks that cycle by immediately providing the JavaLanguageVersion without possibly causing a resolution. + */ +final class JavaInstallationMetadataWrapper implements JavaInstallationMetadata { + private final JavaLanguageVersion javaLanguageVersion; + private final Provider delegate; + + JavaInstallationMetadataWrapper( + JavaLanguageVersion javaLanguageVersion, Provider delegate) { + this.javaLanguageVersion = javaLanguageVersion; + this.delegate = delegate; + } + + @Override + public JavaLanguageVersion getLanguageVersion() { + return javaLanguageVersion; + } + + @Override + public String getJavaRuntimeVersion() { + return delegate.get().getJavaRuntimeVersion(); + } + + @Override + public String getJvmVersion() { + return delegate.get().getJvmVersion(); + } + + @Override + public String getVendor() { + return delegate.get().getVendor(); + } + + @Override + public Directory getInstallationPath() { + return delegate.get().getInstallationPath(); + } +} diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaToolchains.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaToolchains.java index 93eda1cc6..12f203539 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaToolchains.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaToolchains.java @@ -33,16 +33,21 @@ public JavaToolchains(Project project, BaselineJavaVersionsExtension baselineJav } public Provider forVersion(Provider javaLanguageVersionProvider) { - return javaLanguageVersionProvider.flatMap(javaLanguageVersion -> { - Provider configuredJdk = - baselineJavaVersionsExtension.getJdks().getting(javaLanguageVersion); + return javaLanguageVersionProvider.map(javaLanguageVersion -> { + Provider configuredJdkMetadata = baselineJavaVersionsExtension + .getJdks() + .getting(javaLanguageVersion) + .orElse(project.provider(() -> project.getExtensions() + .getByType(JavaToolchainService.class) + .launcherFor(javaToolchainSpec -> + javaToolchainSpec.getLanguageVersion().set(javaLanguageVersion)) + .get() + .getMetadata())); - return configuredJdk - .map(_ignored -> new ConfiguredJavaToolchain(configuredJdk)) - .orElse(project.provider(() -> new FallbackGradleJavaToolchain( - project.getExtensions().getByType(JavaToolchainService.class), - javaToolchainSpec -> - javaToolchainSpec.getLanguageVersion().set(javaLanguageVersion)))); + return new ConfiguredJavaToolchain( + project.getObjects(), + project.provider( + () -> new JavaInstallationMetadataWrapper(javaLanguageVersion, configuredJdkMetadata))); }); } }