com.google.protobuf.gradle.ProtobufPlugin.groovy Maven / Gradle / Ivy
/*
* Original work copyright (c) 2015, Alex Antonov. All rights reserved.
* Modified work copyright (c) 2015, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.protobuf.gradle
import com.google.common.collect.ImmutableList
import groovy.transform.CompileDynamic
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.LibraryElements
import org.gradle.api.attributes.Usage
import org.gradle.api.file.FileCollection
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.internal.file.FileResolver
import org.gradle.api.plugins.AppliedPlugin
import org.gradle.api.tasks.SourceSet
import javax.inject.Inject
/**
* The main class for the protobuf plugin.
*/
@CompileDynamic
class ProtobufPlugin implements Plugin {
// any one of these plugins should be sufficient to proceed with applying this plugin
private static final List PREREQ_PLUGIN_OPTIONS = [
'java',
'java-library',
'com.android.application',
'com.android.feature',
'com.android.library',
'android',
'android-library',
]
private static final List SUPPORTED_LANGUAGES = [
'java',
'kotlin',
]
private final FileResolver fileResolver
private Project project
private boolean wasApplied = false
@Inject
public ProtobufPlugin(FileResolver fileResolver) {
this.fileResolver = fileResolver
}
void apply(final Project project) {
if (Utils.compareGradleVersion(project, "5.6") < 0) {
throw new GradleException(
"Gradle version is ${project.gradle.gradleVersion}. Minimum supported version is 5.6")
}
this.project = project
// At least one of the prerequisite plugins must by applied before this plugin can be applied, so
// we will use the PluginManager.withPlugin() callback mechanism to delay applying this plugin until
// after that has been achieved. If project evaluation completes before one of the prerequisite plugins
// has been applied then we will assume that none of prerequisite plugins were specified and we will
// throw an Exception to alert the user of this configuration issue.
Action super AppliedPlugin> applyWithPrerequisitePlugin = { prerequisitePlugin ->
if (wasApplied) {
project.logger.info('The com.google.protobuf plugin was already applied to the project: ' + project.path
+ ' and will not be applied again after plugin: ' + prerequisitePlugin.id)
} else {
wasApplied = true
doApply()
}
}
PREREQ_PLUGIN_OPTIONS.each { pluginName ->
project.pluginManager.withPlugin(pluginName, applyWithPrerequisitePlugin)
}
project.afterEvaluate {
if (!wasApplied) {
throw new GradleException('The com.google.protobuf plugin could not be applied during project evaluation.'
+ ' The Java plugin or one of the Android plugins must be applied to the project first.')
}
}
}
private static void linkGenerateProtoTasksToTask(Task task, GenerateProtoTask genProtoTask) {
task.dependsOn(genProtoTask)
task.source genProtoTask.getOutputSourceDirectorySet().include("**/*.java", "**/*.kt")
}
private void doApply() {
// Provides the osdetector extension
project.apply([plugin:com.google.gradle.osdetector.OsDetectorPlugin])
project.convention.plugins.protobuf = new ProtobufConvention(project, fileResolver)
addSourceSetExtensions()
getSourceSets().all { sourceSet ->
createProtobufConfiguration(sourceSet.name)
}
project.afterEvaluate {
// All custom source sets, configurations created by other plugins,
// and Android variants are only available at this point.
// Java projects will extract included protos from a 'compileProtoPath'
// configuration of each source set, while Android projects will
// extract included protos from {@code variant.compileConfiguration}
// of each variant.
if (!Utils.isAndroidProject(project)) {
getSourceSets().each { sourceSet ->
createCompileProtoPathConfiguration(sourceSet.name)
}
}
addProtoTasks()
project.protobuf.runTaskConfigClosures()
// Disallow user configuration outside the config closures, because
// next in linkGenerateProtoTasksToSourceCompile() we add generated,
// outputs to the inputs of javaCompile tasks, and any new codegen
// plugin output added after this point won't be added to javaCompile
// tasks.
project.protobuf.generateProtoTasks.all()*.doneConfig()
linkGenerateProtoTasksToSourceCompile()
// protoc and codegen plugin configuration may change through the protobuf{}
// block. Only at this point the configuration has been finalized.
project.protobuf.tools.registerTaskDependencies(project.protobuf.getGenerateProtoTasks().all())
// Register proto and generated sources with IDE
addSourcesToIde()
}
}
/**
* Creates a 'protobuf' configuration for the given source set. The build author can
* configure dependencies for it. The extract-protos task of each source set will
* extract protobuf files from dependencies in this configuration.
*/
private void createProtobufConfiguration(String sourceSetName) {
String protobufConfigName = Utils.getConfigName(sourceSetName, 'protobuf')
if (project.configurations.findByName(protobufConfigName) == null) {
project.configurations.create(protobufConfigName) {
visible = false
transitive = true
extendsFrom = []
}
}
}
/**
* Creates an internal 'compileProtoPath' configuration for the given source set that extends
* compilation configurations as a bucket of dependencies with resources attribute.
* The extract-include-protos task of each source set will extract protobuf files from
* resolved dependencies in this configuration.
*
* For Java projects only.
*
This works around 'java-library' plugin not exposing resources to consumers for compilation.
*/
private void createCompileProtoPathConfiguration(String sourceSetName) {
String compileProtoConfigName = Utils.getConfigName(sourceSetName, 'compileProtoPath')
Configuration compileConfig =
project.configurations.findByName(Utils.getConfigName(sourceSetName, 'compileOnly'))
Configuration implementationConfig =
project.configurations.findByName(Utils.getConfigName(sourceSetName, 'implementation'))
if (project.configurations.findByName(compileProtoConfigName) == null) {
project.configurations.create(compileProtoConfigName) {
visible = false
transitive = true
extendsFrom = [compileConfig, implementationConfig]
canBeConsumed = false
}.getAttributes()
// Variant attributes are not inherited. Setting it too loosely can
// result in ambiguous variant selection errors.
// CompileProtoPath only need proto files from dependency's resources.
// LibraryElement "resources" is compatible with "jar" (if a "resources" variant is
// not found, the "jar" variant will be used).
.attribute(
LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,
project.getObjects().named(LibraryElements, LibraryElements.RESOURCES))
// Although variants with any usage has proto files, not setting usage attribute
// can result in ambiguous variant selection if the producer provides multiple
// variants with different usage attribute.
// Preserve the usage attribute from CompileOnly and Implementation.
.attribute(
Usage.USAGE_ATTRIBUTE,
project.getObjects().named(Usage, Usage.JAVA_RUNTIME))
}
}
/**
* Adds the proto extension to all SourceSets, e.g., it creates
* sourceSets.main.proto and sourceSets.test.proto.
*/
private void addSourceSetExtensions() {
getSourceSets().all { sourceSet ->
String name = sourceSet.name
SourceDirectorySet sds = project.objects.sourceDirectorySet(name, "${name} Proto source")
sourceSet.extensions.add('proto', sds)
sds.srcDir("src/${name}/proto")
sds.include("**/*.proto")
}
}
/**
* Returns the sourceSets container of a Java or an Android project.
*/
private Object getSourceSets() {
if (Utils.isAndroidProject(project)) {
return project.android.sourceSets
}
return project.sourceSets
}
private Object getNonTestVariants() {
return project.android.hasProperty('libraryVariants') ?
project.android.libraryVariants : project.android.applicationVariants
}
/**
* Adds Protobuf-related tasks to the project.
*/
private void addProtoTasks() {
if (Utils.isAndroidProject(project)) {
getNonTestVariants().each { variant ->
addTasksForVariant(variant, false)
}
(project.android.unitTestVariants + project.android.testVariants).each { variant ->
addTasksForVariant(variant, true)
}
} else {
getSourceSets().each { sourceSet ->
addTasksForSourceSet(sourceSet)
}
}
}
/**
* Creates Protobuf tasks for a sourceSet in a Java project.
*/
private void addTasksForSourceSet(final SourceSet sourceSet) {
Task generateProtoTask = addGenerateProtoTask(sourceSet.name, [sourceSet])
generateProtoTask.sourceSet = sourceSet
generateProtoTask.doneInitializing()
generateProtoTask.builtins {
java { }
}
Task extractTask = setupExtractProtosTask(generateProtoTask, sourceSet.name)
setupExtractIncludeProtosTask(generateProtoTask, sourceSet.name)
// Include source proto files in the compiled archive, so that proto files from
// dependent projects can import them.
Task processResourcesTask =
project.tasks.getByName(sourceSet.getTaskName('process', 'resources'))
processResourcesTask.from(generateProtoTask.sourceFiles) {
include '**/*.proto'
}
processResourcesTask.dependsOn(extractTask)
}
/**
* Creates Protobuf tasks for a variant in an Android project.
*/
private void addTasksForVariant(final Object variant, boolean isTestVariant) {
// GenerateProto task, one per variant (compilation unit).
Task generateProtoTask = addGenerateProtoTask(variant.name, variant.sourceSets)
generateProtoTask.setVariant(variant, isTestVariant)
generateProtoTask.flavors = ImmutableList.copyOf(variant.productFlavors.collect { it.name } )
if (variant.hasProperty('buildType')) {
generateProtoTask.buildType = variant.buildType.name
}
generateProtoTask.doneInitializing()
// ExtractIncludeProto task, one per variant (compilation unit).
// Proto definitions from an AAR dependencies are in its JAR resources.
Attribute artifactType = Attribute.of("artifactType", String)
FileCollection classPathConfig = variant.compileConfiguration.incoming.artifactView {
attributes {
it.attribute(artifactType, "jar")
}
}.files
FileCollection testClassPathConfig =
variant.hasProperty("testedVariant") ?
variant.testedVariant.compileConfiguration.incoming.artifactView {
attributes {
it.attribute(artifactType, "jar")
}
}.files : null
setupExtractIncludeProtosTask(generateProtoTask, variant.name, classPathConfig, testClassPathConfig)
// ExtractProto task, one per source set.
if (project.android.hasProperty('libraryVariants')) {
// Include source proto files in the compiled archive, so that proto files from
// dependent projects can import them.
Task processResourcesTask = variant.getProcessJavaResourcesProvider().get()
processResourcesTask.from(generateProtoTask.sourceFiles) {
include '**/*.proto'
}
variant.sourceSets.each {
Task extractTask = setupExtractProtosTask(generateProtoTask, it.name)
processResourcesTask.dependsOn(extractTask)
}
} else {
variant.sourceSets.each {
setupExtractProtosTask(generateProtoTask, it.name)
}
}
}
/**
* Adds a task to run protoc and compile all proto source files for a sourceSet or variant.
*
* @param sourceSetOrVariantName the name of the sourceSet (Java) or
* variant (Android) that this task will run for.
*
* @param sourceSets the sourceSets that contains the proto files to be
* compiled. For Java it's the sourceSet that sourceSetOrVariantName stands
* for; for Android it's the collection of sourceSets that the variant includes.
*/
private Task addGenerateProtoTask(String sourceSetOrVariantName, Collection