com.google.testing.compile.Compiler Maven / Gradle / Ivy
Show all versions of compile-testing Show documentation
/*
* Copyright (C) 2016 Google, 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.google.testing.compile;
import static com.google.common.base.Functions.toStringFunction;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.tools.ToolProvider.getSystemJavaCompiler;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.base.StandardSystemProperty;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.testing.compile.Compilation.Status;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import javax.annotation.processing.Processor;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import org.checkerframework.checker.nullness.qual.Nullable;
/** An object that can {@link #compile} Java source files. */
@AutoValue
// clashes with java.lang.Compiler (which is deprecated for removal in 9)
@SuppressWarnings("JavaLangClash")
public abstract class Compiler {
/** Returns the {@code javac} compiler. */
public static Compiler javac() {
return compiler(getSystemJavaCompiler());
}
/** Returns a {@link Compiler} that uses a given {@link JavaCompiler} instance. */
public static Compiler compiler(JavaCompiler javaCompiler) {
return new AutoValue_Compiler(
javaCompiler, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty());
}
abstract JavaCompiler javaCompiler();
/** The annotation processors applied during compilation. */
public abstract ImmutableList processors();
/** The options passed to the compiler. */
public abstract ImmutableList options();
/** The compilation class path. If not present, the system class path is used. */
public abstract Optional> classPath();
/**
* The annotation processor path. If not present, the system annotation processor path is used.
*/
public abstract Optional> annotationProcessorPath();
/**
* Uses annotation processors during compilation. These replace any previously specified.
*
* Note that most annotation processors cannot be reused for more than one compilation.
*
* @return a new instance with the same options and the given processors
*/
public final Compiler withProcessors(Processor... processors) {
return withProcessors(ImmutableList.copyOf(processors));
}
/**
* Uses annotation processors during compilation. These replace any previously specified.
*
*
Note that most annotation processors cannot be reused for more than one compilation.
*
* @return a new instance with the same options and the given processors
*/
public final Compiler withProcessors(Iterable processors) {
return copy(
ImmutableList.copyOf(processors), options(), classPath(), annotationProcessorPath());
}
/**
* Passes command-line options to the compiler. These replace any previously specified.
*
* @return a new instance with the same processors and the given options
*/
public final Compiler withOptions(Object... options) {
return withOptions(ImmutableList.copyOf(options));
}
/**
* Passes command-line options to the compiler. These replace any previously specified.
*
* @return a new instance with the same processors and the given options
*/
public final Compiler withOptions(Iterable options) {
return copy(
processors(),
FluentIterable.from(options).transform(toStringFunction()).toList(),
classPath(),
annotationProcessorPath());
}
/**
* Uses the classpath from the passed on classloader (and its parents) for the compilation instead
* of the system classpath.
*
* @throws IllegalArgumentException if the given classloader had classpaths which we could not
* determine or use for compilation.
* @deprecated prefer {@link #withClasspath(Iterable)}. This method only supports {@link
* URLClassLoader} and the default system classloader, and {@link File}s are usually a more
* natural way to expression compilation classpaths than class loaders.
*/
@Deprecated
public final Compiler withClasspathFrom(ClassLoader classloader) {
return copy(
processors(),
options(),
Optional.of(getClasspathFromClassloader(classloader)),
annotationProcessorPath());
}
/** Uses the given classpath for the compilation instead of the system classpath. */
public final Compiler withClasspath(Iterable classPath) {
return copy(
processors(),
options(),
Optional.of(ImmutableList.copyOf(classPath)),
annotationProcessorPath());
}
/**
* Uses the given annotation processor path for the compilation instead of the system annotation
* processor path.
*/
public final Compiler withAnnotationProcessorPath(Iterable annotationProcessorPath) {
return copy(
processors(),
options(),
classPath(),
Optional.of(ImmutableList.copyOf(annotationProcessorPath)));
}
/**
* Compiles Java source files.
*
* @return the results of the compilation
*/
public final Compilation compile(JavaFileObject... files) {
return compile(ImmutableList.copyOf(files));
}
/**
* Compiles Java source files.
*
* @return the results of the compilation
*/
public final Compilation compile(Iterable files) {
DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>();
InMemoryJavaFileManager fileManager =
new InMemoryJavaFileManager(
javaCompiler().getStandardFileManager(diagnosticCollector, Locale.getDefault(), UTF_8));
fileManager.addSourceFiles(files);
classPath().ifPresent(path -> setLocation(fileManager, StandardLocation.CLASS_PATH, path));
annotationProcessorPath()
.ifPresent(
path -> setLocation(fileManager, StandardLocation.ANNOTATION_PROCESSOR_PATH, path));
CompilationTask task =
javaCompiler()
.getTask(
null, // use the default because old versions of javac log some output on stderr
fileManager,
diagnosticCollector,
options(),
ImmutableSet.of(),
files);
task.setProcessors(processors());
boolean succeeded = task.call();
Compilation compilation =
new Compilation(
this,
files,
succeeded,
diagnosticCollector.getDiagnostics(),
fileManager.getOutputFiles());
if (compilation.status().equals(Status.FAILURE) && compilation.errors().isEmpty()) {
throw new CompilationFailureException(compilation);
}
return compilation;
}
@VisibleForTesting
static final @Nullable ClassLoader platformClassLoader = getPlatformClassLoader();
private static @Nullable ClassLoader getPlatformClassLoader() {
try {
// JDK >= 9
return (ClassLoader) ClassLoader.class.getMethod("getPlatformClassLoader").invoke(null);
} catch (ReflectiveOperationException e) {
// Java <= 8
return null;
}
}
/**
* Returns the current classpaths of the given classloader including its parents.
*
* @throws IllegalArgumentException if the given classloader had classpaths which we could not
* determine or use for compilation.
*/
private static ImmutableList getClasspathFromClassloader(ClassLoader classloader) {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// Concatenate search paths from all classloaders in the hierarchy 'till the system classloader.
Set classpaths = new LinkedHashSet<>();
for (ClassLoader currentClassloader = classloader;
;
currentClassloader = currentClassloader.getParent()) {
if (currentClassloader == systemClassLoader) {
Iterables.addAll(
classpaths,
Splitter.on(StandardSystemProperty.PATH_SEPARATOR.value())
.split(StandardSystemProperty.JAVA_CLASS_PATH.value()));
break;
}
if (currentClassloader == platformClassLoader) {
break;
}
if (currentClassloader instanceof URLClassLoader) {
// We only know how to extract classpaths from URLClassloaders.
for (URL url : ((URLClassLoader) currentClassloader).getURLs()) {
if (url.getProtocol().equals("file")) {
classpaths.add(url.getPath());
} else {
throw new IllegalArgumentException(
"Given classloader consists of classpaths which are "
+ "unsupported for compilation.");
}
}
} else {
throw new IllegalArgumentException(
String.format(
"Classpath for compilation could not be extracted "
+ "since %s is not an instance of URLClassloader",
currentClassloader));
}
}
return classpaths.stream().map(File::new).collect(toImmutableList());
}
private static void setLocation(
InMemoryJavaFileManager fileManager, StandardLocation location, ImmutableList path) {
try {
fileManager.setLocation(location, path);
} catch (IOException e) {
// impossible by specification
throw new UncheckedIOException(e);
}
}
private Compiler copy(
ImmutableList processors,
ImmutableList options,
Optional> classPath,
Optional> annotationProcessorPath) {
return new AutoValue_Compiler(
javaCompiler(), processors, options, classPath, annotationProcessorPath);
}
}