Add an API for cancelling the compilation before completion.
Bug: b/274628704
Change-Id: Ic52967b32aa2ec4454f5b4ffb4b458674e91b698
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index f251ecb..285ef1b 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -65,6 +65,7 @@
private final List<ArtProfileForRewriting> artProfilesForRewriting;
private final List<StartupProfileProvider> startupProfileProviders;
private final ClassConflictResolver classConflictResolver;
+ private final CancelCompilationChecker cancelCompilationChecker;
BaseCompilerCommand(boolean printHelp, boolean printVersion) {
super(printHelp, printVersion);
@@ -87,6 +88,7 @@
artProfilesForRewriting = null;
startupProfileProviders = null;
classConflictResolver = null;
+ cancelCompilationChecker = null;
}
BaseCompilerCommand(
@@ -109,7 +111,8 @@
boolean isAndroidPlatformBuild,
List<ArtProfileForRewriting> artProfilesForRewriting,
List<StartupProfileProvider> startupProfileProviders,
- ClassConflictResolver classConflictResolver) {
+ ClassConflictResolver classConflictResolver,
+ CancelCompilationChecker cancelCompilationChecker) {
super(app);
assert minApiLevel > 0;
assert mode != null;
@@ -132,6 +135,7 @@
this.artProfilesForRewriting = artProfilesForRewriting;
this.startupProfileProviders = startupProfileProviders;
this.classConflictResolver = classConflictResolver;
+ this.cancelCompilationChecker = cancelCompilationChecker;
}
/**
@@ -244,6 +248,10 @@
return classConflictResolver;
}
+ public CancelCompilationChecker getCancelCompilationChecker() {
+ return cancelCompilationChecker;
+ }
+
DumpInputFlags getDumpInputFlags() {
return dumpInputFlags;
}
@@ -287,6 +295,7 @@
private List<ArtProfileForRewriting> artProfilesForRewriting = new ArrayList<>();
private List<StartupProfileProvider> startupProfileProviders = new ArrayList<>();
private ClassConflictResolver classConflictResolver = null;
+ private CancelCompilationChecker cancelCompilationChecker = null;
abstract CompilationMode defaultCompilationMode();
@@ -733,6 +742,21 @@
}
/**
+ * Set a cancellation checker.
+ *
+ * <p>The cancellation checker will be periodically called to check if the compilation should be
+ * cancelled before completion.
+ */
+ public B setCancelCompilationChecker(CancelCompilationChecker checker) {
+ this.cancelCompilationChecker = checker;
+ return self();
+ }
+
+ public CancelCompilationChecker getCancelCompilationChecker() {
+ return cancelCompilationChecker;
+ }
+
+ /**
* Allow to skip to dump into file and dump into directory instruction, this is primarily used
* for chained compilation in L8 so there are no duplicated dumps.
*/
diff --git a/src/main/java/com/android/tools/r8/CancelCompilationChecker.java b/src/main/java/com/android/tools/r8/CancelCompilationChecker.java
new file mode 100644
index 0000000..f08cde1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/CancelCompilationChecker.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+/** Client supplied checker to allow cancelling a compilation before completion. */
+@Keep
+public interface CancelCompilationChecker {
+
+ /**
+ * Callback that is called periodically by the compiler to check if the current compilation should
+ * be cancelled before completion. The method should return true or false. The behavior of
+ * throwing an exception from this method will also abort the compilation but the compiler will
+ * treat such cases as an unexpected runtime failure and not as an expected client cancellation.
+ *
+ * <p>If this method returns true at any point before compilation has successfully completed, then
+ * the compiler will exit with a {@link CompilationFailedException}. It is *not* possible to
+ * cancel the cancellation.
+ *
+ * <p>This method may be called from multiple compiler threads. It is up to the client to ensure
+ * thread safety in determining if compilation should be cancelled.
+ *
+ * @return True if the compilation should be cancelled, otherwise false.
+ */
+ boolean cancel();
+}
diff --git a/src/main/java/com/android/tools/r8/CompilationFailedException.java b/src/main/java/com/android/tools/r8/CompilationFailedException.java
index 0078c06..94c7fde 100644
--- a/src/main/java/com/android/tools/r8/CompilationFailedException.java
+++ b/src/main/java/com/android/tools/r8/CompilationFailedException.java
@@ -10,19 +10,15 @@
@Keep
public class CompilationFailedException extends Exception {
- public CompilationFailedException() {
- super("Compilation failed to complete");
- }
+ private final boolean cancelled;
- public CompilationFailedException(Throwable cause) {
- this("Compilation failed to complete", cause);
- }
-
- public CompilationFailedException(String message, Throwable cause) {
+ CompilationFailedException(String message, Throwable cause, boolean cancelled) {
super(message, cause);
+ this.cancelled = cancelled;
}
- public CompilationFailedException(String message) {
- super(message);
+ /** True if the compilation was cancelled by {@link CancelCompilationChecker} otherwise false. */
+ public boolean wasCancelled() {
+ return cancelled;
}
}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 1abfbd2..49f8155 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -491,6 +491,7 @@
getArtProfilesForRewriting(),
getStartupProfileProviders(),
getClassConflictResolver(),
+ getCancelCompilationChecker(),
factory);
}
}
@@ -586,6 +587,7 @@
List<ArtProfileForRewriting> artProfilesForRewriting,
List<StartupProfileProvider> startupProfileProviders,
ClassConflictResolver classConflictResolver,
+ CancelCompilationChecker cancelCompilationChecker,
DexItemFactory factory) {
super(
inputApp,
@@ -607,7 +609,8 @@
isAndroidPlatformBuild,
artProfilesForRewriting,
startupProfileProviders,
- classConflictResolver);
+ classConflictResolver,
+ cancelCompilationChecker);
this.intermediate = intermediate;
this.globalSyntheticsConsumer = globalSyntheticsConsumer;
this.syntheticInfoConsumer = syntheticInfoConsumer;
@@ -749,6 +752,8 @@
ProgramClassCollection.wrappedConflictResolver(
getClassConflictResolver(), internal.reporter);
+ internal.cancelCompilationChecker = getCancelCompilationChecker();
+
internal.tool = Tool.D8;
internal.setDumpInputFlags(getDumpInputFlags());
internal.dumpOptions = dumpOptions();
diff --git a/src/main/java/com/android/tools/r8/InternalCompilationFailedExceptionUtils.java b/src/main/java/com/android/tools/r8/InternalCompilationFailedExceptionUtils.java
new file mode 100644
index 0000000..18e96ff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/InternalCompilationFailedExceptionUtils.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+/** Non-API class to allocate compilation failed exceptions without exposing the constructor. */
+public final class InternalCompilationFailedExceptionUtils {
+
+ private InternalCompilationFailedExceptionUtils() {}
+
+ public static CompilationFailedException createForTesting() {
+ return createForTesting("Compilation failed to complete", null);
+ }
+
+ public static CompilationFailedException createForTesting(String message) {
+ return createForTesting(message, null);
+ }
+
+ public static CompilationFailedException createForTesting(Throwable cause) {
+ return createForTesting("Compilation failed to complete", cause);
+ }
+
+ public static CompilationFailedException createForTesting(String message, Throwable cause) {
+ return create(message, cause, false);
+ }
+
+ public static CompilationFailedException create(
+ String message, Throwable cause, boolean cancelled) {
+ return new CompilationFailedException(message, cause, cancelled);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 6bac5df..7a28ef3 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -102,6 +102,7 @@
DumpInputFlags dumpInputFlags,
MapIdProvider mapIdProvider,
ClassConflictResolver classConflictResolver,
+ CancelCompilationChecker cancelCompilationChecker,
DexItemFactory factory) {
super(
inputApp,
@@ -123,7 +124,8 @@
false,
null,
null,
- classConflictResolver);
+ classConflictResolver,
+ cancelCompilationChecker);
this.d8Command = d8Command;
this.r8Command = r8Command;
this.desugaredLibrarySpecification = desugaredLibrarySpecification;
@@ -228,6 +230,8 @@
ProgramClassCollection.wrappedConflictResolver(
getClassConflictResolver(), internal.reporter);
+ internal.cancelCompilationChecker = getCancelCompilationChecker();
+
if (!DETERMINISTIC_DEBUGGING) {
assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
internal.threadCount = getThreadCount();
@@ -456,6 +460,7 @@
getDumpInputFlags(),
getMapIdProvider(),
getClassConflictResolver(),
+ getCancelCompilationChecker(),
factory);
}
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 5fc02c3..6003038 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -659,7 +659,8 @@
getAndroidPlatformBuild(),
getArtProfilesForRewriting(),
getStartupProfileProviders(),
- getClassConflictResolver());
+ getClassConflictResolver(),
+ getCancelCompilationChecker());
if (inputDependencyGraphConsumer != null) {
inputDependencyGraphConsumer.finished();
@@ -938,7 +939,8 @@
boolean isAndroidPlatformBuild,
List<ArtProfileForRewriting> artProfilesForRewriting,
List<StartupProfileProvider> startupProfileProviders,
- ClassConflictResolver classConflictResolver) {
+ ClassConflictResolver classConflictResolver,
+ CancelCompilationChecker cancelCompilationChecker) {
super(
inputApp,
mode,
@@ -959,7 +961,8 @@
isAndroidPlatformBuild,
artProfilesForRewriting,
startupProfileProviders,
- classConflictResolver);
+ classConflictResolver,
+ cancelCompilationChecker);
assert proguardConfiguration != null;
assert mainDexKeepRules != null;
this.mainDexKeepRules = mainDexKeepRules;
@@ -1176,6 +1179,8 @@
ProgramClassCollection.wrappedConflictResolver(
getClassConflictResolver(), internal.reporter);
+ internal.cancelCompilationChecker = getCancelCompilationChecker();
+
if (!DETERMINISTIC_DEBUGGING) {
assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
internal.threadCount = getThreadCount();
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 8f619ac..3d2740a 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -425,7 +425,10 @@
run(mappedArgs, retraceDiagnosticsHandler);
} catch (Throwable t) {
throw failWithFakeEntry(
- retraceDiagnosticsHandler, t, RetraceFailedException::new, RetraceAbortException.class);
+ retraceDiagnosticsHandler,
+ t,
+ (message, cause, ignore) -> new RetraceFailedException(message, cause),
+ RetraceAbortException.class);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/CancelCompilationException.java b/src/main/java/com/android/tools/r8/utils/CancelCompilationException.java
new file mode 100644
index 0000000..0070efe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/CancelCompilationException.java
@@ -0,0 +1,6 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+public class CancelCompilationException extends RuntimeException {}
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index ef5c3ca..959a25e 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.InternalCompilationFailedExceptionUtils;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.Version;
@@ -19,7 +20,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
-import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@@ -87,13 +87,16 @@
private static CompilationFailedException failCompilation(
Reporter reporter, Throwable topMostException) {
return failWithFakeEntry(
- reporter, topMostException, CompilationFailedException::new, AbortException.class);
+ reporter,
+ topMostException,
+ InternalCompilationFailedExceptionUtils::create,
+ AbortException.class);
}
public static <T extends Exception, A extends Exception> T failWithFakeEntry(
DiagnosticsHandler diagnosticsHandler,
Throwable topMostException,
- BiFunction<String, Throwable, T> newException,
+ TriFunction<String, Throwable, Boolean, T> newException,
Class<A> abortException) {
// Find inner-most cause of the failure and compute origin, position and reported for the path.
boolean hasBeenReported = false;
@@ -122,8 +125,13 @@
innerMostCause.addSuppressed(topMostException);
}
+ boolean cancelled =
+ topMostException instanceof CancelCompilationException
+ || innerMostCause instanceof CancelCompilationException;
+ assert !cancelled || topMostException == innerMostCause;
+
// If no abort is seen, the exception is not reported, so report it now.
- if (!hasBeenReported) {
+ if (!cancelled && !hasBeenReported) {
diagnosticsHandler.error(new ExceptionDiagnostic(innerMostCause, origin, position));
}
@@ -136,7 +144,7 @@
message.append(", origin: ").append(origin);
}
// Create the final exception object.
- T rethrow = newException.apply(message.toString(), innerMostCause);
+ T rethrow = newException.apply(message.toString(), innerMostCause, cancelled);
// Replace its stack by the cause stack and insert version info at the top.
String filename = "Version_" + Version.LABEL + ".java";
StackTraceElement versionElement =
@@ -244,6 +252,8 @@
Origin origin, Position position, Supplier<T> action) {
try {
return action.get();
+ } catch (CancelCompilationException e) {
+ throw e;
} catch (RuntimeException e) {
throw OriginAttachmentException.wrap(e, origin, position);
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 6c09eb1..8ce6323 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.utils.AndroidApiLevel.B;
import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
+import com.android.tools.r8.CancelCompilationChecker;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.DataResourceConsumer;
@@ -167,6 +168,27 @@
return itemFactory;
}
+ // Internal state signifying that the compilation is cancelled.
+ // The state can only ever transition from false to true.
+ private final AtomicBoolean cancelled = new AtomicBoolean(false);
+ public CancelCompilationChecker cancelCompilationChecker = null;
+
+ public boolean checkIfCancelled() {
+ if (cancelCompilationChecker == null) {
+ assert !cancelled.get();
+ return false;
+ }
+ if (cancelled.get()) {
+ return true;
+ }
+ if (cancelCompilationChecker.cancel()) {
+ cancelled.set(true);
+ return true;
+ }
+ // Return the cancelled value in case another thread has cancelled.
+ return cancelled.get();
+ }
+
public boolean hasProguardConfiguration() {
return proguardConfiguration != null;
}
diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java
index 3947cae..dc7db26 100644
--- a/src/main/java/com/android/tools/r8/utils/Timing.java
+++ b/src/main/java/com/android/tools/r8/utils/Timing.java
@@ -62,11 +62,60 @@
return Timing.EMPTY;
}
+ private static class TimingWithCancellation extends Timing {
+ private final InternalOptions options;
+ private final Timing timing;
+
+ TimingWithCancellation(InternalOptions options, Timing timing) {
+ super("<cancel>", false);
+ this.options = options;
+ this.timing = timing;
+ }
+
+ @Override
+ public TimingMerger beginMerger(String title, int numberOfThreads) {
+ return timing.beginMerger(title, numberOfThreads);
+ }
+
+ @Override
+ public void begin(String title) {
+ if (options.checkIfCancelled()) {
+ throw new CancelCompilationException();
+ }
+ timing.begin(title);
+ }
+
+ @Override
+ public <E extends Exception> void time(String title, ThrowingAction<E> action) throws E {
+ timing.time(title, action);
+ }
+
+ @Override
+ public <T, E extends Exception> T time(String title, ThrowingSupplier<T, E> supplier) throws E {
+ return timing.time(title, supplier);
+ }
+
+ @Override
+ public void end() {
+ timing.end();
+ }
+
+ @Override
+ public void report() {
+ timing.report();
+ }
+ }
+
public static Timing create(String title, InternalOptions options) {
// We also create a timer when running assertions to validate wellformedness of the node stack.
- return options.printTimes || InternalOptions.assertionsEnabled()
- ? new Timing(title, options.printMemory)
- : Timing.empty();
+ Timing timing =
+ options.printTimes || InternalOptions.assertionsEnabled()
+ ? new Timing(title, options.printMemory)
+ : Timing.empty();
+ if (options.cancelCompilationChecker != null) {
+ return new TimingWithCancellation(options, timing);
+ }
+ return timing;
}
public static Timing create(String title, boolean printMemory) {
diff --git a/src/test/java/com/android/tools/r8/CommandTestBase.java b/src/test/java/com/android/tools/r8/CommandTestBase.java
index 6c72b39..1ea2212 100644
--- a/src/test/java/com/android/tools/r8/CommandTestBase.java
+++ b/src/test/java/com/android/tools/r8/CommandTestBase.java
@@ -96,7 +96,7 @@
try {
command.getReporter().failIfPendingErrors();
} catch (RuntimeException e) {
- throw new CompilationFailedException();
+ throw InternalCompilationFailedExceptionUtils.createForTesting();
}
});
fail("Failure expected");
@@ -144,7 +144,7 @@
try {
command.getReporter().failIfPendingErrors();
} catch (RuntimeException e) {
- throw new CompilationFailedException();
+ throw InternalCompilationFailedExceptionUtils.createForTesting();
}
});
fail("Failure expected");
@@ -190,7 +190,7 @@
try {
command.getReporter().failIfPendingErrors();
} catch (RuntimeException e) {
- throw new CompilationFailedException();
+ throw InternalCompilationFailedExceptionUtils.createForTesting();
}
});
fail("Expected failure");
@@ -209,7 +209,7 @@
try {
command.getReporter().failIfPendingErrors();
} catch (RuntimeException e) {
- throw new CompilationFailedException();
+ throw InternalCompilationFailedExceptionUtils.createForTesting();
}
});
fail("Expected failure");
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index 2808f05..444d14b 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -197,7 +197,7 @@
return new ExternalR8TestCompileResult(
getState(), outputJar, processResult, proguardMap, getMinApiLevel(), getOutputMode());
} catch (IOException e) {
- throw new CompilationFailedException(e);
+ throw InternalCompilationFailedExceptionUtils.createForTesting(e);
}
}
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index d91646a..ac6e335 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -114,14 +114,14 @@
ProcessBuilder pbuilder = new ProcessBuilder(command);
ProcessResult result = ToolHelper.runProcess(pbuilder, getStdoutForTesting());
if (result.exitCode != 0) {
- throw new CompilationFailedException(result.toString());
+ throw InternalCompilationFailedExceptionUtils.createForTesting(result.toString());
}
String proguardMap =
Files.exists(mapFile) ? FileUtils.readTextFile(mapFile, Charsets.UTF_8) : "";
return new ProguardTestCompileResult(
result, getState(), outJar, getMinApiLevel(), proguardMap);
} catch (IOException e) {
- throw new CompilationFailedException(e);
+ throw InternalCompilationFailedExceptionUtils.createForTesting(e);
}
}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index 1bbaf31..71c9dc4 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.compilerapi.androidplatformbuild.AndroidPlatformBuildApiTest;
import com.android.tools.r8.compilerapi.artprofiles.ArtProfilesForRewritingApiTest;
import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
+import com.android.tools.r8.compilerapi.cancelcompilationchecker.CancelCompilationCheckerTest;
import com.android.tools.r8.compilerapi.classconflictresolver.ClassConflictResolverTest;
import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
import com.android.tools.r8.compilerapi.diagnostics.ProguardKeepRuleDiagnosticsApiTest;
@@ -62,7 +63,7 @@
ExtractMarkerApiTest.ApiTest.class);
private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
- ImmutableList.of();
+ ImmutableList.of(CancelCompilationCheckerTest.ApiTest.class);
private final TemporaryFolder temp;
diff --git a/src/test/java/com/android/tools/r8/compilerapi/cancelcompilationchecker/CancelCompilationCheckerTest.java b/src/test/java/com/android/tools/r8/compilerapi/cancelcompilationchecker/CancelCompilationCheckerTest.java
new file mode 100644
index 0000000..8afacb4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/cancelcompilationchecker/CancelCompilationCheckerTest.java
@@ -0,0 +1,123 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi.cancelcompilationchecker;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CancelCompilationChecker;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.IntBox;
+import java.util.function.BooleanSupplier;
+import org.junit.Test;
+
+public class CancelCompilationCheckerTest extends CompilerApiTestRunner {
+
+ public CancelCompilationCheckerTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ // Use an integer box to delay the cancel until some time internal in the compiler.
+ IntBox i = new IntBox();
+ try {
+ new ApiTest(ApiTest.PARAMETERS).runD8(() -> i.incrementAndGet() > 10);
+ fail("excepted cancelled");
+ } catch (CompilationFailedException e) {
+ assertTrue(e.wasCancelled());
+ }
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ // Use an integer box to delay the cancel until some time internal in the compiler.
+ IntBox i = new IntBox();
+ try {
+ new ApiTest(ApiTest.PARAMETERS).runR8(() -> i.incrementAndGet() > 20);
+ fail("excepted cancelled");
+ } catch (CompilationFailedException e) {
+ assertTrue(e.wasCancelled());
+ }
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ public void runD8(BooleanSupplier supplier) throws Exception {
+ D8.run(
+ D8Command.builder()
+ .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .setCancelCompilationChecker(
+ new CancelCompilationChecker() {
+ @Override
+ public boolean cancel() {
+ return supplier.getAsBoolean();
+ }
+ })
+ .build());
+ }
+
+ public void runR8(BooleanSupplier supplier) throws Exception {
+ R8.run(
+ R8Command.builder()
+ .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .setCancelCompilationChecker(
+ new CancelCompilationChecker() {
+ @Override
+ public boolean cancel() {
+ return supplier.getAsBoolean();
+ }
+ })
+ .build());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ try {
+ runD8(() -> true);
+ } catch (CompilationFailedException e) {
+ if (e.wasCancelled()) {
+ return;
+ }
+ }
+ throw new AssertionError("expected cancelled");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ try {
+ runR8(() -> true);
+ } catch (CompilationFailedException e) {
+ if (e.wasCancelled()) {
+ return;
+ }
+ }
+ throw new AssertionError("expected cancelled");
+ }
+ }
+}