/*
* Copyright 2017-present Facebook, 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 com.facebook.buck.jvm.java;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.util.ClassLoaderCache;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.processing.Processor;
class AnnotationProcessorFactory implements AutoCloseable {
private final JavacEventSink eventSink;
private final ClassLoader compilerClassLoader;
private final ClassLoaderCache globalClassLoaderCache;
private final ClassLoaderCache localClassLoaderCache = new ClassLoaderCache();
private final BuildTarget target;
AnnotationProcessorFactory(
JavacEventSink eventSink,
ClassLoader compilerClassLoader,
ClassLoaderCache globalClassLoaderCache,
BuildTarget target) {
this.eventSink = eventSink;
this.compilerClassLoader = compilerClassLoader;
this.globalClassLoaderCache = globalClassLoaderCache;
this.target = target;
}
@Override
public void close() throws IOException {
localClassLoaderCache.close();
}
public List<Processor> createProcessors(ImmutableList<JavacPluginJsr199Fields> fields) {
return fields
.stream()
.map(this::createProcessorsWithCommonClasspath)
.flatMap(Function.identity())
.collect(Collectors.toList());
}
private Stream<Processor> createProcessorsWithCommonClasspath(JavacPluginJsr199Fields fields) {
ClassLoader classLoader = getClassLoaderForProcessorGroup(fields);
return fields.getProcessorNames().stream().map(name -> createProcessor(classLoader, name));
}
private Processor createProcessor(ClassLoader classLoader, String name) {
try {
Class<? extends Processor> aClass = classLoader.loadClass(name).asSubclass(Processor.class);
return new TracingProcessorWrapper(eventSink, target, aClass.newInstance());
} catch (ReflectiveOperationException e) {
// If this happens, then the build is really in trouble. Better warn the user.
throw new HumanReadableException(
"%s: javac unable to load annotation processor: %s",
target.getFullyQualifiedName(), name);
}
}
@VisibleForTesting
ClassLoader getClassLoaderForProcessorGroup(JavacPluginJsr199Fields processorGroup) {
ClassLoaderCache cache;
// We can avoid lots of overhead in large builds by reusing the same classloader for annotation
// processors. However, some annotation processors use static variables in a way that assumes
// there is only one instance running in the process at a time (or at all), and such annotation
// processors would break running inside of Buck. So we default to creating a new ClassLoader
// for each build rule, with an option to whitelist "safe" processors in .buckconfig.
if (processorGroup.getCanReuseClassLoader()) {
cache = globalClassLoaderCache;
} else {
cache = localClassLoaderCache;
}
return cache.getClassLoaderForClassPath(
compilerClassLoader, ImmutableList.copyOf(processorGroup.getClasspath()));
}
}