com.android.build.gradle.tasks.JavaPreCompileTask.java Source code

Java tutorial

Introduction

Here is the source code for com.android.build.gradle.tasks.JavaPreCompileTask.java

Source

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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 com.android.build.gradle.tasks;

import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.ALL;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.CLASSES;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.JAR;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.ANNOTATION_PROCESSOR;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH;

import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.build.gradle.api.AnnotationProcessorOptions;
import com.android.build.gradle.internal.scope.TaskConfigAction;
import com.android.build.gradle.internal.scope.VariantScope;
import com.android.build.gradle.internal.tasks.BaseTask;
import com.android.utils.FileUtils;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import org.gradle.api.artifacts.ArtifactCollection;
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;

/** Tasks to perform necessary action before a JavaCompile. */
@CacheableTask
public class JavaPreCompileTask extends BaseTask {

    @VisibleForTesting
    static final String DATA_BINDING_SPEC = "android.databinding.DataBinding";

    private static final String PROCESSOR_SERVICES = "META-INF/services/javax.annotation.processing.Processor";

    private File processorListFile;

    private String annotationProcessorConfigurationName;

    private ArtifactCollection annotationProcessorConfiguration;

    private ArtifactCollection compileClasspaths;

    private AnnotationProcessorOptions annotationProcessorOptions;

    private boolean isForTesting;

    private boolean dataBindingEnabled;

    @VisibleForTesting
    void init(@NonNull File processorListFile, @NonNull String annotationProcessorConfigurationName,
            @NonNull ArtifactCollection annotationProcessorConfiguration,
            @NonNull ArtifactCollection compileClasspaths,
            @NonNull AnnotationProcessorOptions annotationProcessorOptions, boolean isForTesting,
            boolean dataBindingEnabled) {
        this.processorListFile = processorListFile;
        this.annotationProcessorConfigurationName = annotationProcessorConfigurationName;
        this.annotationProcessorConfiguration = annotationProcessorConfiguration;
        this.compileClasspaths = compileClasspaths;
        this.annotationProcessorOptions = annotationProcessorOptions;
        this.isForTesting = isForTesting;
        this.dataBindingEnabled = dataBindingEnabled;
    }

    @OutputFile
    public File getProcessorListFile() {
        return processorListFile;
    }

    @Classpath
    public FileCollection getAnnotationProcessorConfiguration() {
        return annotationProcessorConfiguration.getArtifactFiles();
    }

    @Classpath
    public FileCollection getCompileClasspaths() {
        return compileClasspaths.getArtifactFiles();
    }

    @TaskAction
    public void preCompile() throws IOException {
        boolean grandfathered = annotationProcessorOptions.getIncludeCompileClasspath() != null;
        Collection<ResolvedArtifactResult> compileProcessors = null;
        if (!grandfathered) {
            compileProcessors = collectAnnotationProcessors(compileClasspaths);
            FileCollection annotationProcessors = annotationProcessorConfiguration.getArtifactFiles();
            compileProcessors = compileProcessors.stream()
                    .filter(artifact -> !annotationProcessors.contains(artifact.getFile()))
                    .collect(Collectors.toList());
            if (!compileProcessors.isEmpty()) {
                String message = "Annotation processors must be explicitly declared now.  The following "
                        + "dependencies on the compile classpath are found to contain "
                        + "annotation processor.  Please add them to the " + annotationProcessorConfigurationName
                        + " configuration.\n  - "
                        + Joiner.on("\n  - ").join(convertArtifactsToNames(compileProcessors))
                        + "\nAlternatively, set "
                        + "android.defaultConfig.javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true "
                        + "to continue with previous behavior.  Note that this option "
                        + "is deprecated and will be removed in the future.\n" + "See "
                        + "https://developer.android.com/r/tools/annotation-processor-error-message.html "
                        + "for more details.";
                if (isForTesting) {
                    getLogger().warn(message);
                } else {
                    throw new RuntimeException(message);
                }
            }
        }

        // Get all the annotation processors for metrics collection.
        Set<String> classNames = Sets.newHashSet();

        // Add the annotation processors on classpath only when includeCompileClasspath is true.
        if (Boolean.TRUE.equals(annotationProcessorOptions.getIncludeCompileClasspath())) {
            if (compileProcessors == null) {
                compileProcessors = collectAnnotationProcessors(compileClasspaths);
            }
            classNames.addAll(convertArtifactsToNames(compileProcessors));
        }

        // Add all annotation processors on the annotation processor configuration.
        classNames.addAll(convertArtifactsToNames(collectAnnotationProcessors(annotationProcessorConfiguration)));

        // Add the explicitly declared processors.
        // For metrics purposes, we don't care how they include the processor in their build.
        classNames.addAll(annotationProcessorOptions.getClassNames());

        // Add a generic reference to data binding, if present.
        if (dataBindingEnabled) {
            classNames.add(DATA_BINDING_SPEC);
        }

        FileUtils.deleteIfExists(processorListFile);
        Gson gson = new GsonBuilder().create();
        try (FileWriter writer = new FileWriter(processorListFile)) {
            gson.toJson(classNames, writer);
        }
    }

