From eaacb62442192a3ea02e98dd5fc196acf1f0f83e Mon Sep 17 00:00:00 2001 From: Louis Jacomet Date: Thu, 2 Mar 2023 22:26:18 +0100 Subject: [PATCH] Make publishing incompatible with CC in some cases When using unsafe credentials, we mark the publication task as incompatible with CC. This helps user experience instead of having weird failures on CC load. Issue #24122 --- .../maven/MavenPublishHttpIntegTest.groovy | 17 +++++-- .../maven/plugins/MavenPublishPlugin.java | 49 ++++++++++++++++++- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpIntegTest.groovy index f57ca86c1737..96c2ea97a80d 100644 --- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpIntegTest.groovy +++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpIntegTest.groovy @@ -18,7 +18,7 @@ package org.gradle.api.publish.maven import org.gradle.api.credentials.Credentials import org.gradle.api.credentials.PasswordCredentials -import org.gradle.integtests.fixtures.UnsupportedWithConfigurationCache +import org.gradle.integtests.fixtures.executer.GradleContextualExecuter import org.gradle.integtests.fixtures.publish.maven.AbstractMavenPublishIntegTest import org.gradle.internal.credentials.DefaultPasswordCredentials import org.gradle.test.fixtures.server.http.AuthScheme @@ -325,7 +325,6 @@ class MavenPublishHttpIntegTest extends AbstractMavenPublishIntegTest { /** * @see org.gradle.configurationcache.ConfigurationCachePublishingIntegrationTest */ - @UnsupportedWithConfigurationCache(because="Unsafe/inline credentials not supported with CC") def "cannot publish to authenticated repository using credentials Provider with inferred identity if repo has incompatible name"() { given: buildFile << publicationBuild(version, group, mavenRemoteRepo.uri, "incompatible_repo_name") @@ -335,11 +334,14 @@ class MavenPublishHttpIntegTest extends AbstractMavenPublishIntegTest { fails 'publish' then: - failure.assertHasDescription("Execution failed for task ':publishMavenPublicationToIncompatible_repo_nameRepository'.") + if (GradleContextualExecuter.isConfigCache()) { + failure.assertHasDescription("Configuration cache state could not be cached:") + } else { + failure.assertHasDescription("Execution failed for task ':publishMavenPublicationToIncompatible_repo_nameRepository'.") + } failure.assertHasCause("Identity may contain only letters and digits, received: incompatible_repo_name") } - @UnsupportedWithConfigurationCache(because="Unsafe/inline credentials not supported with CC") def "can publish to authenticated repository using inlined credentials"() { given: PasswordCredentials credentials = new DefaultPasswordCredentials('username', 'password') @@ -357,9 +359,11 @@ class MavenPublishHttpIntegTest extends AbstractMavenPublishIntegTest { then: module.assertPublishedAsJavaModule() + if (GradleContextualExecuter.isConfigCache()) { + postBuildOutputContains("Configuration cache entry discarded") + } } - @UnsupportedWithConfigurationCache(because="Unsafe/inline credentials not supported with CC") def "can publish to authenticated repository with name not valid as identity as long as one uses inlined credentials "() { given: PasswordCredentials credentials = new DefaultPasswordCredentials('username', 'password') @@ -380,6 +384,9 @@ class MavenPublishHttpIntegTest extends AbstractMavenPublishIntegTest { then: module.assertPublishedAsJavaModule() + if (GradleContextualExecuter.isConfigCache()) { + postBuildOutputContains("Configuration cache entry discarded") + } } def "fails at configuration time with helpful error message when username and password provider has no value"() { diff --git a/subprojects/maven/src/main/java/org/gradle/api/publish/maven/plugins/MavenPublishPlugin.java b/subprojects/maven/src/main/java/org/gradle/api/publish/maven/plugins/MavenPublishPlugin.java index de2721b3a747..e9baeb2b3c72 100644 --- a/subprojects/maven/src/main/java/org/gradle/api/publish/maven/plugins/MavenPublishPlugin.java +++ b/subprojects/maven/src/main/java/org/gradle/api/publish/maven/plugins/MavenPublishPlugin.java @@ -16,6 +16,7 @@ package org.gradle.api.publish.maven.plugins; +import org.apache.commons.lang.builder.EqualsBuilder; import org.gradle.api.NamedDomainObjectFactory; import org.gradle.api.NamedDomainObjectList; import org.gradle.api.NamedDomainObjectSet; @@ -24,13 +25,18 @@ import org.gradle.api.Task; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.attributes.Usage; +import org.gradle.api.credentials.Credentials; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.internal.artifacts.Module; import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider; +import org.gradle.api.internal.artifacts.repositories.DefaultMavenArtifactRepository; import org.gradle.api.internal.attributes.ImmutableAttributesFactory; import org.gradle.api.internal.file.FileResolver; +import org.gradle.api.internal.project.ProjectInternal; +import org.gradle.api.internal.provider.MissingValueException; import org.gradle.api.internal.tasks.TaskDependencyFactory; import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; import org.gradle.api.publish.PublishingExtension; import org.gradle.api.publish.internal.versionmapping.DefaultVersionMappingStrategy; @@ -126,7 +132,7 @@ private void realizePublishingTasksLater(final Project project, final Publishing createGenerateMetadataTask(tasks, publication, mavenPublications, buildDirectory); createGeneratePomTask(tasks, publication, buildDirectory, project); createLocalInstallTask(tasks, publishLocalLifecycleTask, publication); - createPublishTasksForEachMavenRepo(tasks, publishLifecycleTask, publication, repositories); + createPublishTasksForEachMavenRepo(project, tasks, publishLifecycleTask, publication, repositories); }); } @@ -134,7 +140,7 @@ private String publishAllToSingleRepoTaskName(MavenArtifactRepository repository return "publishAllPublicationsTo" + capitalize(repository.getName()) + "Repository"; } - private void createPublishTasksForEachMavenRepo(final TaskContainer tasks, final TaskProvider publishLifecycleTask, final MavenPublicationInternal publication, final NamedDomainObjectList repositories) { + private void createPublishTasksForEachMavenRepo(final Project project, final TaskContainer tasks, final TaskProvider publishLifecycleTask, final MavenPublicationInternal publication, final NamedDomainObjectList repositories) { final String publicationName = publication.getName(); repositories.all(repository -> { final String repositoryName = repository.getName(); @@ -144,12 +150,51 @@ private void createPublishTasksForEachMavenRepo(final TaskContainer tasks, final publishTask.setRepository(repository); publishTask.setGroup(PublishingPlugin.PUBLISH_TASK_GROUP); publishTask.setDescription("Publishes Maven publication '" + publicationName + "' to Maven repository '" + repositoryName + "'."); + project.getGradle().getTaskGraph().whenReady(graph -> { + if (graph.hasTask(publishTask)) { + validateCredentialsSetup(project, publishTask); + } + }); }); + publishLifecycleTask.configure(task -> task.dependsOn(publishTaskName)); tasks.named(publishAllToSingleRepoTaskName(repository), publish -> publish.dependsOn(publishTaskName)); }); } + private static void validateCredentialsSetup(Project project, PublishToMavenRepository publishToMavenRepository) { + DefaultMavenArtifactRepository repository = (DefaultMavenArtifactRepository) publishToMavenRepository.getRepository(); + Credentials creds; + try { + creds = repository.getConfiguredCredentials().getOrNull(); + } catch (Exception e) { + // In case of exception, we assume compatibility as this will fail later as well + creds = null; + } + if (creds != null && !isUsingCredentialsProvider((ProjectInternal) project, repository.getName(), creds)) { + publishToMavenRepository.notCompatibleWithConfigurationCache("Publishing to a repository without a credentials provider is not yet supported for the configuration cache"); + } + } + + private static boolean isUsingCredentialsProvider(ProjectInternal project, String identity, Credentials toCheck) { + ProviderFactory providerFactory = project.getServices().get(ProviderFactory.class); + Credentials referenceCredentials; + try { + Provider credentialsProvider; + try { + credentialsProvider = providerFactory.credentials(toCheck.getClass(), identity); + } catch (IllegalArgumentException e) { + // some possibilities are invalid repository names and invalid credential types + // either way, this is not the place to validate that + return false; + } + referenceCredentials = credentialsProvider.get(); + } catch (MissingValueException e) { + return false; + } + return EqualsBuilder.reflectionEquals(toCheck, referenceCredentials); + } + private void createLocalInstallTask(TaskContainer tasks, final TaskProvider publishLocalLifecycleTask, final MavenPublicationInternal publication) { final String publicationName = publication.getName(); final String installTaskName = "publish" + capitalize(publicationName) + "PublicationToMavenLocal";