Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Several Gradle improvements #27

Closed
wants to merge 1 commit into from

Conversation

eygraber
Copy link
Contributor

This needs to be cleaned up (and possibly broken up into multiple PRs) but I wanted to get opinions on this before doing any more work.

Comment on lines -114 to -122
Provider<Set<ResolvedArtifactResult>> artifacts =
project
.getConfigurations()
.getByName(configurationName)
.getIncoming()
.getArtifacts()
.getResolvedArtifacts();
t.getResolvedArtifacts().putAll(artifacts.map(new ArtifactTransformer()));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was causing resolution issues in my project because of variants and mismatched attributes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you describe the issue a little more for us to understand?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish I could 😅

Gradle has a concept of variants, and for some reason this code causes Gradle to use the wrong attributes when resolving some dependencies from some configurations.

The issue with the pomsConfig below is similar, and I think it is related to detached configurations not copying the attributes. I tried manually copying the attributes from the original configuration to the detached one, but there where still issues.

This is an area that I don't have the most expertise in with Gradle, so I'm not much help here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm working on a standalone repro, but in the meantime here are some of the failures from my project:

spdxSbom {
  targets {
    create("release") {
      configurations.set(listOf("devDebugRuntimeClasspath"))
    }
  }
}
> Could not create task ':app:spdxSbomForRelease'.
   > Could not resolve all artifacts for configuration ':app:detachedConfiguration9'.
      > Could not resolve com.eygraber:android-date-time-input-view:0.9.16.
        Required by:
            project :app
         > Cannot choose between the following variants of com.eygraber:android-date-time-input-view:0.9.16:
             - debugVariantMavenRuntimePublication
             - releaseVariantMavenRuntimePublication
           All of them match the consumer attributes:
             - Variant 'debugVariantMavenRuntimePublication' capability com.eygraber:android-date-time-input-view:0.9.16:
                 - Unmatched attributes:
                     - Provides com.android.build.api.attributes.BuildTypeAttr 'debug' but the consumer didn't ask for it
                     - Provides org.gradle.category 'library' but the consumer didn't ask for it
                     - Provides org.gradle.dependency.bundling 'external' but the consumer didn't ask for it
                     - Provides org.gradle.libraryelements 'aar' but the consumer didn't ask for it
                     - Provides org.gradle.status 'release' but the consumer didn't ask for it
                     - Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it
             - Variant 'releaseVariantMavenRuntimePublication' capability com.eygraber:android-date-time-input-view:0.9.16:
                 - Unmatched attributes:
                     - Provides com.android.build.api.attributes.BuildTypeAttr 'release' but the consumer didn't ask for it
                     - Provides org.gradle.category 'library' but the consumer didn't ask for it
                     - Provides org.gradle.dependency.bundling 'external' but the consumer didn't ask for it
                     - Provides org.gradle.libraryelements 'aar' but the consumer didn't ask for it
                     - Provides org.gradle.status 'release' but the consumer didn't ask for it
                     - Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it
      > Could not resolve com.eygraber:android-date-time-input-common:0.9.16.
        Required by:
            project :app
         > Cannot choose between the following variants of com.eygraber:android-date-time-input-common:0.9.16:
             - debugVariantMavenRuntimePublication
             - releaseVariantMavenRuntimePublication
           All of them match the consumer attributes:
             - Variant 'debugVariantMavenRuntimePublication' capability com.eygraber:android-date-time-input-common:0.9.16:
                 - Unmatched attributes:
                     - Provides com.android.build.api.attributes.BuildTypeAttr 'debug' but the consumer didn't ask for it
                     - Provides org.gradle.category 'library' but the consumer didn't ask for it
                     - Provides org.gradle.dependency.bundling 'external' but the consumer didn't ask for it
                     - Provides org.gradle.libraryelements 'aar' but the consumer didn't ask for it
                     - Provides org.gradle.status 'release' but the consumer didn't ask for it
                     - Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it
             - Variant 'releaseVariantMavenRuntimePublication' capability com.eygraber:android-date-time-input-common:0.9.16:
                 - Unmatched attributes:
                     - Provides com.android.build.api.attributes.BuildTypeAttr 'release' but the consumer didn't ask for it
                     - Provides org.gradle.category 'library' but the consumer didn't ask for it
                     - Provides org.gradle.dependency.bundling 'external' but the consumer didn't ask for it
                     - Provides org.gradle.libraryelements 'aar' but the consumer didn't ask for it
                     - Provides org.gradle.status 'release' but the consumer didn't ask for it
                     - Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it
      > Could not resolve app.cash.sqldelight:android-driver:2.0.0-alpha05.
        Required by:
            project :app
         > Cannot choose between the following variants of app.cash.sqldelight:android-driver:2.0.0-alpha05:
             - debugVariantMavenRuntimePublication
             - releaseVariantMavenRuntimePublication
           All of them match the consumer attributes:
             - Variant 'debugVariantMavenRuntimePublication' capability app.cash.sqldelight:android-driver:2.0.0-alpha05:
                 - Unmatched attributes:
                     - Provides com.android.build.api.attributes.BuildTypeAttr 'debug' but the consumer didn't ask for it
                     - Provides org.gradle.category 'library' but the consumer didn't ask for it
                     - Provides org.gradle.dependency.bundling 'external' but the consumer didn't ask for it
                     - Provides org.gradle.libraryelements 'aar' but the consumer didn't ask for it
                     - Provides org.gradle.status 'release' but the consumer didn't ask for it
                     - Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it
             - Variant 'releaseVariantMavenRuntimePublication' capability app.cash.sqldelight:android-driver:2.0.0-alpha05:
                 - Unmatched attributes:
                     - Provides com.android.build.api.attributes.BuildTypeAttr 'release' but the consumer didn't ask for it
                     - Provides org.gradle.category 'library' but the consumer didn't ask for it
                     - Provides org.gradle.dependency.bundling 'external' but the consumer didn't ask for it
                     - Provides org.gradle.libraryelements 'aar' but the consumer didn't ask for it
                     - Provides org.gradle.status 'release' but the consumer didn't ask for it
                     - Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it

