Skip to content

Commit

Permalink
Create BootClassPathCachingFileManager a subclass of JavacFileManager…
Browse files Browse the repository at this point in the history
… which handles caching for the boot classpaths. The class also decides if an instance can be kept for the current compilation or needs to be updated (e.g., if a bootclasspath digest has changed)

Note: BootClassPathCachingFileManager is used only with singleplex-workers that have list of boot classpaths along with their digest information.

RELNOTES: None.
PiperOrigin-RevId: 387592935
  • Loading branch information
zshmeis authored and copybara-github committed Jul 29, 2021
1 parent 8ffc333 commit a0e5e45
Show file tree
Hide file tree
Showing 4 changed files with 315 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,15 @@ JAVAC_OPTIONS_SRCS = [
"javac/WerrorCustomOption.java",
]

JAVAC_TEST_SRCS = [
"javac/BootClassPathCachingFileManagerTest.java",
]

java_library(
name = "javac",
srcs = glob(
["javac/*.java"],
exclude = JAVAC_OPTIONS_SRCS,
exclude = JAVAC_OPTIONS_SRCS + JAVAC_TEST_SRCS,
),
deps = [
":invalid_command_line_exception",
Expand Down Expand Up @@ -204,3 +208,16 @@ bootstrap_java_binary(
"//src/java_tools/buildjar/java/com/google/devtools/build/buildjar/jarhelper:bootstrap_jarhelper",
],
)

java_test(
name = "BootClassPathCachingFileManagerTest",
srcs = ["javac/BootClassPathCachingFileManagerTest.java"],
deps = [
":javac",
"//third_party:guava",
"//third_party:junit4",
"//third_party:truth",
"//third_party/java/jdk/langtools:javac",
"//third_party/protobuf:protobuf_java",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.StandardLocation;

/**
Expand Down Expand Up @@ -109,6 +112,11 @@ public static BlazeJavacResult compile(BlazeJavacArguments arguments) {
try (JavacFileManager fileManager =
new ClassloaderMaskingFileManager(context, arguments.builtinProcessors())) {

bootFileManager = getBootFileManager(arguments);
if (bootFileManager != null) {
setLocations(bootFileManager, arguments);
}

setLocations(fileManager, arguments);

JavacTask task =
Expand Down Expand Up @@ -305,6 +313,32 @@ private static void setLocations(JavacFileManager fileManager, BlazeJavacArgumen
}
}

/** a javac file manager instance specific only for boot classpaths */
private static BootClassPathCachingFileManager bootFileManager;

/**
* Returns a BootClassPathCachingFileManager instance if it is a singleplex-worker with valid
* arguments
*/
private static BootClassPathCachingFileManager getBootFileManager(BlazeJavacArguments arguments) {

if (!arguments.requestId().isPresent()) {
// worker mode is not enabled
return null;
}
if (arguments.requestId().getAsInt() != 0) {
// worker is not singleplex worker
return null;
}
if (!BootClassPathCachingFileManager.areArgumentsValid(arguments)) {
return null;
}

return (bootFileManager != null && !bootFileManager.needsUpdate(arguments))
? bootFileManager
: new BootClassPathCachingFileManager(new Context(), arguments);
}

/**
* When Bazel invokes JavaBuilder, it puts javac.jar on the bootstrap class path and
* JavaBuilder_deploy.jar on the user class path. We need Error Prone to be available on the
Expand All @@ -321,6 +355,16 @@ public ClassloaderMaskingFileManager(Context context, ImmutableSet<String> built
this.builtinProcessors = builtinProcessors;
}

@Override
public Iterable<JavaFileObject> list(
Location location, String packageName, Set<Kind> kinds, boolean recurse)
throws IOException {
if (bootFileManager != null && location == StandardLocation.PLATFORM_CLASS_PATH) {
return bootFileManager.list(location, packageName, kinds, recurse);
}
return super.list(location, packageName, kinds, recurse);
}

@Override
protected ClassLoader getClassLoader(URL[] urls) {
return new URLClassLoader(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2021 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.javac;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.ByteString;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.util.Context;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

/** A subclass of the JavacFileManager that handles only boot classpaths */
@VisibleForTesting
class BootClassPathCachingFileManager extends JavacFileManager {

private final Map<String, ByteString> bootJarsAndDigest = new HashMap<>();

/** Create a JavacFileManager using a given context and BlazeJavacArguments */
public BootClassPathCachingFileManager(Context context, BlazeJavacArguments arguments) {
super(context, false, UTF_8);

for (Path bootClassPath : arguments.bootClassPath()) {
bootJarsAndDigest.put(
bootClassPath.toString(), arguments.inputsAndDigest().get(bootClassPath.toString()));
}
}

/**
* Checks if this instance or a new instance is needed for the {@code BlazeJavacArguments}. An
* update is needed if the bootclasspath in {@code BlazeJavacArguments} have changed its digest.
*/
@VisibleForTesting
boolean needsUpdate(BlazeJavacArguments arguments) {
for (Path bootClassPath : arguments.bootClassPath()) {
ByteString currDigest = arguments.inputsAndDigest().get(bootClassPath.toString());

if (currDigest == null) {
return true;
}

ByteString oldDigest = bootJarsAndDigest.putIfAbsent(bootClassPath.toString(), currDigest);

if (oldDigest != null && !oldDigest.equals(currDigest)) {
return true;
}
}
return false;
}

/**
* Checks if the bootClassPaths in {@code BlazeJavacArguments} can benefit from the {@code
* BootCachingFileManager}. Arguments are not valid if missing boot classpath or at least one
* digest
*/
@VisibleForTesting
static boolean areArgumentsValid(BlazeJavacArguments arguments) {
if (arguments.bootClassPath().isEmpty()) {
return false;
}

for (Path bootClassPath : arguments.bootClassPath()) {
ByteString currDigest = arguments.inputsAndDigest().get(bootClassPath.toString());
if (currDigest == null) {
return false;
}
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright 2021 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.javac;

import static com.google.common.truth.Truth.assertThat;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.protobuf.ByteString;
import com.sun.tools.javac.util.Context;
import java.io.IOException;
import java.nio.file.Path;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** {@link BootClassPathCachingFileManager}Test */
@RunWith(JUnit4.class)
public class BootClassPathCachingFileManagerTest {
private ImmutableList<Path> bootClassPathsCandidates;
private BootClassPathCachingFileManager bootFileManager;

@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();

@Before
public void createBootFileManager() throws IOException {
bootClassPathsCandidates =
ImmutableList.of(
temporaryFolder.newFolder().toPath().resolve("BootClassPath0.jar"),
temporaryFolder.newFolder().toPath().resolve("BootClassPath1.jar"),
temporaryFolder.newFolder().toPath().resolve("BootClassPath2.jar"));

ImmutableList<Path> bootClassPaths =
ImmutableList.of(bootClassPathsCandidates.get(0), bootClassPathsCandidates.get(1));

ImmutableMap<String, ByteString> inputsAndDigest =
ImmutableMap.<String, ByteString>builder()
.put(
bootClassPaths.get(0).toString(),
ByteString.copyFromUtf8(bootClassPaths.get(0).toString()))
.put(
bootClassPaths.get(1).toString(),
ByteString.copyFromUtf8(bootClassPaths.get(1).toString()))
.build();

BlazeJavacArguments arguments =
BlazeJavacArguments.builder()
.bootClassPath(bootClassPaths)
.inputsAndDigest(inputsAndDigest)
.classOutput(temporaryFolder.newFolder().toPath().resolve("output_0.jar"))
.build();

bootFileManager = new BootClassPathCachingFileManager(new Context(), arguments);
}

@Test
public void testNeedsUpdate() throws IOException {
ImmutableList<Path> bootClassPaths =
ImmutableList.of(
bootClassPathsCandidates.get(0),
bootClassPathsCandidates.get(1),
bootClassPathsCandidates.get(2));

ImmutableMap<String, ByteString> inputsAndDigest =
ImmutableMap.<String, ByteString>builder()
.put(
bootClassPaths.get(0).toString(),
ByteString.copyFromUtf8(bootClassPaths.get(0).toString()))
.put(
bootClassPaths.get(1).toString(),
ByteString.copyFromUtf8(bootClassPaths.get(1).toString()))
.put(
bootClassPaths.get(2).toString(),
ByteString.copyFromUtf8(bootClassPaths.get(2).toString()))
.build();

BlazeJavacArguments arguments =
BlazeJavacArguments.builder()
.bootClassPath(bootClassPaths)
.inputsAndDigest(inputsAndDigest)
.classOutput(temporaryFolder.newFolder().toPath().resolve("output_1.jar"))
.build();

assertThat(bootFileManager.needsUpdate(arguments)).isFalse();
}

@Test
public void testNeedsUpdate_withDifferentDigest() throws IOException {
ImmutableList<Path> bootClassPaths =
ImmutableList.of(bootClassPathsCandidates.get(0), bootClassPathsCandidates.get(1));

ImmutableMap<String, ByteString> inputsAndDigest =
ImmutableMap.<String, ByteString>builder()
.put(bootClassPaths.get(0).toString(), ByteString.copyFromUtf8("different digest"))
.put(
bootClassPaths.get(1).toString(),
ByteString.copyFromUtf8(bootClassPaths.get(1).toString()))
.build();

BlazeJavacArguments arguments =
BlazeJavacArguments.builder()
.bootClassPath(bootClassPaths)
.inputsAndDigest(inputsAndDigest)
.classOutput(temporaryFolder.newFolder().toPath().resolve("output_1.jar"))
.build();

assertThat(bootFileManager.needsUpdate(arguments)).isTrue();
}

@Test
public void testAreArgumentsValid_withOneMissingDigest() throws IOException {
ImmutableList<Path> bootClassPaths =
ImmutableList.of(bootClassPathsCandidates.get(0), bootClassPathsCandidates.get(1));

ImmutableMap<String, ByteString> inputsAndDigest =
ImmutableMap.of(
bootClassPaths.get(1).toString(),
ByteString.copyFromUtf8(bootClassPaths.get(1).toString()));

BlazeJavacArguments arguments =
BlazeJavacArguments.builder()
.bootClassPath(bootClassPaths)
.inputsAndDigest(inputsAndDigest)
.classOutput(temporaryFolder.newFolder().toPath().resolve("output_1.jar"))
.build();

assertThat(BootClassPathCachingFileManager.areArgumentsValid(arguments)).isFalse();
}

@Test
public void testAreArgumentsValid_withEmptyInputsAndDigest() throws IOException {
ImmutableList<Path> bootClassPaths =
ImmutableList.of(bootClassPathsCandidates.get(0), bootClassPathsCandidates.get(1));

BlazeJavacArguments arguments =
BlazeJavacArguments.builder()
.bootClassPath(bootClassPaths)
.classOutput(temporaryFolder.newFolder().toPath().resolve("output_1.jar"))
.build();

assertThat(BootClassPathCachingFileManager.areArgumentsValid(arguments)).isFalse();
}

@Test
public void testAreArgumentsValid_withEmptyBootClassPaths() throws IOException {
assertThat(
BootClassPathCachingFileManager.areArgumentsValid(
BlazeJavacArguments.builder()
.classOutput(temporaryFolder.newFolder().toPath().resolve("output_1.jar"))
.build()))
.isFalse();
}
}

0 comments on commit a0e5e45

Please sign in to comment.