blob: d62e749d6daca217553782eef635be3714f025ca [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// 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.devtools.build.buildjar;
import com.google.common.base.CharMatcher;
import com.google.common.io.ByteStreams;
import com.google.common.io.MoreFiles;
import com.google.common.io.RecursiveDeleteOption;
import com.google.devtools.build.buildjar.instrumentation.JacocoInstrumentationProcessor;
import com.google.devtools.build.buildjar.jarhelper.JarCreator;
import com.google.devtools.build.buildjar.javac.BlazeJavacMain;
import com.google.devtools.build.buildjar.javac.BlazeJavacResult;
import com.google.devtools.build.buildjar.javac.BlazeJavacResult.Status;
import com.google.devtools.build.buildjar.javac.JavacRunner;
import com.google.devtools.build.buildjar.javac.statistics.BlazeJavacStatistics;
import com.google.errorprone.annotations.CheckReturnValue;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.annotation.Nullable;
/** An implementation of the JavaBuilder that uses in-process javac to compile java files. */
public class SimpleJavaLibraryBuilder implements Closeable {
/** The name of the protobuf meta file. */
private static final String PROTOBUF_META_NAME = "protobuf.meta";
BlazeJavacResult compileSources(JavaLibraryBuildRequest build, JavacRunner javacRunner)
throws IOException {
BlazeJavacResult result =
javacRunner.invokeJavac(build.toBlazeJavacArguments(build.getClassPath()));
BlazeJavacStatistics.Builder stats =
result.statistics().toBuilder()
.transitiveClasspathLength(build.getClassPath().size())
.reducedClasspathLength(build.getClassPath().size())
.transitiveClasspathFallback(false);
build.getProcessors().stream()
.map(p -> p.substring(p.lastIndexOf('.') + 1))
.forEachOrdered(stats::addProcessor);
return result.withStatistics(stats.build());
}
protected void prepareSourceCompilation(JavaLibraryBuildRequest build) throws IOException {
cleanupDirectory(build.getClassDir());
setUpSourceJars(build);
cleanupDirectory(build.getSourceGenDir());
cleanupDirectory(build.getNativeHeaderDir());
}
// Necessary for local builds in order to discard previous outputs
private static void cleanupDirectory(@Nullable Path directory) throws IOException {
if (directory == null) {
return;
}
if (Files.exists(directory)) {
try {
MoreFiles.deleteRecursively(directory, RecursiveDeleteOption.ALLOW_INSECURE);
} catch (IOException e) {
throw new IOException("Cannot clean '" + directory + "'", e);
}
}
Files.createDirectories(directory);
}
public void buildGensrcJar(JavaLibraryBuildRequest build) throws IOException {
JarCreator jar = new JarCreator(build.getGeneratedSourcesOutputJar());
try {
jar.setNormalize(true);
jar.setCompression(build.compressJar());
jar.addDirectory(build.getSourceGenDir());
} finally {
jar.execute();
}
}
/**
* Prepares a compilation run and sets everything up so that the source files in the build request
* can be compiled. Invokes compileSources to do the actual compilation.
*
* @param build A JavaLibraryBuildRequest request object describing what to compile
*/
private BlazeJavacResult compileJavaLibrary(final JavaLibraryBuildRequest build)
throws Exception {
prepareSourceCompilation(build);
if (build.getSourceFiles().isEmpty()) {
return BlazeJavacResult.ok();
}
return compileSources(build, BlazeJavacMain::compile);
}
/** Perform the build. */
@CheckReturnValue
public BlazeJavacResult run(JavaLibraryBuildRequest build) throws Exception {
BlazeJavacResult result = BlazeJavacResult.error("");
try {
result = compileJavaLibrary(build);
if (result.isOk()) {
buildJar(build);
nativeHeaderOutput(build);
}
if (!build.getProcessors().isEmpty()) {
if (build.getGeneratedSourcesOutputJar() != null) {
buildGensrcJar(build);
}
}
} finally {
build
.getDependencyModule()
.emitDependencyInformation(
build.getClassPath(),
result.isOk(),
/* requiresFallback= */ result.status() == Status.REQUIRES_FALLBACK);
build.getProcessingModule().emitManifestProto();
}
return result;
}
public void buildJar(JavaLibraryBuildRequest build) throws IOException {
Files.createDirectories(build.getOutputJar().getParent());
JarCreator jar = new JarCreator(build.getOutputJar());
JacocoInstrumentationProcessor processor = null;
try {
jar.setNormalize(true);
jar.setCompression(build.compressJar());
jar.addDirectory(build.getClassDir());
jar.setJarOwner(build.getTargetLabel(), build.getInjectingRuleKind());
processor = build.getJacocoInstrumentationProcessor();
if (processor != null) {
processor.processRequest(build, processor.isNewCoverageImplementation() ? jar : null);
}
} finally {
jar.execute();
if (processor != null) {
processor.cleanup();
}
}
}
public void nativeHeaderOutput(JavaLibraryBuildRequest build) throws IOException {
if (build.getNativeHeaderOutput() == null) {
return;
}
JarCreator jar = new JarCreator(build.getNativeHeaderOutput());
try {
jar.setNormalize(true);
jar.setCompression(build.compressJar());
jar.addDirectory(build.getNativeHeaderDir());
} finally {
jar.execute();
}
}
/**
* Extracts the all source jars from the build request into the temporary directory specified in
* the build request. Empties the temporary directory, if it exists.
*/
private void setUpSourceJars(JavaLibraryBuildRequest build) throws IOException {
Path sourcesDir = build.getTempDir();
cleanupDirectory(sourcesDir);
if (build.getSourceJars().isEmpty()) {
return;
}
final ByteArrayOutputStream protobufMetadataBuffer = new ByteArrayOutputStream();
for (Path sourceJar : build.getSourceJars()) {
try (JarFile jarFile = new JarFile(sourceJar.toFile())) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String fileName = entry.getName();
if (fileName.endsWith(".java")) {
if (fileName.charAt(0) == '/') {
fileName = CharMatcher.is('/').trimLeadingFrom(fileName);
}
Path to = sourcesDir.resolve(fileName);
int root = 1;
if (Files.exists(to)) {
// Make paths unique e.g. if extracting two srcjar entries that differ only in case
// to a case-insenitive target filesystem (e.g. on Macs).
do {
to = sourcesDir.resolve(Integer.toString(root++)).resolve(fileName);
} while (Files.exists(to));
}
Files.createDirectories(to.getParent());
Files.copy(jarFile.getInputStream(entry), to);
build.getSourceFiles().add(to);
} else if (fileName.equals(PROTOBUF_META_NAME)) {
ByteStreams.copy(jarFile.getInputStream(entry), protobufMetadataBuffer);
}
}
} catch (IOException e) {
throw new IOException("unable to open " + sourceJar + " as a jar file", e);
}
}
Path output = build.getClassDir().resolve(PROTOBUF_META_NAME);
if (protobufMetadataBuffer.size() > 0) {
try (OutputStream outputStream = Files.newOutputStream(output)) {
protobufMetadataBuffer.writeTo(outputStream);
}
} else if (Files.exists(output)) {
// Delete stalled meta file.
Files.delete(output);
}
}
@Override
public void close() throws IOException {}
}