    /**
     * Returns a List of packages in the configuration believed to contain an annotation processor.
     *
     * <p>We assume a package has an annotation processor if it contains the
     * META-INF/services/javax.annotation.processing.Processor file.
     */
    private static List<ResolvedArtifactResult> collectAnnotationProcessors(ArtifactCollection configuration) {
        List<ResolvedArtifactResult> processors = Lists.newArrayList();
        for (ResolvedArtifactResult artifact : configuration) {
            File file = artifact.getFile();
            if (!file.exists()) {
                continue;
            }
            if (file.isDirectory()) {
                if (new File(file, PROCESSOR_SERVICES).exists()) {
                    processors.add(artifact);
                }
            } else {
                try (JarFile jarFile = new JarFile(file)) {
                    JarEntry entry = jarFile.getJarEntry(PROCESSOR_SERVICES);
                    //noinspection VariableNotUsedInsideIf
                    if (entry != null) {
                        processors.add(artifact);
                    }
                } catch (IOException iox) {
                    // Can happen when we encounter a folder instead of a jar; for instance, in
                    // sub-modules. We're just displaying a warning, so there's no need to stop the
                    // build here.
                }
            }
        }
        return processors;
    }

    private static List<String> convertArtifactsToNames(Collection<ResolvedArtifactResult> files) {
        return files.stream().map(artifact -> artifact.getId().getDisplayName()).collect(Collectors.toList());
    }

    public static class ConfigAction implements TaskConfigAction<JavaPreCompileTask> {

        private final VariantScope scope;
        private final File processorListFile;

        public ConfigAction(VariantScope scope, File processorListFile) {
            this.scope = scope;
            this.processorListFile = processorListFile;
        }

        @NonNull
        @Override
        public String getName() {
            return scope.getTaskName("javaPreCompile");
        }

        @NonNull
        @Override
        public Class<JavaPreCompileTask> getType() {
            return JavaPreCompileTask.class;
        }

        @Override
        public void execute(@NonNull JavaPreCompileTask task) {
            task.init(processorListFile,
                    scope.getVariantData().getType().isForTesting()
                            ? scope.getVariantData().getType().getPrefix() + "AnnotationProcessor"
                            : "annotationProcessor",
                    scope.getArtifactCollection(ANNOTATION_PROCESSOR, ALL, JAR),
                    scope.getJavaClasspathArtifacts(COMPILE_CLASSPATH, CLASSES, null),
                    scope.getVariantConfiguration().getJavaCompileOptions().getAnnotationProcessorOptions(),
                    scope.getVariantData().getType().isForTesting(), false);
            task.setVariantName(scope.getFullVariantName());
        }
    }
}