Skip to content

Commit

Permalink
Handle dependent artifacts that have multiple files (#130)
Browse files Browse the repository at this point in the history
Turns out, `ArtifactCollection.artifactFiles` coalesces multiple files
associated with the same artifact.

This manifests itself when e.g. a subproject dependency has a mix of
kotlin and java sources and thus has two class output directories
(build/classes/kotlin/main and build/classes/java/main). One of the
output directories will be "lost" and won't be "seen" by the Gradle
task.
  • Loading branch information
ogolberg authored Nov 6, 2024
1 parent ea7351b commit 733239b
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ abstract class ExpediterTask : DefaultTask(), TaskWithProjectOutputs {
if (platformConfigurationArtifacts.isNotEmpty()) {
providers.add(
InMemoryPlatformTypeProvider(
ClasspathScanner(platformConfigurationArtifacts.flatMap { it.artifacts.map { it.source() } }).scan { i, _ -> TypeParsers.typeDescriptor(i) }
ClasspathScanner(platformConfigurationArtifacts.sources()).scan { i, _ -> TypeParsers.typeDescriptor(i) }
)
)
}
Expand Down
55 changes: 44 additions & 11 deletions plugin/src/main/kotlin/com/toasttab/expediter/gradle/Sources.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,58 @@ import com.toasttab.expediter.types.ClassfileSourceType
import org.gradle.api.artifacts.ArtifactCollection
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import org.gradle.api.attributes.AttributeContainer
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.internal.artifacts.configurations.ArtifactCollectionInternal
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ArtifactVisitor
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ResolvableArtifact
import org.gradle.api.provider.ListProperty
import org.gradle.internal.DisplayName
import org.gradle.internal.component.external.model.ImmutableCapabilities
import java.io.File

fun ResolvedArtifactResult.source() =
when (id.componentIdentifier) {
is ModuleComponentIdentifier -> ClassfileSource(file, ClassfileSourceType.EXTERNAL_DEPENDENCY)
is ProjectComponentIdentifier -> ClassfileSource(file, ClassfileSourceType.SUBPROJECT_DEPENDENCY)
else -> ClassfileSource(file, ClassfileSourceType.UNKNOWN)
}

fun File.source() = ClassfileSource(this, ClassfileSourceType.UNKNOWN)

fun ArtifactCollection.sources() = artifacts.map { it.source() }

fun ConfigurableFileCollection.sources() = map { it.source() }

fun Collection<ArtifactCollection>.sources() = flatMapTo(LinkedHashSet(), ArtifactCollection::sources)
fun ResolvableArtifact.source() = when (id.componentIdentifier) {
is ModuleComponentIdentifier -> ClassfileSource(file, ClassfileSourceType.EXTERNAL_DEPENDENCY)
is ProjectComponentIdentifier -> ClassfileSource(file, ClassfileSourceType.SUBPROJECT_DEPENDENCY)
else -> ClassfileSource(file, ClassfileSourceType.UNKNOWN)
}

private fun ArtifactCollectionInternal.sources(): Collection<ClassfileSource> {
val sources = mutableListOf<ClassfileSource>()

// Note that ArtifactCollection.artifactFiles coalesces multiple files associated
// with the same artifact; for example, when a project dependency has multiple outputs
// (e.g. build/classes/kotlin/main and build/classes/java/main), only one of those
// outputs will be present in ArtifactCollection.artifactFiles.
//
// To deal with that, we visit all artifacts, similarly to the implementation
// of ArtifactCollection.artifactFiles, but we don't coalesce the files.
visitArtifacts(object : ArtifactVisitor {
override fun visitArtifact(
variantName: DisplayName,
variantAttributes: AttributeContainer,
capabilities: ImmutableCapabilities,
artifact: ResolvableArtifact
) {
sources.add(artifact.source())
}

override fun requireArtifactFiles() = true

override fun visitFailure(failure: Throwable) {
}
})

return sources
}

fun Collection<ArtifactCollection>.sources() = flatMapTo(LinkedHashSet()) {
(it as ArtifactCollectionInternal).sources()
}

fun ListProperty<out FileSystemLocation>.sources() = get().map { ClassfileSource(it.asFile, ClassfileSourceType.PROJECT) }
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,11 @@ class ExpediterPluginIntegrationTest {
expectThat(report.issues).filterIsInstance<Issue.DuplicateType>().isEmpty()
}

@ParameterizedWithGradleVersions
fun `multiple outputs`(project: TestProject) {
project.build("check")
}

@ParameterizedWithGradleVersions
fun `ignore`(project: TestProject) {
project.build("check")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
java
id("com.toasttab.expediter") version "@VERSION@"
}

expediter {
failOnIssues = true

platform {
jvmVersion = 8
}
}

dependencies {
implementation(project(":lib"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2024 Toast Inc.
*
* 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 test;

public class Caller {
Callee callee;
Callee1 callee1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
plugins {
id("com.toasttab.testkit.coverage") version "@TESTKIT_PLUGIN_VERSION@"
}

subprojects {
repositories {
mavenCentral()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
plugins {
java
kotlin("jvm") version "@KOTLIN_VERSION@"
id("com.toasttab.expediter") version "@VERSION@"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2024 Toast Inc.
*
* 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 test;

public class Callee1 {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2024 Toast Inc.
*
* 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 test

class Callee
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
rootProject.name = "test"

include(":lib", ":app")

0 comments on commit 733239b

Please sign in to comment.