Comment on lines -131 to -147
Configuration pomsConfig =
project
.getConfigurations()
.detachedConfiguration(
project
.getConfigurations()
.getByName(configurationName)
.getIncoming()
.getResolutionResult()
.getAllComponents()
.stream()
.filter(rcr -> rcr.getId() instanceof ModuleComponentIdentifier)
.map(rcr -> rcr.getId().getDisplayName() + "@pom")
.map(pom -> project.getDependencies().create(pom))
.toArray(Dependency[]::new));
t.getPoms()
.putAll(PomResolver.newPomResolver(project).effectivePoms(pomsConfig));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was causing resolution issues in my project because of variants and mismatched attributes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 129 to 137
private Map<String, ArtifactRepository> getAllRepositories(Project project) {
Map<String, ArtifactRepository> projectRepositories = project.getRepositories().getAsMap();

// nasty case because of https://github.com/gradle/gradle/issues/17295
Map<String, ArtifactRepository> settingsRepositories = ((GradleInternal) project.getGradle())
.getSettings()
.getDependencyResolutionManagement()
.getRepositories()
.getAsMap();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only use dependencyResolutionManagement.repositories to add my repositories, so the project one is empty. There could technically be repositories added in both places, so that's why I'm doing this.

Comment on lines 64 to 65
@Inject protected abstract DependencyHandler getDependencyHandler();
@Inject protected abstract ConfigurationContainer getConfigurations();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Injecting like this allows the task to be configuration cache compliant.

Comment on lines 99 to 127
Set<ComponentIdentifier> componentIds = new HashSet<>();
for (var rootComponent : getRootComponents().get()) {
componentIds.addAll(
gatherSelectedDependencies(
rootComponent,
new HashSet<>(),
new HashSet<>()
)
);
}

Map<ComponentArtifactIdentifier, File> resolvedArtifactsById = new HashMap<>();

@SuppressWarnings("unchecked")
List<ResolvedArtifactResult> resolvedArtifactResults =
getDependencyHandler()
.createArtifactResolutionQuery()
.forComponents(componentIds)
.withArtifacts(MavenModule.class, MavenPomArtifact.class)
.execute()
.getResolvedComponents()
.stream()
.flatMap(componentArtifactsResult -> componentArtifactsResult.getArtifacts(MavenPomArtifact.class).stream())
.filter(artifactResult -> artifactResult instanceof ResolvedArtifactResult)
.map(artifactResult -> (ResolvedArtifactResult) artifactResult)
.peek(resolvedArtifactResult -> resolvedArtifactsById.put(resolvedArtifactResult.getId(), resolvedArtifactResult.getFile()))
.collect(Collectors.toList());

PomResolver pomResolver = PomResolver.newPomResolver(getDependencyHandler(), getConfigurations(), getLogger());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests and functionalTests both pass with this, and my project looks OK with it. I think it should be fine, but I'm not 100% sure.

@eygraber eygraber force-pushed the gradle-improvements branch 2 times, most recently from 4b690c1 to 45ffa80 Compare June 13, 2023 10:33
Map<ComponentArtifactIdentifier, File> resolvedArtifactsById = new HashMap<>();

@SuppressWarnings("unchecked")
List<ResolvedArtifactResult> resolvedArtifactResults =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess fundamentally, is this okay by gradle's build conventions? Resolving artifacts at task execution time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been using this in my project for ~8 months and it's been working great. I think it just looks at the result of the resolution that is done before execution time. The javadoc for ArtifactResolutionQuery shows an example of using it in a task.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So further research is telling me this is not compatible with task configuration avoidance, someone from game recommended against this approach

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure Gradle uses this approach themselves, and I've been using it with task configuration avoidance for a while now. I can look for more info from Gradle on this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotcha, I'll see if I can validate what I've been told too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a configuration cache perspective it should be fine

Waiting to hear from someone at Gradle conclusively about usage of the API in general, but in this case the existing code is using the same underlying API, and it won't work with projects using variants, so it's probably fine.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked very closely, but Gradle has been thinking about disallowing resolving dependencies at configuration time: gradle/gradle#2298 . Are there any other times at which we might resolve these artifacts? (Resolving dependencies during task execution makes sense to me)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't heard from anyone at gradle directly, but it looks like they're working on a new API too address this. Until that's released this is one of at least two options.

The other option is something that CashApp is doing in Licensee where they used detached configurations and copy all the variant information into it. I think the sentiment is that this solution is more "correct" (despite being a little hacky), but it is vastly more complex.

Comment on lines -114 to -122
Provider<Set<ResolvedArtifactResult>> artifacts =
project
.getConfigurations()
.getByName(configurationName)
.getIncoming()
.getArtifacts()
.getResolvedArtifacts();
t.getResolvedArtifacts().putAll(artifacts.map(new ArtifactTransformer()));

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you describe the issue a little more for us to understand?

@loosebazooka
Copy link
Collaborator

@eygraber just fyi I'm on parental leave and will be quite slow to respond on these.

@loosebazooka
Copy link
Collaborator

loosebazooka commented Oct 30, 2023

Hey @eygraber, sorry I let this PR kinda fall to the side. Are there parts specifically that are still failing with 0.3.0, that we can begin to address again. Would prefer each issue is its own PR if possible (as mentioned up top).

@eygraber
Copy link
Contributor Author

@loosebazooka I tested 0.3.0 (I've been using my own internal builds for now) and it is still failing because of the mismatched variants and attributes.

As discussed here this PR has a fix for that, but Gradle plans on removing that workaround in the future. They don't have a proper workaround for now. There are other workarounds that involve a lot more work, but aren't using APIs that Gradle plans on removing (for now).

I think there's one other issue addressed here that I can separate out (i.e. ArtifactRepository not getting picked up from settings files).

@loosebazooka
Copy link
Collaborator

Cool, lets do the ArtifactRepository one at least for now. That seems pretty straight forward?

@eygraber
Copy link
Contributor Author

eygraber commented Nov 7, 2023

I got some guidance from Gradle on how to handle the rest of it

So in general we strongly discourage doing any sort of dependency resolution at execution time.
Would you be able to add an @input to your task with some Property, and then configure the value of that property with the result of your ARQ? That way, the ARQ is executed during serialization-time, in between configuration time and execution time
So something along the lines of

abstract class MyTask {
    @Input
    abstract MapProperty<String, PomInfo> getPoms()
}

tasks.register("doStuff", MyTask) {
    poms = project.provider(() -> {
        // Do ARQ and effective POM calculations here
    })
}

I'm a little busy at the moment, but I'll get back to all of this when I have a few minutes.

@eygraber
Copy link
Contributor Author

eygraber commented Nov 8, 2023

Replaced by #70, #71, and #72

@eygraber eygraber closed this Nov 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants