diff --git a/buildSrc/src/main/kotlin/dagger/gradle/build/ResourceCopyTask.kt b/buildSrc/src/main/kotlin/dagger/gradle/build/ResourceCopyTask.kt new file mode 100644 index 00000000000..06bb8e8e804 --- /dev/null +++ b/buildSrc/src/main/kotlin/dagger/gradle/build/ResourceCopyTask.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2025 The Dagger 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 dagger.gradle.build + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.MapProperty +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction +import org.gradle.work.DisableCachingByDefault + +/** + * A task for copying JAR resources files located in the repository structure into a generated resource source set + * that matches the JAR's resources structure. This is necessary due to the repository's structure not being the + * standard Gradle source set structure. + */ +@DisableCachingByDefault(because = "Not worth caching") +abstract class ResourceCopyTask : DefaultTask() { + + /** + * Specifications of resource files to copy and their destination directory within the JAR. + */ + @get:Input + abstract val resourceSpecs: MapProperty + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val inputFiles: ListProperty + + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + @TaskAction + fun execute() { + val specMap = resourceSpecs.get() + inputFiles.get().forEach { resourceFile -> + val inputFile = resourceFile.asFile + check(inputFile.exists()) { + "Resource file does not exist: $inputFile" + } + check(inputFile.isFile) { + "Resource file must be a file not a directory: $inputFile" + } + val jarOutputDir = specMap.getValue(inputFile.path) + val outputFile = outputDirectory.get().dir(jarOutputDir).file(inputFile.name).asFile + inputFile.copyTo(outputFile, overwrite = true) + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/dagger/gradle/build/SourceSetConfiguration.kt b/buildSrc/src/main/kotlin/dagger/gradle/build/SourceSetConfiguration.kt index e60d912f5de..06975cca17d 100644 --- a/buildSrc/src/main/kotlin/dagger/gradle/build/SourceSetConfiguration.kt +++ b/buildSrc/src/main/kotlin/dagger/gradle/build/SourceSetConfiguration.kt @@ -20,6 +20,7 @@ import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project import org.gradle.api.file.SourceDirectorySet import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.TaskProvider import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import java.nio.file.Path @@ -37,34 +38,111 @@ class DaggerSourceSet( private val kotlinSourceSets: NamedDomainObjectContainer, private val javaSourceSets: NamedDomainObjectContainer, ) { + private val resourceCopyTask: TaskProvider = + project.tasks.register("copyResources", ResourceCopyTask::class.java) { + outputDirectory.set(project.layout.buildDirectory.dir("generated/resources")) + } + + init { + listOf(resourceCopyTask.map { it.outputDirectory }).let { + kotlinSourceSets.named("main").configure { resources.setSrcDirs(it) } + javaSourceSets.named("main").configure { resources.setSrcDirs(it) } + } + } + + /** + * The main source set whose based path is `/java` + */ val main: SourceSet = object : SourceSet { override fun setPackages(packages: List) { val packagePaths = packages.map { Path(it) } - kotlinSourceSets.getByName("main").kotlin - .includePackages("${project.rootDir}/java", packagePaths) - javaSourceSets.getByName("main").java - .includePackages("${project.rootDir}/java", packagePaths) + kotlinSourceSets.named("main").configure { + kotlin.includePackages("${project.rootDir}/java", packagePaths) + } + javaSourceSets.named("main").configure { + java.includePackages("${project.rootDir}/java", packagePaths) + } + } + + override fun setResources(resources: Map) { + resourceCopyTask.configure { + val baseDir = project.rootProject.layout.projectDirectory.dir("java") + resources.forEach { (resourceFilePath, jarDirectoryPath) -> + val resource = baseDir.file(resourceFilePath) + resourceSpecs.put(resource.asFile.path, jarDirectoryPath) + inputFiles.add(resource) + } + } } } + + /** + * The main source set whose based path is `/javatests` + */ val test: SourceSet = object : SourceSet { override fun setPackages(packages: List) { val packagePaths = packages.map { Path(it) } - kotlinSourceSets.getByName("test").kotlin - .includePackages("${project.rootDir}/javatests", packagePaths) - javaSourceSets.getByName("test").java - .includePackages("${project.rootDir}/javatests", packagePaths) + kotlinSourceSets.named("test").configure { + kotlin.includePackages("${project.rootDir}/javatests", packagePaths) + } + javaSourceSets.named("test").configure { + java.includePackages("${project.rootDir}/javatests", packagePaths) + } + } + + override fun setResources(resources: Map) { + throw UnsupportedOperationException( + "Resources are only configurable for the 'main' source set." + ) } } interface SourceSet { + /** + * Sets the list of source packages that are part of the project's source set. + * + * Only sources directly in those packages are included and not in its subpackages. + * + * Example usage: + * ``` + * daggerSources { + * main.setPackages( + * listOf( + * "dagger", + * "dagger/assisted", + * "dagger/internal", + * "dagger/multibindings", + * ) + * ) + * } + * ``` + * @see daggerSources + */ fun setPackages(packages: List) + + /** + * Sets the resource file paths and their corresponding artifact location. + * + * Example usage: + * ``` + * daggerSources { + * main.setResources( + * mapOf("dagger/r8.pro" to "META-INF/com.android.tools/r8/") + * ) + * } + * ``` + * @see daggerSources + */ + fun setResources(resources: Map) } } /** * Configure project's source set based on Dagger's project structure. * - * Specifically it will include sources in the packages specified by [DaggerSourceSet.SourceSet.setPackages]. + * Specifically it will include sources in the packages specified by + * [DaggerSourceSet.SourceSet.setPackages] and resources as specified by + * [DaggerSourceSet.SourceSet.setResources]. */ fun Project.daggerSources(block: DaggerSourceSet.() -> Unit) { val kotlinExtension = extensions.findByType(KotlinProjectExtension::class.java) diff --git a/gradle-projects/dagger-runtime/build.gradle.kts b/gradle-projects/dagger-runtime/build.gradle.kts index 8a1dceeebde..cb0054f5ddf 100644 --- a/gradle-projects/dagger-runtime/build.gradle.kts +++ b/gradle-projects/dagger-runtime/build.gradle.kts @@ -4,7 +4,6 @@ plugins { alias(libs.plugins.dagger.kotlinJvm) } -// TODO(danysantiago): Add proguard files as META-INF resources daggerSources { main.setPackages( listOf( @@ -14,6 +13,12 @@ daggerSources { "dagger/multibindings", ) ) + main.setResources( + mapOf( + "dagger/proguard.pro" to "META-INF/com.android.tools/proguard", + "dagger/r8.pro" to "META-INF/com.android.tools/r8" + ) + ) test.setPackages( listOf( "dagger",