blob: b166897ea349c80e4f0cca0cd24c0239e193f249 [file] [log] [blame]
/*
* Copyright 2020 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.android.desugar.testing.junit;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.tools.JavaCompiler;
/** Represents a compilation unit with a single jar output. */
public final class SourceCompilationUnit {
/** The java compiler used to compile source files. */
private final JavaCompiler compiler;
/**
* customJavacOptions The javac options used for compilation, with the same support of `javacopts`
* attribute in java_binary rule.
*/
private final ImmutableList<String> customJavacOptions;
/** The collection of source files subject to compile. */
private final ImmutableList<Path> sourceInputs;
/** The caller-specified write-permissible file path to the compiled jar. */
private final Path outputJar;
public SourceCompilationUnit(
JavaCompiler compiler,
ImmutableList<String> customJavacOptions,
ImmutableList<Path> sourceInputs,
Path outputJar) {
this.compiler = compiler;
this.customJavacOptions = customJavacOptions;
this.sourceInputs = sourceInputs;
this.outputJar = outputJar;
}
/** Compiles Java source files and write to the pre-specified path to the output jar. */
Path compile() throws IOException, SourceCompilationException {
Path compilationStdOut = Files.createTempFile("compilation_stdout_", ".txt");
Path compilationStdErr = Files.createTempFile("compilation_stderr_", ".txt");
Path compiledRootDir = Files.createTempDirectory("compilation_prodout_");
ImmutableList<String> javacOptions =
ImmutableList.<String>builder()
.addAll(customJavacOptions)
.add("-d " + compiledRootDir)
.build();
final List<Path> compiledFiles;
try (OutputStream stdOutStream = Files.newOutputStream(compilationStdOut);
OutputStream stdErrStream = Files.newOutputStream(compilationStdErr)) {
Splitter splitter = Splitter.on(" ").trimResults().omitEmptyStrings();
ImmutableList<String> compilationArguments =
ImmutableList.<String>builder()
.addAll(splitter.split(String.join(" ", javacOptions)))
.addAll(sourceInputs.stream().map(Path::toString).collect(Collectors.toList()))
.build();
compiler.run(
nullInputStream(),
stdOutStream,
stdErrStream,
compilationArguments.toArray(new String[0]));
int maxDepth = sourceInputs.stream().mapToInt(Path::getNameCount).max().getAsInt();
try (Stream<Path> outputStream =
Files.find(compiledRootDir, maxDepth, (path, fileAttr) -> true)) {
compiledFiles = outputStream.collect(Collectors.toList());
}
try (JarOutputStream jarOutputStream =
new JarOutputStream(Files.newOutputStream(outputJar))) {
for (Path compiledFile : compiledFiles) {
try (InputStream inputStream = Files.newInputStream(compiledFile)) {
Path inArchivalPath = compiledRootDir.relativize(compiledFile);
JarEntry jarEntry = new JarEntry(inArchivalPath.toString());
jarOutputStream.putNextEntry(jarEntry);
if (!Files.isDirectory(compiledFile)) {
ByteStreams.copy(inputStream, jarOutputStream);
}
jarOutputStream.closeEntry();
}
}
}
}
String compilationStandardErrorMessage =
new String(Files.readAllBytes(compilationStdErr), Charset.defaultCharset());
if (!compilationStandardErrorMessage.isEmpty()) {
throw new SourceCompilationException(compilationStandardErrorMessage);
}
return outputJar;
}
private static InputStream nullInputStream() {
return new ByteArrayInputStream(new byte[] {});
}
static class SourceCompilationException extends Exception {
public SourceCompilationException(String message) {
super(message);
}
}
}