com.google.devtools.build.buildjar.VanillaJavaBuilder.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.buildjar.VanillaJavaBuilder.java

Source

// Copyright 2017 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 static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Locale.ENGLISH;

import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.Iterables;
import com.google.devtools.build.buildjar.jarhelper.JarCreator;
import com.google.devtools.build.buildjar.javac.JavacOptions;
import com.google.devtools.build.buildjar.proto.JavaCompilation.Manifest;
import com.google.devtools.build.buildjar.resourcejar.ResourceJarBuilder;
import com.google.devtools.build.buildjar.resourcejar.ResourceJarOptions;
import com.google.devtools.build.lib.view.proto.Deps;
import com.google.devtools.build.lib.worker.WorkerProtocol.WorkRequest;
import com.google.devtools.build.lib.worker.WorkerProtocol.WorkResponse;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URI;
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.List;
import java.util.Map;
import javax.annotation.processing.Processor;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;

/**
 * A JavaBuilder that supports non-standard JDKs and unmodified javac's.
 *
 * <p>Does not support:
 *
 * <ul>
 *   <li>Error Prone
 *   <li>strict Java deps
 *   <li>header compilation
 *   <li>Android desugaring
 *   <li>coverage instrumentation
 *   <li>genclass handling for IDEs
 * </ul>
 */
public class VanillaJavaBuilder implements Closeable {

