// 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.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.HashMap; import java.util.Map; /** 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 { Path classDirectory = Paths.get(build.getClassDir()); if (Files.exists(classDirectory)) { try { // Necessary for local builds in order to discard previous outputs cleanupDirectory(classDirectory); } catch (IOException e) { throw new IOException("Cannot clean output directory '" + classDirectory + "'", e); } } Files.createDirectories(classDirectory); setUpSourceJars(build); // Create sourceGenDir if necessary. if (build.getSourceGenDir() != null) { Path sourceGenDir = Paths.get(build.getSourceGenDir()); if (Files.exists(sourceGenDir)) { try { cleanupDirectory(sourceGenDir); } catch (IOException e) { throw new IOException("Cannot clean output directory '" + sourceGenDir + "'", e); } } Files.createDirectories(sourceGenDir); } } 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); JacocoInstrumentationProcessor processor = build.getJacocoInstrumentationProcessor(); if (processor != null) { processor.processRequest(build); } 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); } 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()); } 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 { String sourcesDir = build.getTempDir(); Path sourceDirFile = Paths.get(sourcesDir); if (Files.exists(sourceDirFile)) { cleanupDirectory(sourceDirFile); } if (build.getSourceJars().isEmpty()) { return; } final ByteArrayOutputStream protobufMetadataBuffer = new ByteArrayOutputStream(); for (String sourceJar : build.getSourceJars()) { for (Path root : getJarFileSystem(Paths.get(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 = Paths.get(build.getClassDir(), 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; } // TODO(b/27069912): handle symlinks private static void cleanupDirectory(Path dir) throws IOException { Files.walkFileTree( dir, 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 { Files.delete(dir); return FileVisitResult.CONTINUE; } }); } @Override public void close() throws IOException { for (FileSystem fs : filesystems.values()) { fs.close(); } } }