diff --git a/subprojects/core/src/main/java/org/gradle/initialization/DefaultSettings.java b/subprojects/core/src/main/java/org/gradle/initialization/DefaultSettings.java index 26da9719a86b..d0020adbe97b 100644 --- a/subprojects/core/src/main/java/org/gradle/initialization/DefaultSettings.java +++ b/subprojects/core/src/main/java/org/gradle/initialization/DefaultSettings.java @@ -43,6 +43,7 @@ import org.gradle.internal.Actions; import org.gradle.internal.deprecation.DeprecationLogger; import org.gradle.internal.management.DependencyResolutionManagementInternal; +import org.gradle.internal.management.ToolchainManagementInternal; import org.gradle.internal.resource.TextUriResourceLoader; import org.gradle.internal.service.ServiceRegistry; import org.gradle.internal.service.scopes.ServiceRegistryFactory; @@ -77,6 +78,8 @@ public abstract class DefaultSettings extends AbstractPluginAware implements Set private final List includedBuildSpecs = new ArrayList<>(); private final DependencyResolutionManagementInternal dependencyResolutionManagement; + private final ToolchainManagementInternal toolchainManagement; + public DefaultSettings( ServiceRegistryFactory serviceRegistryFactory, GradleInternal gradle, @@ -97,6 +100,7 @@ public DefaultSettings( this.services = serviceRegistryFactory.createFor(this); this.rootProjectDescriptor = createProjectDescriptor(null, settingsDir.getName(), settingsDir); this.dependencyResolutionManagement = services.get(DependencyResolutionManagementInternal.class); + this.toolchainManagement = services.get(ToolchainManagementInternal.class); } @Override @@ -370,13 +374,14 @@ public void enableFeaturePreview(String name) { } @Override - public void dependencyResolutionManagement(Action dependencyResolutionConfiguration) { - dependencyResolutionConfiguration.execute(dependencyResolutionManagement); + public void preventFromFurtherMutation() { + dependencyResolutionManagement.preventFromFurtherMutation(); + toolchainManagement.preventFromFurtherMutation(); } @Override - public void preventFromFurtherMutation() { - dependencyResolutionManagement.preventFromFurtherMutation(); + public void dependencyResolutionManagement(Action dependencyResolutionConfiguration) { + dependencyResolutionConfiguration.execute(dependencyResolutionManagement); } @Override @@ -385,13 +390,12 @@ public DependencyResolutionManagementInternal getDependencyResolutionManagement( } @Override - @Inject public ToolchainManagement getToolchainManagement() { - throw new UnsupportedOperationException(); + return toolchainManagement; } @Override public void toolchainManagement(Action toolchainManagementConfiguration) { - toolchainManagementConfiguration.execute(getToolchainManagement()); + toolchainManagementConfiguration.execute(toolchainManagement); } } diff --git a/subprojects/core/src/main/java/org/gradle/initialization/DefaultToolchainManagement.java b/subprojects/core/src/main/java/org/gradle/initialization/DefaultToolchainManagement.java index d303dec60692..a840ccb306a7 100644 --- a/subprojects/core/src/main/java/org/gradle/initialization/DefaultToolchainManagement.java +++ b/subprojects/core/src/main/java/org/gradle/initialization/DefaultToolchainManagement.java @@ -16,7 +16,19 @@ package org.gradle.initialization; -import org.gradle.api.toolchain.management.ToolchainManagement; +import org.gradle.api.internal.plugins.ExtensionContainerInternal; +import org.gradle.internal.FinalizableValue; +import org.gradle.internal.management.ToolchainManagementInternal; + +public abstract class DefaultToolchainManagement implements ToolchainManagementInternal { + + @Override + public void preventFromFurtherMutation() { + ExtensionContainerInternal extensions = (ExtensionContainerInternal) getExtensions(); + extensions.getAsMap().values().stream() + .filter(ext -> ext instanceof FinalizableValue) + .map(ext -> (FinalizableValue) ext) + .forEach(FinalizableValue::preventFromFurtherMutation); + } -public abstract class DefaultToolchainManagement implements ToolchainManagement { } diff --git a/subprojects/core/src/main/java/org/gradle/internal/FinalizableValue.java b/subprojects/core/src/main/java/org/gradle/internal/FinalizableValue.java new file mode 100644 index 000000000000..34676ccd0a85 --- /dev/null +++ b/subprojects/core/src/main/java/org/gradle/internal/FinalizableValue.java @@ -0,0 +1,35 @@ +/* + * Copyright 2022 the original author or authors. + * + * 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 org.gradle.internal; + +/** + * Mutable value type for which mutation can be disabled at a certain point in time, + * by calling the {@code disableFurtherMutations()} method. + *

+ * After {@code disableFurtherMutations()} has been called, any subsequent calls to methods that mutate + * the value in any way will fail by throwing an {@code IllegalStateException}. + */ +public interface FinalizableValue { + + /** + * Disallows further changes to the value represented by this type. + *

+ * Subsequent calls to methods that mutate the value in any way will fail by throwing an {@code IllegalStateException}. + */ + void preventFromFurtherMutation(); + +} diff --git a/subprojects/core/src/main/java/org/gradle/internal/management/DependencyResolutionManagementInternal.java b/subprojects/core/src/main/java/org/gradle/internal/management/DependencyResolutionManagementInternal.java index 07e0482b36ef..544b6df56df4 100644 --- a/subprojects/core/src/main/java/org/gradle/internal/management/DependencyResolutionManagementInternal.java +++ b/subprojects/core/src/main/java/org/gradle/internal/management/DependencyResolutionManagementInternal.java @@ -22,18 +22,17 @@ import org.gradle.api.initialization.resolve.RulesMode; import org.gradle.api.internal.project.ProjectInternal; import org.gradle.api.provider.Property; +import org.gradle.internal.FinalizableValue; import org.gradle.internal.service.scopes.Scopes; import org.gradle.internal.service.scopes.ServiceScope; import java.util.List; @ServiceScope(Scopes.Build.class) -public interface DependencyResolutionManagementInternal extends DependencyResolutionManagement { +public interface DependencyResolutionManagementInternal extends DependencyResolutionManagement, FinalizableValue { void configureProject(ProjectInternal project); - void preventFromFurtherMutation(); - void applyRules(ComponentMetadataHandler target); RepositoriesModeInternal getConfiguredRepositoriesMode(); diff --git a/subprojects/core/src/main/java/org/gradle/internal/management/ToolchainManagementInternal.java b/subprojects/core/src/main/java/org/gradle/internal/management/ToolchainManagementInternal.java new file mode 100644 index 000000000000..d2ba3270d9ca --- /dev/null +++ b/subprojects/core/src/main/java/org/gradle/internal/management/ToolchainManagementInternal.java @@ -0,0 +1,26 @@ +/* + * Copyright 2022 the original author or authors. + * + * 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 org.gradle.internal.management; + +import org.gradle.api.toolchain.management.ToolchainManagement; +import org.gradle.internal.FinalizableValue; +import org.gradle.internal.service.scopes.Scopes; +import org.gradle.internal.service.scopes.ServiceScope; + +@ServiceScope(Scopes.Build.class) +public interface ToolchainManagementInternal extends ToolchainManagement, FinalizableValue { +} diff --git a/subprojects/jvm-services/src/main/java/org/gradle/internal/jvm/inspection/JvmInstallationMetadata.java b/subprojects/jvm-services/src/main/java/org/gradle/internal/jvm/inspection/JvmInstallationMetadata.java index 2b566019e3fd..9cdb48b5abb8 100644 --- a/subprojects/jvm-services/src/main/java/org/gradle/internal/jvm/inspection/JvmInstallationMetadata.java +++ b/subprojects/jvm-services/src/main/java/org/gradle/internal/jvm/inspection/JvmInstallationMetadata.java @@ -261,6 +261,21 @@ public Throwable getErrorCause() { public boolean isValidInstallation() { return true; } + + @Override + public String toString() { + return "DefaultJvmInstallationMetadata{" + + "languageVersion=" + languageVersion + + ", javaVersion='" + javaVersion + '\'' + + ", javaVendor='" + javaVendor + '\'' + + ", runtimeName='" + runtimeName + '\'' + + ", runtimeVersion='" + runtimeVersion + '\'' + + ", jvmName='" + jvmName + '\'' + + ", jvmVersion='" + jvmVersion + '\'' + + ", jvmVendor='" + jvmVendor + '\'' + + ", architecture='" + architecture + '\'' + + '}'; + } } class FailureInstallationMetadata implements JvmInstallationMetadata { diff --git a/subprojects/jvm-services/src/main/java/org/gradle/jvm/toolchain/internal/JavaInstallationRegistry.java b/subprojects/jvm-services/src/main/java/org/gradle/jvm/toolchain/internal/JavaInstallationRegistry.java index e4b9d4660b97..a541a2e1ea85 100644 --- a/subprojects/jvm-services/src/main/java/org/gradle/jvm/toolchain/internal/JavaInstallationRegistry.java +++ b/subprojects/jvm-services/src/main/java/org/gradle/jvm/toolchain/internal/JavaInstallationRegistry.java @@ -17,8 +17,6 @@ package org.gradle.jvm.toolchain.internal; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; import org.gradle.api.GradleException; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; @@ -37,12 +35,13 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Collectors; public class JavaInstallationRegistry { private final BuildOperationExecutor executor; - private final Supplier> installations; + private final Installations installations; private final Logger logger; private final OperatingSystem os; @@ -54,7 +53,7 @@ public JavaInstallationRegistry(List suppliers, BuildOpera private JavaInstallationRegistry(List suppliers, Logger logger, BuildOperationExecutor executor, OperatingSystem os) { this.logger = logger; this.executor = executor; - this.installations = Suppliers.memoize(() -> collectInBuildOperation(suppliers)); + this.installations = new Installations(() -> collectInBuildOperation(suppliers)); this.os = os; } @@ -71,6 +70,10 @@ public Set listInstallations() { return installations.get(); } + public void addInstallation(InstallationLocation installation) { + installations.add(installation); + } + private Set collectInstallations(List suppliers) { return suppliers.parallelStream() .map(InstallationSupplier::get) @@ -146,4 +149,32 @@ public BuildOperationDescriptor.Builder description() { } } + private static class Installations { + + private final Supplier> initializer; + + private Set locations = null; + + Installations(Supplier> initializer) { + this.initializer = initializer; + } + + synchronized Set get() { + initIfNeeded(); + return locations; + } + + synchronized void add(InstallationLocation location) { + initIfNeeded(); + locations.add(location); + } + + private void initIfNeeded() { + if (locations == null) { + locations = initializer.get(); + } + } + + } + } diff --git a/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadSpiAuthenticationIntegrationTest.groovy b/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadSpiAuthenticationIntegrationTest.groovy index 0f3138f25e3f..2703f451d231 100644 --- a/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadSpiAuthenticationIntegrationTest.groovy +++ b/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadSpiAuthenticationIntegrationTest.groovy @@ -82,7 +82,8 @@ class JavaToolchainDownloadSpiAuthenticationIntegrationTest extends AbstractJava .assertHasCause("Error while evaluating property 'javaCompiler' of task ':compileJava'.") .assertHasCause("Failed to calculate the value of task ':compileJava' property 'javaCompiler'.") .assertHasCause("Unable to download toolchain matching the requirements ({languageVersion=99, vendor=matching('exotic'), implementation=vendor-specific}) from '" + archiveUri + "'.") - .assertHasCause("Provisioned toolchain '" + temporaryFolder.testDirectory.file("user-home", "jdks", "toolchain") + "' could not be probed.") + .assertHasCause("Provisioned toolchain '" + temporaryFolder.testDirectory.file("user-home", "jdks", "toolchain") + "' could not be probed: " + + "A problem occurred starting process 'command '") } @ToBeFixedForConfigurationCache(because = "Fails the build with an additional error") @@ -135,7 +136,8 @@ class JavaToolchainDownloadSpiAuthenticationIntegrationTest extends AbstractJava .assertHasCause("Error while evaluating property 'javaCompiler' of task ':compileJava'") .assertHasCause("Failed to calculate the value of task ':compileJava' property 'javaCompiler'.") .assertHasCause("Unable to download toolchain matching the requirements ({languageVersion=99, vendor=matching('exotic'), implementation=vendor-specific}) from '" + archiveUri + "'.") - .assertHasCause("Provisioned toolchain '" + temporaryFolder.testDirectory.file("user-home", "jdks", "toolchain") + "' could not be probed.") + .assertHasCause("Provisioned toolchain '" + temporaryFolder.testDirectory.file("user-home", "jdks", "toolchain") + "' could not be probed: " + + "A problem occurred starting process 'command '") } private static String customToolchainResolverCode(String uri) { diff --git a/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadSpiIntegrationTest.groovy b/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadSpiIntegrationTest.groovy index e22e1ecc0b55..945978777cd6 100644 --- a/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadSpiIntegrationTest.groovy +++ b/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadSpiIntegrationTest.groovy @@ -369,7 +369,7 @@ class JavaToolchainDownloadSpiIntegrationTest extends AbstractJavaToolchainDownl } } - println(\"\"\"Explicitly requested toolchains: \${toolchainManagement.jvm.getJavaRepositories().collect { it.getName() }}.\"\"\") + println(\"\"\"Explicitly requested toolchains: \${toolchainManagement.jvm.getJavaRepositories().getAsList().collect { it.getName() }}.\"\"\") """ buildFile << """ @@ -395,6 +395,81 @@ class JavaToolchainDownloadSpiIntegrationTest extends AbstractJavaToolchainDownl failure.getOutput().contains("Explicitly requested toolchains: [useless3, useless1].") } + @ToBeFixedForConfigurationCache(because = "Fails the build with an additional error") + def "created repository can be removed"() { + settingsFile << """ + ${applyToolchainResolverPlugin("UselessToolchainResolver1", uselessToolchainResolverCode("UselessToolchainResolver1"))} + ${applyToolchainResolverPlugin("UselessToolchainResolver2", uselessToolchainResolverCode("UselessToolchainResolver2"))} + ${applyToolchainResolverPlugin("UselessToolchainResolver3", uselessToolchainResolverCode("UselessToolchainResolver3"))} + toolchainManagement { + jvm { + javaRepositories { + repository('useless1') { + resolverClass = UselessToolchainResolver1 + } + repository('useless2') { + resolverClass = UselessToolchainResolver2 + } + repository('useless3') { + resolverClass = UselessToolchainResolver3 + } + } + } + } + + toolchainManagement.jvm.javaRepositories.remove('useless2') + + println(\"\"\"Explicitly requested toolchains: \${toolchainManagement.jvm.getJavaRepositories().getAsList().collect { it.getName() }}.\"\"\") + """ + + buildFile << """ + apply plugin: "java" + + java { + toolchain { + languageVersion = JavaLanguageVersion.of(99) + } + } + """ + + file("src/main/java/Foo.java") << "public class Foo {}" + + when: + failure = executer + .withTasks("compileJava") + .requireOwnGradleUserHomeDir() + .withToolchainDownloadEnabled() + .runWithFailure() + + then: + failure.getOutput().contains("Explicitly requested toolchains: [useless1, useless3].") + } + + def "cannot mutate repository rules after settings have been evaluated"() { + settingsFile << """ + ${applyToolchainResolverPlugin("UselessToolchainResolver", uselessToolchainResolverCode("UselessToolchainResolver"))} + toolchainManagement { + jvm { + javaRepositories { + repository('useless') { + resolverClass = UselessToolchainResolver + } + } + } + } + """ + + buildFile << """ + gradle.settings.toolchainManagement.jvm.javaRepositories.remove('useless') + """ + + when: + fails ":help" + + then: + failure.assertHasCause("Mutation of toolchain repositories declared in settings is only allowed during settings evaluation") + } + private static String customToolchainResolverCode() { """ import java.util.Optional; @@ -455,13 +530,13 @@ class JavaToolchainDownloadSpiIntegrationTest extends AbstractJavaToolchainDownl SystemInfo systemInfo = NativeServices.getInstance().get(SystemInfo.class) switch (systemInfo.architecture) { case SystemInfo.Architecture.i386: - return "x32"; + return "x32" case SystemInfo.Architecture.amd64: - return "x64"; + return "x64" case SystemInfo.Architecture.aarch64: - return "aarch64"; + return "aarch64" default: - return "unknown"; + return "unknown" } } diff --git a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/JavaToolchainRepositoryHandler.java b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/JavaToolchainRepositoryHandler.java index c1c672e57eee..7570d106f81d 100644 --- a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/JavaToolchainRepositoryHandler.java +++ b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/JavaToolchainRepositoryHandler.java @@ -18,7 +18,8 @@ import org.gradle.api.Action; import org.gradle.api.Incubating; -import org.gradle.api.NamedDomainObjectList; + +import java.util.List; /** * {@link org.gradle.api.NamedDomainObjectList} based handler for configuring an @@ -27,7 +28,7 @@ * @since 7.6 */ @Incubating -public interface JavaToolchainRepositoryHandler extends NamedDomainObjectList { +public interface JavaToolchainRepositoryHandler { /** * Utility method for creating a named {@link JavaToolchainRepository} based on @@ -35,4 +36,28 @@ public interface JavaToolchainRepositoryHandler extends NamedDomainObjectList configureAction); + /** + * Returns a list of repositories that have been added so far. The list order + * reflects the order in which the repositories have been declared. + * + * @since 7.6.1 + */ + List getAsList(); + + /** + * Returns the count of the repositories added so far. + * + * @since 7.6.1 + */ + int size(); + + /** + * Removes the repository with the given name. + *

+ * Returns true if a repository with the specified name exists and has been successfully removed, false otherwise. + * + * @since 7.6.1 + */ + boolean remove(String name); + } diff --git a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/DefaultJavaToolchainRepositoryHandler.java b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/DefaultJavaToolchainRepositoryHandler.java index b601738c6ccc..8c29e371a169 100644 --- a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/DefaultJavaToolchainRepositoryHandler.java +++ b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/DefaultJavaToolchainRepositoryHandler.java @@ -18,26 +18,35 @@ import org.gradle.api.Action; import org.gradle.api.GradleException; +import org.gradle.api.InvalidUserCodeException; import org.gradle.api.Namer; +import org.gradle.api.artifacts.repositories.AuthenticationContainer; +import org.gradle.api.artifacts.repositories.PasswordCredentials; +import org.gradle.api.credentials.Credentials; import org.gradle.api.internal.CollectionCallbackActionDecorator; import org.gradle.api.internal.DefaultNamedDomainObjectList; import org.gradle.api.internal.artifacts.repositories.AuthenticationSupporter; import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.Property; import org.gradle.api.provider.ProviderFactory; import org.gradle.authentication.Authentication; import org.gradle.internal.authentication.AuthenticationSchemeRegistry; import org.gradle.internal.authentication.DefaultAuthenticationContainer; import org.gradle.internal.reflect.Instantiator; import org.gradle.jvm.toolchain.JavaToolchainRepository; -import org.gradle.jvm.toolchain.JavaToolchainRepositoryHandler; +import org.gradle.jvm.toolchain.JavaToolchainResolver; import javax.inject.Inject; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; -public class DefaultJavaToolchainRepositoryHandler extends DefaultNamedDomainObjectList - implements JavaToolchainRepositoryHandler { +public class DefaultJavaToolchainRepositoryHandler implements JavaToolchainRepositoryHandlerInternal { - private final JavaToolchainResolverRegistryInternal registry; + private final DefaultNamedDomainObjectList repositories; private final Instantiator instantiator; @@ -47,16 +56,21 @@ public class DefaultJavaToolchainRepositoryHandler extends DefaultNamedDomainObj private final AuthenticationSchemeRegistry authenticationSchemeRegistry; + private boolean mutable = true; + @Inject public DefaultJavaToolchainRepositoryHandler( - JavaToolchainResolverRegistryInternal registry, Instantiator instantiator, ObjectFactory objectFactory, ProviderFactory providerFactory, AuthenticationSchemeRegistry authenticationSchemeRegistry ) { - super(JavaToolchainRepository.class, instantiator, new RepositoryNamer(), CollectionCallbackActionDecorator.NOOP); - this.registry = registry; + this.repositories = new DefaultNamedDomainObjectList(JavaToolchainRepository.class, instantiator, new RepositoryNamer(), CollectionCallbackActionDecorator.NOOP) { + @Override + public String getTypeDisplayName() { + return "repository"; + } + }; this.instantiator = instantiator; this.objectFactory = objectFactory; this.providerFactory = providerFactory; @@ -72,6 +86,8 @@ public String determineName(JavaToolchainRepository repository) { @Override public void repository(String name, Action configureAction) { + assertMutable(); + DefaultAuthenticationContainer authenticationContainer = new DefaultAuthenticationContainer(instantiator, CollectionCallbackActionDecorator.NOOP); for (Map.Entry, Class> e : authenticationSchemeRegistry.getRegisteredSchemes().entrySet()) { authenticationContainer.registerBinding(e.getKey(), e.getValue()); @@ -81,14 +97,106 @@ public void repository(String name, Action conf DefaultJavaToolchainRepository repository = objectFactory.newInstance(DefaultJavaToolchainRepository.class, name, authenticationContainer, authenticationSupporter, providerFactory); configureAction.execute(repository); - boolean isNew = registry.getRepositories().add(repository); + boolean isNew = repositories.add(repository); if (!isNew) { throw new GradleException("Duplicate configuration for repository '" + name + "'."); } } @Override - public String getTypeDisplayName() { - return "repository"; + public List getAsList() { + ArrayList copy = repositories.stream() + .map(it -> (JavaToolchainRepositoryInternal) it) + .map(ImmutableJavaToolchainRepository::new) + .collect(Collectors.toCollection(ArrayList::new)); + return Collections.unmodifiableList(copy); + } + + @Override + public int size() { + return repositories.size(); + } + + @Override + public boolean remove(String name) { + assertMutable(); + + JavaToolchainRepository repository = repositories.findByName(name); + if (repository == null) { + return false; + } + + return repositories.remove(repository); + } + + @Override + public void preventFromFurtherMutation() { + this.mutable = false; } + + private void assertMutable() { + if (!mutable) { + throw new InvalidUserCodeException("Mutation of toolchain repositories declared in settings is only allowed during settings evaluation"); + } + } + + private static class ImmutableJavaToolchainRepository implements JavaToolchainRepositoryInternal { + + private final JavaToolchainRepositoryInternal delegate; + + public ImmutableJavaToolchainRepository(JavaToolchainRepositoryInternal delegate) { + this.delegate = delegate; + } + + @Override + public Collection getConfiguredAuthentication() { + return delegate.getConfiguredAuthentication(); + } + + @Override + public PasswordCredentials getCredentials() { + return delegate.getCredentials(); + } + + @Override + public T getCredentials(Class credentialsType) { + return delegate.getCredentials(credentialsType); + } + + @Override + public void credentials(Action action) { + throw new UnsupportedOperationException("Can't modify repositories through a read-only view"); + } + + @Override + public void credentials(Class credentialsType, Action action) { + throw new UnsupportedOperationException("Can't modify repositories through a read-only view"); + } + + @Override + public void credentials(Class credentialsType) { + throw new UnsupportedOperationException("Can't modify repositories through a read-only view"); + } + + @Override + public void authentication(Action action) { + throw new UnsupportedOperationException("Can't modify repositories through a read-only view"); + } + + @Override + public AuthenticationContainer getAuthentication() { + return delegate.getAuthentication(); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public Property> getResolverClass() { + return delegate.getResolverClass(); + } + } + } diff --git a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/DefaultJavaToolchainResolverRegistry.java b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/DefaultJavaToolchainResolverRegistry.java index 5489dc802d6e..a153a02ce9f0 100644 --- a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/DefaultJavaToolchainResolverRegistry.java +++ b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/DefaultJavaToolchainResolverRegistry.java @@ -29,7 +29,6 @@ import org.gradle.internal.authentication.AuthenticationSchemeRegistry; import org.gradle.internal.reflect.Instantiator; import org.gradle.jvm.toolchain.JavaToolchainRepository; -import org.gradle.jvm.toolchain.JavaToolchainRepositoryHandler; import org.gradle.jvm.toolchain.JavaToolchainResolver; import javax.inject.Inject; @@ -47,7 +46,7 @@ public abstract class DefaultJavaToolchainResolverRegistry implements JavaToolch private final BuildServiceRegistry sharedServices; - private final DefaultJavaToolchainRepositoryHandler repositories; + private final DefaultJavaToolchainRepositoryHandler repositoryHandler; private final List realizedRepositories = new ArrayList<>(); @@ -62,12 +61,12 @@ public DefaultJavaToolchainResolverRegistry( AuthenticationSchemeRegistry authenticationSchemeRegistry ) { this.sharedServices = gradle.getSharedServices(); - this.repositories = objectFactory.newInstance(DefaultJavaToolchainRepositoryHandler.class, this, instantiator, objectFactory, providerFactory, authenticationSchemeRegistry); + this.repositoryHandler = objectFactory.newInstance(DefaultJavaToolchainRepositoryHandler.class, instantiator, objectFactory, providerFactory, authenticationSchemeRegistry); } @Override - public JavaToolchainRepositoryHandler getRepositories() { - return repositories; + public JavaToolchainRepositoryHandlerInternal getRepositories() { + return repositoryHandler; } @Override @@ -82,7 +81,7 @@ public void register(Class implementationTy @Override public List requestedRepositories() { - if (realizedRepositories.size() != repositories.size()) { + if (realizedRepositories.size() != repositoryHandler.size()) { realizeRepositories(); } return realizedRepositories; @@ -92,7 +91,7 @@ private void realizeRepositories() { realizedRepositories.clear(); Set> resolvers = new HashSet<>(); - for (JavaToolchainRepository repository : repositories) { + for (JavaToolchainRepository repository : repositoryHandler.getAsList()) { if (!resolvers.add(repository.getResolverClass().get())) { throw new GradleException("Duplicate configuration for repository implementation '" + repository.getResolverClass().get().getName() + "'."); } diff --git a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/DefaultJvmToolchainManagement.java b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/DefaultJvmToolchainManagement.java index 59dae352c97b..783b570bb338 100644 --- a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/DefaultJvmToolchainManagement.java +++ b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/DefaultJvmToolchainManagement.java @@ -17,12 +17,13 @@ package org.gradle.jvm.toolchain.internal; import org.gradle.api.Action; +import org.gradle.internal.FinalizableValue; import org.gradle.jvm.toolchain.JavaToolchainRepositoryHandler; import org.gradle.jvm.toolchain.JvmToolchainManagement; import javax.inject.Inject; -public abstract class DefaultJvmToolchainManagement implements JvmToolchainManagement { +public abstract class DefaultJvmToolchainManagement implements JvmToolchainManagement, FinalizableValue { private final JavaToolchainResolverRegistryInternal registry; @@ -41,4 +42,8 @@ public void javaRepositories(Action conf configureAction.execute(getJavaRepositories()); } + @Override + public void preventFromFurtherMutation() { + registry.getRepositories().preventFromFurtherMutation(); + } } diff --git a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/JavaToolchainQueryService.java b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/JavaToolchainQueryService.java index f5ec0db7bcb0..3e26e40c8e76 100644 --- a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/JavaToolchainQueryService.java +++ b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/JavaToolchainQueryService.java @@ -25,6 +25,8 @@ import org.gradle.api.provider.ProviderFactory; import org.gradle.internal.deprecation.DeprecationLogger; import org.gradle.internal.jvm.Jvm; +import org.gradle.internal.service.scopes.Scopes; +import org.gradle.internal.service.scopes.ServiceScope; import org.gradle.jvm.toolchain.JavaToolchainSpec; import org.gradle.jvm.toolchain.internal.install.DefaultJavaToolchainProvisioningService; import org.gradle.jvm.toolchain.internal.install.JavaToolchainProvisioningService; @@ -36,6 +38,7 @@ import java.util.Objects; import java.util.Optional; +@ServiceScope(Scopes.Project.class) public class JavaToolchainQueryService { private final JavaInstallationRegistry registry; @@ -121,30 +124,42 @@ private JavaToolchain query(JavaToolchainSpec spec) { if (spec instanceof CurrentJvmToolchainSpec) { return asToolchain(new InstallationLocation(Jvm.current().getJavaHome(), "current JVM"), spec).get(); } + if (spec instanceof SpecificInstallationToolchainSpec) { return asToolchain(new InstallationLocation(((SpecificInstallationToolchainSpec) spec).getJavaHome(), "specific installation"), spec).get(); } - return registry.listInstallations().stream() - .map(javaHome -> asToolchain(javaHome, spec)) - .filter(Optional::isPresent) - .map(Optional::get) - .filter(new JavaToolchainMatcher(spec)) - .min(new JavaToolchainComparator()) - .orElseGet(() -> downloadToolchain(spec)); + Optional detectedToolchain = registry.listInstallations().stream() + .map(javaHome -> asToolchain(javaHome, spec)) + .filter(Optional::isPresent) + .map(Optional::get) + .filter(new JavaToolchainMatcher(spec)) + .min(new JavaToolchainComparator()); + + if (detectedToolchain.isPresent()) { + return detectedToolchain.get(); + } + + InstallationLocation downloadedInstallation = downloadToolchain(spec); + JavaToolchain downloadedToolchain = asToolchainOrThrow(downloadedInstallation, spec); + registry.addInstallation(downloadedInstallation); + return downloadedToolchain; } - private JavaToolchain downloadToolchain(JavaToolchainSpec spec) { + private InstallationLocation downloadToolchain(JavaToolchainSpec spec) { final Optional installation = installService.tryInstall(spec); if (!installation.isPresent()) { throw new NoToolchainAvailableException(spec, detectEnabled.getOrElse(true), downloadEnabled.getOrElse(true)); } - Optional toolchain = asToolchain(new InstallationLocation(installation.get(), "provisioned toolchain"), spec); + return new InstallationLocation(installation.get(), "provisioned toolchain"); + } + + private JavaToolchain asToolchainOrThrow(InstallationLocation javaHome, JavaToolchainSpec spec) { + Optional toolchain = asToolchain(javaHome, spec); if (!toolchain.isPresent()) { - throw new GradleException("Provisioned toolchain '" + installation.get() + "' could not be probed."); + throw new GradleException("Toolchain installation '" + javaHome.getLocation() + "' could not be probed."); } - return toolchain.get(); } diff --git a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/JavaToolchainRepositoryHandlerInternal.java b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/JavaToolchainRepositoryHandlerInternal.java new file mode 100644 index 000000000000..069d2ae6e3bd --- /dev/null +++ b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/JavaToolchainRepositoryHandlerInternal.java @@ -0,0 +1,23 @@ +/* + * Copyright 2022 the original author or authors. + * + * 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 org.gradle.jvm.toolchain.internal; + +import org.gradle.internal.FinalizableValue; +import org.gradle.jvm.toolchain.JavaToolchainRepositoryHandler; + +public interface JavaToolchainRepositoryHandlerInternal extends JavaToolchainRepositoryHandler, FinalizableValue { +} diff --git a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/JavaToolchainResolverRegistryInternal.java b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/JavaToolchainResolverRegistryInternal.java index e5c85cacf0b3..8448afe83bfc 100644 --- a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/JavaToolchainResolverRegistryInternal.java +++ b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/JavaToolchainResolverRegistryInternal.java @@ -17,13 +17,12 @@ package org.gradle.jvm.toolchain.internal; import org.gradle.jvm.toolchain.JavaToolchainResolverRegistry; -import org.gradle.jvm.toolchain.JavaToolchainRepositoryHandler; import java.util.List; public interface JavaToolchainResolverRegistryInternal extends JavaToolchainResolverRegistry { - JavaToolchainRepositoryHandler getRepositories(); + JavaToolchainRepositoryHandlerInternal getRepositories(); List requestedRepositories(); } diff --git a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/install/JdkCacheDirectory.java b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/install/JdkCacheDirectory.java index c585f8171293..9f6f2f8f9864 100644 --- a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/install/JdkCacheDirectory.java +++ b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/internal/install/JdkCacheDirectory.java @@ -118,32 +118,101 @@ private File getJavaHome(File markedLocation) { * Unpacks and installs the given JDK archive. Returns a file pointing to the java home directory. */ public File provisionFromArchive(JavaToolchainSpec spec, File jdkArchive, URI uri) { - final File unpackFolder = unpack(jdkArchive); - File markedLocation = markedLocation(unpackFolder); - markAsReady(markedLocation); + final File[] unpackFolder = new File[1]; + final File[] installFolder = new File[1]; + try { + unpackFolder[0] = unpack(jdkArchive); + + //put the marker file in the unpack folder from where it will get in its proper place + //when the contents are copied to the installation folder + File markedLocation = markLocationInsideFolder(unpackFolder[0]); + + //probe unpacked installation for its metadata + JvmInstallationMetadata metadata = getMetadata(markedLocation); + + validateMetadataMatchesSpec(spec, uri, metadata); + + String installFolderName = getInstallFolderName(metadata); + installFolder[0] = new File(jdkDirectory, installFolderName); + + //make sure the install folder is empty + checkInstallFolderForLeftoverContent(installFolder[0], uri, spec, metadata); + operations.delete(installFolder[0]); + + //copy content of unpack folder to install folder, including the marker file + operations.copy(copySpec -> { + copySpec.from(unpackFolder[0]); + copySpec.into(installFolder[0]); + }); + + LOGGER.info("Installed toolchain from {} into {}", uri, installFolder[0]); + return getJavaHome(markedLocation(installFolder[0])); + } catch (Throwable t) { + // provisioning failed, clean up leftovers + if (installFolder[0] != null) { + operations.delete(installFolder[0]); + } + throw t; + } finally { + // clean up temporary unpack folder, regardless if provisioning succeeded or not + if (unpackFolder[0] != null) { + operations.delete(unpackFolder[0]); + } + } + } + + private void checkInstallFolderForLeftoverContent(File installFolder, URI uri, JavaToolchainSpec spec, JvmInstallationMetadata metadata) { + if (!installFolder.exists()) { + return; //install folder doesn't even exist + } + + File[] filesInInstallFolder = installFolder.listFiles(); + if (filesInInstallFolder == null || filesInInstallFolder.length == 0) { + return; //no files in install folder + } + + File markerLocation = markedLocation(installFolder); + if (!isMarkedLocation(markerLocation)) { + return; //no marker found + } + + String leftoverMetadata; + try { + leftoverMetadata = getMetadata(markerLocation).toString(); + } catch (Exception e) { + LOGGER.debug("Failed determining metadata of installation leftover", e); + leftoverMetadata = "Could not be determined due to: " + e.getMessage(); + } + LOGGER.warn("While provisioning Java toolchain from '{}' to satisfy spec '{}' (with metadata '{}'), " + + "leftover content (with metadata '{}') was found in the install folder '{}'. " + + "The existing installation will be replaced by the new download.", + uri, spec, metadata, leftoverMetadata, installFolder); + } + + private JvmInstallationMetadata getMetadata(File markedLocation) { File javaHome = getJavaHome(markedLocation); JvmInstallationMetadata metadata = detector.getMetadata(new InstallationLocation(javaHome, "provisioned toolchain")); if (!metadata.isValidInstallation()) { - throw new GradleException("Provisioned toolchain '" + javaHome + "' could not be probed."); - } - if (!new JavaToolchainMatcher(spec).test(metadata)) { - throw new GradleException("Toolchain provisioned from '" + uri + "' doesn't satisfy the specification: " + spec.getDisplayName() + "."); + throw new GradleException("Provisioned toolchain '" + javaHome + "' could not be probed: " + metadata.getErrorMessage(), metadata.getErrorCause()); } - File installFolder = new File(jdkDirectory, toDirectoryName(metadata)); - operations.copy(copySpec -> { - copySpec.from(unpackFolder); - copySpec.into(installFolder); - }); - operations.delete(unpackFolder); + return metadata; + } - LOGGER.info("Installed toolchain from {} into {}", uri, installFolder); + private File markLocationInsideFolder(File unpackedInstallationFolder) { + File markedLocation = markedLocation(unpackedInstallationFolder); + markAsReady(markedLocation); + return markedLocation; + } - return getJavaHome(markedLocation(installFolder)); + private static void validateMetadataMatchesSpec(JavaToolchainSpec spec, URI uri, JvmInstallationMetadata metadata) { + if (!new JavaToolchainMatcher(spec).test(metadata)) { + throw new GradleException("Toolchain provisioned from '" + uri + "' doesn't satisfy the specification: " + spec.getDisplayName() + "."); + } } - private static String toDirectoryName(JvmInstallationMetadata metadata) { + private static String getInstallFolderName(JvmInstallationMetadata metadata) { String vendor = metadata.getJvmVendor(); if (vendor == null || vendor.isEmpty()) { vendor = metadata.getVendor().getRawVendor(); diff --git a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/internal/JavaToolchainQueryServiceTest.groovy b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/internal/JavaToolchainQueryServiceTest.groovy index 4f0574a37b39..4372dc3df3ff 100644 --- a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/internal/JavaToolchainQueryServiceTest.groovy +++ b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/internal/JavaToolchainQueryServiceTest.groovy @@ -287,7 +287,7 @@ class JavaToolchainQueryServiceTest extends Specification { then: def e = thrown(GradleException) - e.message == "Provisioned toolchain '${File.separator}path${File.separator}12.broken' could not be probed." + e.message == "Toolchain installation '${File.separator}path${File.separator}12.broken' could not be probed." } def "provisioned toolchain is cached no re-request"() { diff --git a/subprojects/soak/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadComplexProjectSoakTest.groovy b/subprojects/soak/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadComplexProjectSoakTest.groovy new file mode 100644 index 000000000000..6b238975f95d --- /dev/null +++ b/subprojects/soak/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadComplexProjectSoakTest.groovy @@ -0,0 +1,90 @@ +/* + * Copyright 2022 the original author or authors. + * + * 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 org.gradle.jvm.toolchain + +import org.gradle.integtests.fixtures.AbstractIntegrationSpec + +class JavaToolchainDownloadComplexProjectSoakTest extends AbstractIntegrationSpec { + + def setup() { + executer.requireOwnGradleUserHomeDir() + executer.withToolchainDownloadEnabled() + } + + def "multiple subprojects with identical toolchain definitions"() { + given: + settingsFile << settingsForBuildWithSubprojects() + + setupSubproject("subproject1", "Foo", "ADOPTIUM") + setupSubproject("subproject2", "Bar", "ADOPTIUM") + + when: + result = executer + .withTasks("compileJava") + .run() + + then: + !result.plainTextOutput.matches("(?s).*The existing installation will be replaced by the new download.*") + } + + def "multiple subprojects with different toolchain definitions"() { + given: + settingsFile << settingsForBuildWithSubprojects() + + setupSubproject("subproject1", "Foo", "ADOPTIUM") + setupSubproject("subproject2", "Bar", "ORACLE") + + when: + result = executer + .withTasks("compileJava") + .withArgument("--info") + .run() + + then: + result.plainTextOutput.matches("(?s).*Compiling with toolchain.*adoptium.*") + result.plainTextOutput.matches("(?s).*Compiling with toolchain.*oracle.*") + } + + private String settingsForBuildWithSubprojects() { + return """ + plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.2' + } + rootProject.name = 'main' + + include('subproject1') + include('subproject2') + """ + } + + private void setupSubproject(String subprojectName, String className, String vendorName) { + file("${subprojectName}/build.gradle") << """ + plugins { + id 'java' + } + + java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + vendor = JvmVendorSpec.${vendorName} + } + } + """ + file("${subprojectName}/src/main/java/${className}.java") << "public class ${className} {}" + } + +} diff --git a/subprojects/soak/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadSoakTest.groovy b/subprojects/soak/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadSoakTest.groovy index 444468b5b1a2..c8bc3cf7c82b 100644 --- a/subprojects/soak/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadSoakTest.groovy +++ b/subprojects/soak/src/integTest/groovy/org/gradle/jvm/toolchain/JavaToolchainDownloadSoakTest.groovy @@ -37,14 +37,13 @@ class JavaToolchainDownloadSoakTest extends AbstractIntegrationSpec { executer.requireOwnGradleUserHomeDir() executer - .withToolchainDetectionEnabled() .withToolchainDownloadEnabled() } def "can download missing jdk automatically"() { when: result = executer - .withTasks("compileJava", "-Porg.gradle.java.installations.auto-detect=false") + .withTasks("compileJava") .expectDocumentedDeprecationWarning("Java toolchain auto-provisioning needed, but no java toolchain repositories declared by the build. Will rely on the built-in repository. " + "This behaviour has been deprecated and is scheduled to be removed in Gradle 8.0. " + "In order to declare a repository for java toolchains, you must edit your settings script and add one via the toolchainManagement block. " + @@ -67,7 +66,7 @@ class JavaToolchainDownloadSoakTest extends AbstractIntegrationSpec { when: result = executer - .withTasks("compileJava", "-Porg.gradle.java.installations.auto-detect=false") + .withTasks("compileJava") .expectDocumentedDeprecationWarning("Java toolchain auto-provisioning needed, but no java toolchain repositories declared by the build. Will rely on the built-in repository. " + "This behaviour has been deprecated and is scheduled to be removed in Gradle 8.0. " + "In order to declare a repository for java toolchains, you must edit your settings script and add one via the toolchainManagement block. " +