    /** Cache of opened zip filesystems. */
    private final Map<Path, FileSystem> filesystems = new HashMap<>();

    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;
    }

    public static void main(String[] args) throws IOException {
        if (args.length == 1 && args[0].equals("--persistent_worker")) {
            System.exit(runPersistentWorker());
        } else {
            try (VanillaJavaBuilder builder = new VanillaJavaBuilder()) {
                VanillaJavaBuilderResult result = builder.run(ImmutableList.copyOf(args));
                System.err.print(result.output());
                System.exit(result.ok() ? 0 : 1);
            }
        }
    }

    private static int runPersistentWorker() {
        while (true) {
            try {
                WorkRequest request = WorkRequest.parseDelimitedFrom(System.in);
                if (request == null) {
                    break;
                }
                try (VanillaJavaBuilder builder = new VanillaJavaBuilder()) {
                    VanillaJavaBuilderResult result = builder.run(request.getArgumentsList());
                    WorkResponse response = WorkResponse.newBuilder().setOutput(result.output())
                            .setExitCode(result.ok() ? 0 : 1).build();
                    response.writeDelimitedTo(System.out);
                }
                System.out.flush();
            } catch (IOException e) {
                e.printStackTrace();
                return 1;
            }
        }
        return 0;
    }

    /** Return result of a {@link VanillaJavaBuilder} build. */
    public static class VanillaJavaBuilderResult {
        private final boolean ok;
        private final String output;

        public VanillaJavaBuilderResult(boolean ok, String output) {
            this.ok = ok;
            this.output = output;
        }

        /** True if the compilation was succesfull. */
        public boolean ok() {
            return ok;
        }

        /** Log output from the compilation. */
        public String output() {
            return output;
        }
    }

    public VanillaJavaBuilderResult run(List<String> args) throws IOException {
        OptionsParser optionsParser;
        try {
            optionsParser = new OptionsParser(args);
        } catch (InvalidCommandLineException e) {
            return new VanillaJavaBuilderResult(false, e.getMessage());
        }
        DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
        StringWriter output = new StringWriter();
        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(diagnosticCollector, ENGLISH,
                UTF_8);
        setLocations(optionsParser, fileManager);
        ImmutableList<JavaFileObject> sources = getSources(optionsParser, fileManager);
        boolean ok;
        if (sources.isEmpty()) {
            ok = true;
        } else {
            CompilationTask task = javaCompiler.getTask(new PrintWriter(output, true), fileManager,
                    diagnosticCollector, JavacOptions.removeBazelSpecificFlags(optionsParser.getJavacOpts()),
                    ImmutableList.<String>of() /*classes*/, sources);
            setProcessors(optionsParser, fileManager, task);
            ok = task.call();
        }
        if (ok) {
            writeOutput(optionsParser);
        }
        writeGeneratedSourceOutput(optionsParser);
        // the jdeps output doesn't include any information about dependencies, but Bazel still expects
        // the file to be created
        if (optionsParser.getOutputDepsProtoFile() != null) {
            try (OutputStream os = Files.newOutputStream(Paths.get(optionsParser.getOutputDepsProtoFile()))) {
                Deps.Dependencies.newBuilder().setRuleLabel(optionsParser.getTargetLabel()).setSuccess(ok).build()
                        .writeTo(os);
            }
        }
        // TODO(cushon): support manifest protos & genjar
        if (optionsParser.getManifestProtoPath() != null) {
            try (OutputStream os = Files.newOutputStream(Paths.get(optionsParser.getManifestProtoPath()))) {
                Manifest.getDefaultInstance().writeTo(os);
            }
        }

        for (Diagnostic<? extends JavaFileObject> diagnostic : diagnosticCollector.getDiagnostics()) {
            StringBuilder message = new StringBuilder();
            if (diagnostic.getSource() != null) {
                message.append(diagnostic.getSource().getName());
                if (diagnostic.getLineNumber() != -1) {
                    message.append(':').append(diagnostic.getLineNumber());
                }
                message.append(": ");
            }
            message.append(diagnostic.getKind().toString().toLowerCase(ENGLISH));
            message.append(": ").append(diagnostic.getMessage(ENGLISH)).append(System.lineSeparator());
            output.write(message.toString());
        }
        return new VanillaJavaBuilderResult(ok, output.toString());
    }

    /** Returns the sources to compile, including any source jar entries. */
    private ImmutableList<JavaFileObject> getSources(OptionsParser optionsParser,
            StandardJavaFileManager fileManager) throws IOException {
        final ImmutableList.Builder<JavaFileObject> sources = ImmutableList.builder();
        sources.addAll(fileManager.getJavaFileObjectsFromStrings(optionsParser.getSourceFiles()));
        for (String sourceJar : optionsParser.getSourceJars()) {
            for (final Path root : getJarFileSystem(Paths.get(sourceJar)).getRootDirectories()) {
                Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
                        if (path.getFileName().toString().endsWith(".java")) {
                            sources.add(new SourceJarFileObject(root, path));
                        }
                        return FileVisitResult.CONTINUE;
                    }
                });
            }
        }
        return sources.build();
    }

    /** Sets the compilation search paths and output directories. */
    private static void setLocations(OptionsParser optionsParser, StandardJavaFileManager fileManager)
            throws IOException {
        fileManager.setLocation(StandardLocation.CLASS_PATH, toFiles(optionsParser.getClassPath()));
        fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH,
                Iterables.concat(toFiles(optionsParser.getBootClassPath()), toFiles(optionsParser.getExtdir())));
        fileManager.setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH,
                toFiles(optionsParser.getProcessorPath()));
        if (optionsParser.getSourceGenDir() != null) {
            Path sourceGenDir = Paths.get(optionsParser.getSourceGenDir());
            Files.createDirectories(sourceGenDir);
            fileManager.setLocation(StandardLocation.SOURCE_OUTPUT, ImmutableList.of(sourceGenDir.toFile()));
        }
        Path classDir = Paths.get(optionsParser.getClassDir());
        Files.createDirectories(classDir);
        fileManager.setLocation(StandardLocation.CLASS_OUTPUT, ImmutableList.of(classDir.toFile()));
    }

    /** Sets the compilation's annotation processors. */
    private static void setProcessors(OptionsParser optionsParser, StandardJavaFileManager fileManager,
            CompilationTask task) {
        ClassLoader processorLoader = fileManager.getClassLoader(StandardLocation.ANNOTATION_PROCESSOR_PATH);
        Builder<Processor> processors = ImmutableList.builder();
        for (String processor : optionsParser.getProcessorNames()) {
            try {
                processors.add((Processor) processorLoader.loadClass(processor).getConstructor().newInstance());
            } catch (ReflectiveOperationException e) {
                throw new LinkageError(e.getMessage(), e);
            }
        }
        task.setProcessors(processors.build());
    }

    /** Writes a jar containing any sources generated by annotation processors. */
    private static void writeGeneratedSourceOutput(OptionsParser optionsParser) throws IOException {
        if (optionsParser.getGeneratedSourcesOutputJar() == null) {
            return;
        }
        JarCreator jar = new JarCreator(optionsParser.getGeneratedSourcesOutputJar());
        jar.setNormalize(true);
        jar.setCompression(optionsParser.compressJar());
        jar.addDirectory(optionsParser.getSourceGenDir());
        jar.execute();
    }

    /** Writes the class output jar, including any resource entries. */
    private static void writeOutput(OptionsParser optionsParser) throws IOException {
        JarCreator jar = new JarCreator(optionsParser.getOutputJar());
        jar.setNormalize(true);
        jar.setCompression(optionsParser.compressJar());
        jar.addDirectory(optionsParser.getClassDir());
        // TODO(cushon): kill this once resource jar creation is decoupled from JavaBuilder
        try (ResourceJarBuilder resourceBuilder = new ResourceJarBuilder(ResourceJarOptions.builder()
                .setMessages(ImmutableList.copyOf(optionsParser.getMessageFiles()))
                .setResourceJars(ImmutableList.copyOf(optionsParser.getResourceJars()))
                .setResources(ImmutableList.copyOf(optionsParser.getResourceFiles()))
                .setClasspathResources(ImmutableList.copyOf(optionsParser.getRootResourceFiles())).build())) {
            resourceBuilder.build(jar);
        }
        jar.execute();
    }

    private static ImmutableList<File> toFiles(String classPath) {
        if (classPath == null) {
            return ImmutableList.of();
        }
        ImmutableList.Builder<File> files = ImmutableList.builder();
        for (String path : Splitter.on(File.pathSeparatorChar).split(classPath)) {
            files.add(new File(path));
        }
        return files.build();
    }

    @Override
    public void close() throws IOException {
        for (FileSystem fs : filesystems.values()) {
            fs.close();
        }
    }

    /**
     * Wraps a {@link Path} as a {@link JavaFileObject}; used to avoid extracting source jar entries
     * to disk when using file managers that don't support nio.
     */
    private static class SourceJarFileObject extends SimpleJavaFileObject {
        private final Path path;

        public SourceJarFileObject(Path root, Path path) {
            super(URI.create("file:/" + root + "!" + root.resolve(path)), Kind.SOURCE);
            this.path = path;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
            return new String(Files.readAllBytes(path), UTF_8);
        }
    }
}