blob: 50c936ac43360e6efa4b9c23a57031a0d97c0b87 [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.devtools.build.buildjar.instrumentation.JacocoInstrumentationProcessor;
import com.google.devtools.build.buildjar.jarhelper.JarCreator;
import com.google.devtools.build.buildjar.javac.BlazeJavacArguments;
import com.google.devtools.build.buildjar.javac.BlazeJavacMain;
import com.google.devtools.build.buildjar.javac.BlazeJavacResult;
import com.google.devtools.build.buildjar.javac.JavacRunner;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
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";
/** Cache of opened zip filesystems for srcjars. */
private final Map<Path, FileSystem> filesystems = new HashMap<>();
BlazeJavacResult compileSources(JavaLibraryBuildRequest build, JavacRunner javacRunner)
throws IOException {
return javacRunner.invokeJavac(build.toBlazeJavacArguments(build.getClassPath()));
}
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)) {
Files.createDirectories(directory);
return;
}
try {
// TODO(b/27069912): handle symlinks
Files.walkFileTree(
directory,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
if (!dir.equals(directory)) {
Files.delete(dir);
}
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
throw new IOException("Cannot clean '" + directory + "'", e);
}
}
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
*/
public BlazeJavacResult compileJavaLibrary(final JavaLibraryBuildRequest build) throws Exception {
prepareSourceCompilation(build);
if (build.getSourceFiles().isEmpty()) {
return BlazeJavacResult.ok();
}
JavacRunner javacRunner =
new JavacRunner() {
@Override
public BlazeJavacResult invokeJavac(BlazeJavacArguments arguments) {
return BlazeJavacMain.compile(arguments);
}
};
BlazeJavacResult result = compileSources(build, javacRunner);
return result;
}
/** Perform the build. */
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());
build.getProcessingModule().emitManifestProto();
}
return result;
}
public void buildJar(JavaLibraryBuildRequest build) throws IOException {
JarCreator jar = new JarCreator(build.getOutputJar());
try {
jar.setNormalize(true);
jar.setCompression(build.compressJar());
jar.addDirectory(build.getClassDir());
jar.setJarOwner(build.getTargetLabel(), build.getInjectingRuleKind());
JacocoInstrumentationProcessor processor = build.getJacocoInstrumentationProcessor();
if (processor != null) {
processor.processRequest(build, processor.isNewCoverageImplementation() ? jar : null);
}
} finally {
jar.execute();
}
}
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()) {
for (Path root : getJarFileSystem(sourceJar).getRootDirectories()) {
Files.walkFileTree(
root,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs)
throws IOException {
String fileName = path.getFileName().toString();
if (fileName.endsWith(".java")) {
build.getSourceFiles().add(path);
} else if (fileName.equals(PROTOBUF_META_NAME)) {
Files.copy(path, protobufMetadataBuffer);
}
return FileVisitResult.CONTINUE;
}
});
}
}
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);
}
}
private FileSystem getJarFileSystem(Path sourceJar) throws IOException {
FileSystem fs = filesystems.get(sourceJar);
if (fs == null) {
filesystems.put(sourceJar, fs = FileSystems.newFileSystem(sourceJar, null));
}
return fs;
}
@Override
public void close() throws IOException {
for (FileSystem fs : filesystems.values()) {
fs.close();
}
}
}