aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib
diff options
context:
space:
mode:
authorGravatar Googler <noreply@google.com>2018-05-23 12:32:07 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-05-23 12:33:20 -0700
commit29eafdfe329b300dc42fddafde87bddae2f07a4c (patch)
tree7b20b5796aeb5c32ce66c3160ebdb8f56d32106e /src/main/java/com/google/devtools/build/lib
parent3e951fcb946b9f8efdef7a84a2fb0fe03ede010e (diff)
Initial implementation of a Skylark debug server API.
I've pulled out the API for separate review. It includes all hooks from blaze/skylark used by the debugger. Debuggable thread contexts are currently declared in 3 places: - BuildFileAST (top-level evaluation of BUILD files) - SkylarkRuleConfiguredTargetUtil (rules) - SkylarkAspectFactory (aspects) The purpose of declaring these contexts is so that the debugger can track currently-active threads (and stop tracking them when the task is completed). Details of the actual debugging server are in unknown commit. PiperOrigin-RevId: 197770547
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib')
-rw-r--r--src/main/java/com/google/devtools/build/lib/BUILD3
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java18
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/Bazel.java1
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java50
-rw-r--r--src/main/java/com/google/devtools/build/lib/skylarkdebug/module/BUILD25
-rw-r--r--src/main/java/com/google/devtools/build/lib/skylarkdebug/module/SkylarkDebuggerModule.java47
-rw-r--r--src/main/java/com/google/devtools/build/lib/skylarkdebug/module/SkylarkDebuggerOptions.java45
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java16
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/DebugFrame.java58
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/DebugServer.java42
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/DebugServerUtils.java64
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/Debuggable.java80
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/Environment.java90
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/Eval.java25
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java2
15 files changed, 534 insertions, 32 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index 3704699f2d..ada11c9514 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -59,6 +59,7 @@ filegroup(
"//src/main/java/com/google/devtools/build/lib/skylarkbuildapi:srcs",
"//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp:srcs",
"//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/java:srcs",
+ "//src/main/java/com/google/devtools/build/lib/skylarkdebug/module:srcs",
"//src/main/java/com/google/devtools/build/lib/skylarkdebug/proto:srcs",
"//src/main/java/com/google/devtools/build/lib/skylarkdebug/server:srcs",
"//src/main/java/com/google/devtools/build/lib/skylarkinterface/processor:srcs",
@@ -710,6 +711,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/profiler/memory:allocationtracker_module",
"//src/main/java/com/google/devtools/build/lib/remote",
"//src/main/java/com/google/devtools/build/lib/sandbox",
+ "//src/main/java/com/google/devtools/build/lib/skylarkdebug/module",
"//src/main/java/com/google/devtools/build/lib/ssd",
"//src/main/java/com/google/devtools/build/lib/standalone",
"//src/main/java/com/google/devtools/build/lib/worker",
@@ -1289,6 +1291,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/query2:query-engine",
"//src/main/java/com/google/devtools/build/lib/query2:query-output",
"//src/main/java/com/google/devtools/build/lib/shell",
+ "//src/main/java/com/google/devtools/build/lib/skylarkdebug/module:options",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/build/lib/windows",
"//src/main/java/com/google/devtools/build/skyframe",
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
index 1cc7543472..5dd147985f 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
@@ -43,6 +43,7 @@ import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
import com.google.devtools.build.lib.syntax.BaseFunction;
import com.google.devtools.build.lib.syntax.ClassObject;
+import com.google.devtools.build.lib.syntax.DebugServerUtils;
import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.EvalExceptionWithStackTrace;
@@ -92,12 +93,19 @@ public final class SkylarkRuleConfiguredTargetUtil {
.setEventHandler(ruleContext.getAnalysisEnvironment().getEventHandler())
.build(); // NB: loading phase functions are not available: this is analysis already,
// so we do *not* setLoadingPhase().
+
+ final SkylarkRuleContext finalContext = skylarkRuleContext;
Object target =
- ruleImplementation.call(
- /*args=*/ ImmutableList.of(skylarkRuleContext),
- /*kwargs*/ ImmutableMap.of(),
- /*ast=*/ null,
- env);
+ DebugServerUtils.runWithDebuggingIfEnabled(
+ env,
+ () ->
+ String.format("Target %s", ruleContext.getTarget().getLabel().getCanonicalForm()),
+ () ->
+ ruleImplementation.call(
+ /*args=*/ ImmutableList.of(finalContext),
+ /*kwargs*/ ImmutableMap.of(),
+ /*ast=*/ null,
+ env));
if (ruleContext.hasErrors()) {
return null;
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/Bazel.java b/src/main/java/com/google/devtools/build/lib/bazel/Bazel.java
index 6c3e1d777f..c6cabaa75f 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/Bazel.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/Bazel.java
@@ -43,6 +43,7 @@ public final class Bazel {
com.google.devtools.build.lib.bazel.BazelWorkspaceStatusModule.class,
com.google.devtools.build.lib.bazel.BazelDiffAwarenessModule.class,
com.google.devtools.build.lib.bazel.BazelRepositoryModule.class,
+ com.google.devtools.build.lib.skylarkdebug.module.SkylarkDebuggerModule.class,
com.google.devtools.build.lib.bazel.repository.RepositoryResolvedModule.class,
com.google.devtools.build.lib.bazel.SpawnLogModule.class,
com.google.devtools.build.lib.ssd.SsdModule.class,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
index a01560e427..fad7c84150 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
@@ -29,6 +29,7 @@ import com.google.devtools.build.lib.packages.Info;
import com.google.devtools.build.lib.packages.SkylarkDefinedAspect;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
+import com.google.devtools.build.lib.syntax.DebugServerUtils;
import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.EvalExceptionWithStackTrace;
@@ -37,9 +38,7 @@ import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.SkylarkType;
import java.util.Map;
-/**
- * A factory for aspects that are defined in Skylark.
- */
+/** A factory for aspects that are defined in Skylark. */
public class SkylarkAspectFactory implements ConfiguredAspectFactory {
private final SkylarkDefinedAspect skylarkAspect;
@@ -54,12 +53,13 @@ public class SkylarkAspectFactory implements ConfiguredAspectFactory {
throws InterruptedException {
SkylarkRuleContext skylarkRuleContext = null;
try (Mutability mutability = Mutability.create("aspect")) {
- AspectDescriptor aspectDescriptor = new AspectDescriptor(
- skylarkAspect.getAspectClass(), parameters);
+ AspectDescriptor aspectDescriptor =
+ new AspectDescriptor(skylarkAspect.getAspectClass(), parameters);
AnalysisEnvironment analysisEnv = ruleContext.getAnalysisEnvironment();
try {
- skylarkRuleContext = new SkylarkRuleContext(
- ruleContext, aspectDescriptor, analysisEnv.getSkylarkSemantics());
+ skylarkRuleContext =
+ new SkylarkRuleContext(
+ ruleContext, aspectDescriptor, analysisEnv.getSkylarkSemantics());
} catch (EvalException e) {
ruleContext.ruleError(e.getMessage());
return null;
@@ -71,16 +71,25 @@ public class SkylarkAspectFactory implements ConfiguredAspectFactory {
// NB: loading phase functions are not available: this is analysis already, so we do
// *not* setLoadingPhase().
.build();
- Object aspectSkylarkObject;
try {
- aspectSkylarkObject =
- skylarkAspect
- .getImplementation()
- .call(
- /*args=*/ ImmutableList.of(ctadBase.getConfiguredTarget(), skylarkRuleContext),
- /* kwargs= */ ImmutableMap.of(),
- /*ast=*/ null,
- env);
+ final SkylarkRuleContext finalRuleContext = skylarkRuleContext;
+ Object aspectSkylarkObject =
+ DebugServerUtils.runWithDebuggingIfEnabled(
+ env,
+ () ->
+ String.format(
+ "Aspect %s on %s",
+ skylarkAspect.getName(),
+ ruleContext.getTarget().getLabel().getCanonicalForm()),
+ () ->
+ skylarkAspect
+ .getImplementation()
+ .call(
+ /*args=*/ ImmutableList.of(
+ ctadBase.getConfiguredTarget(), finalRuleContext),
+ /* kwargs= */ ImmutableMap.of(),
+ /*ast=*/ null,
+ env));
if (ruleContext.hasErrors()) {
return null;
@@ -99,9 +108,9 @@ public class SkylarkAspectFactory implements ConfiguredAspectFactory {
return null;
}
} finally {
- if (skylarkRuleContext != null) {
- skylarkRuleContext.nullify();
- }
+ if (skylarkRuleContext != null) {
+ skylarkRuleContext.nullify();
+ }
}
}
@@ -160,8 +169,7 @@ public class SkylarkAspectFactory implements ConfiguredAspectFactory {
}
}
- private static void addOutputGroups(Object value, Location loc,
- ConfiguredAspect.Builder builder)
+ private static void addOutputGroups(Object value, Location loc, ConfiguredAspect.Builder builder)
throws EvalException {
Map<String, SkylarkValue> outputGroups =
SkylarkType.castMap(value, String.class, SkylarkValue.class, "output_groups");
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkdebug/module/BUILD b/src/main/java/com/google/devtools/build/lib/skylarkdebug/module/BUILD
new file mode 100644
index 0000000000..00fae167a0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skylarkdebug/module/BUILD
@@ -0,0 +1,25 @@
+package(default_visibility = ["//src:__subpackages__"])
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+ visibility = ["//src/main/java/com/google/devtools/build/lib:__pkg__"],
+)
+
+java_library(
+ name = "module",
+ srcs = ["SkylarkDebuggerModule.java"],
+ deps = [
+ ":options",
+ "//src/main/java/com/google/devtools/build/lib:events",
+ "//src/main/java/com/google/devtools/build/lib:runtime",
+ ],
+)
+
+java_library(
+ name = "options",
+ srcs = ["SkylarkDebuggerOptions.java"],
+ deps = [
+ "//src/main/java/com/google/devtools/common/options",
+ ],
+)
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkdebug/module/SkylarkDebuggerModule.java b/src/main/java/com/google/devtools/build/lib/skylarkdebug/module/SkylarkDebuggerModule.java
new file mode 100644
index 0000000000..167d3c853b
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skylarkdebug/module/SkylarkDebuggerModule.java
@@ -0,0 +1,47 @@
+// Copyright 2018 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.lib.skylarkdebug.module;
+
+import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.runtime.BlazeModule;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
+
+/** Blaze module for setting up Skylark debugging. */
+public final class SkylarkDebuggerModule extends BlazeModule {
+ @Override
+ public void beforeCommand(CommandEnvironment env) {
+ // Conditionally enable debugging
+ SkylarkDebuggerOptions buildOptions = env.getOptions().getOptions(SkylarkDebuggerOptions.class);
+ boolean enabled = buildOptions != null && buildOptions.debugSkylark;
+ if (enabled) {
+ initializeDebugging(env.getReporter(), buildOptions.debugServerPort);
+ } else {
+ disableDebugging();
+ }
+ }
+
+ @Override
+ public void afterCommand() {
+ disableDebugging();
+ }
+
+ private static void initializeDebugging(Reporter reporter, int debugPort) {
+ // TODO(brendandouglas): implement a debug server
+ }
+
+ private static void disableDebugging() {
+ // TODO(brendandouglas): implement a debug server
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkdebug/module/SkylarkDebuggerOptions.java b/src/main/java/com/google/devtools/build/lib/skylarkdebug/module/SkylarkDebuggerOptions.java
new file mode 100644
index 0000000000..831a7770c6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skylarkdebug/module/SkylarkDebuggerOptions.java
@@ -0,0 +1,45 @@
+// Copyright 2018 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.lib.skylarkdebug.module;
+
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionDocumentationCategory;
+import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionMetadataTag;
+import com.google.devtools.common.options.OptionsBase;
+
+/** Configuration options for Skylark debugging. */
+public final class SkylarkDebuggerOptions extends OptionsBase {
+ @Option(
+ name = "experimental_skylark_debug",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {OptionEffectTag.EXECUTION},
+ metadataTags = {OptionMetadataTag.EXPERIMENTAL},
+ help =
+ "If true, Blaze will open the Skylark debug server at the start of the build "
+ + "invocation, and wait for a debugger to attach before running the build.")
+ public boolean debugSkylark;
+
+ @Option(
+ name = "experimental_debug_server_port",
+ defaultValue = "7300",
+ category = "server startup",
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {OptionEffectTag.EXECUTION},
+ metadataTags = {OptionMetadataTag.EXPERIMENTAL},
+ help = "The port on which the Skylark debug server will listen for connections.")
+ public int debugServerPort;
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java b/src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java
index 439e4c5f50..ca6307fa8a 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java
@@ -193,6 +193,18 @@ public class BuildFileAST extends ASTNode {
* @return true if no error occurred during execution.
*/
public boolean exec(Environment env, EventHandler eventHandler) throws InterruptedException {
+ try {
+ // Handles debugging BUILD file evaluation
+ // TODO(bazel-team): add support for debugging skylark file evaluation
+ return DebugServerUtils.runWithDebuggingIfEnabled(
+ env, () -> Thread.currentThread().getName(), () -> doExec(env, eventHandler));
+ } catch (EvalException e) {
+ // runWithDebuggingIfEnabled() shouldn't throw EvalException, since doExec() does not
+ throw new AssertionError("Unexpected EvalException", e);
+ }
+ }
+
+ private boolean doExec(Environment env, EventHandler eventHandler) throws InterruptedException {
boolean ok = true;
for (Statement stmt : statements) {
if (!execTopLevelStatement(stmt, env, eventHandler)) {
@@ -222,7 +234,7 @@ public class BuildFileAST extends ASTNode {
public boolean execTopLevelStatement(Statement stmt, Environment env,
EventHandler eventHandler) throws InterruptedException {
try {
- new Eval(env).exec(stmt);
+ Eval.fromEnvironment(env).exec(stmt);
return true;
} catch (EvalException e) {
// Do not report errors caused by a previous parsing error, as it has already been
@@ -367,7 +379,7 @@ public class BuildFileAST extends ASTNode {
*/
@Nullable public Object eval(Environment env) throws EvalException, InterruptedException {
Object last = null;
- Eval evaluator = new Eval(env);
+ Eval evaluator = Eval.fromEnvironment(env);
for (Statement statement : statements) {
if (statement instanceof ExpressionStatement) {
last = ((ExpressionStatement) statement).getExpression().eval(env);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DebugFrame.java b/src/main/java/com/google/devtools/build/lib/syntax/DebugFrame.java
new file mode 100644
index 0000000000..362739ca93
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DebugFrame.java
@@ -0,0 +1,58 @@
+// Copyright 2018 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.lib.syntax;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.events.Location;
+import javax.annotation.Nullable;
+
+/** The information about a single frame in a thread's stack trace relevant to the debugger. */
+@AutoValue
+public abstract class DebugFrame {
+ /** The source location where the frame is currently paused. */
+ @Nullable
+ public abstract Location location();
+
+ /** The name of the function that this frame represents. */
+ public abstract String functionName();
+
+ /**
+ * The local bindings associated with the current lexical frame. For the outer-most scope this
+ * will be empty.
+ */
+ public abstract ImmutableMap<String, Object> lexicalFrameBindings();
+
+ /** The global vars and builtins for this frame. May be shadowed by the lexical frame bindings. */
+ public abstract ImmutableMap<String, Object> globalBindings();
+
+ public static Builder builder() {
+ return new AutoValue_DebugFrame.Builder().setLexicalFrameBindings(ImmutableMap.of());
+ }
+
+ /** Builder class for {@link DebugFrame}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setLocation(@Nullable Location location);
+
+ public abstract Builder setFunctionName(String functionName);
+
+ public abstract Builder setLexicalFrameBindings(ImmutableMap<String, Object> bindings);
+
+ public abstract Builder setGlobalBindings(ImmutableMap<String, Object> bindings);
+
+ public abstract DebugFrame build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DebugServer.java b/src/main/java/com/google/devtools/build/lib/syntax/DebugServer.java
new file mode 100644
index 0000000000..f462550797
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DebugServer.java
@@ -0,0 +1,42 @@
+// Copyright 2018 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.lib.syntax;
+
+/** A debug server interface, called from core skylark code. */
+public interface DebugServer {
+
+ /**
+ * Tracks the execution of the given callable object in the debug server.
+ *
+ * @param env the Skylark execution environment
+ * @param threadName the descriptive name of the thread
+ * @param callable the callable object whose execution will be tracked
+ * @param <T> the result type of the callable
+ * @return the value returned by the callable
+ */
+ <T> T runWithDebugging(Environment env, String threadName, DebugCallable<T> callable)
+ throws EvalException, InterruptedException;
+
+ /** Represents an invocation that will be tracked as a thread by the Skylark debug server. */
+ interface DebugCallable<T> {
+
+ /**
+ * The invocation that will be tracked.
+ *
+ * @return the result
+ */
+ T call() throws EvalException, InterruptedException;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DebugServerUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/DebugServerUtils.java
new file mode 100644
index 0000000000..f653cd994f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DebugServerUtils.java
@@ -0,0 +1,64 @@
+// Copyright 2018 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.lib.syntax;
+
+import com.google.devtools.build.lib.syntax.DebugServer.DebugCallable;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/** A helper class for enabling/disabling skylark debugging. */
+public final class DebugServerUtils {
+
+ private DebugServerUtils() {}
+
+ private static final AtomicReference<DebugServer> instance = new AtomicReference<>();
+
+ /**
+ * Called at the start of a debuggable skylark session to enable debugging. The custom {@link
+ * Eval} supplier provided should intercept statement execution to check for breakpoints.
+ */
+ public static void initializeDebugServer(
+ DebugServer server, Function<Environment, Eval> evalOverride) {
+ instance.set(server);
+ Eval.setEvalSupplier(evalOverride);
+ }
+
+ /** Called at the end of a debuggable skylark session to disable debugging. */
+ public static void disableDebugging() {
+ instance.set(null);
+ Eval.removeCustomEval();
+ }
+
+ /**
+ * Tracks the execution of the given callable object in the debug server.
+ *
+ * <p>If the skylark debugger is not enabled, runs {@code callable} directly.
+ *
+ * @param env the Skylark execution environment
+ * @param threadName the descriptive name of the thread
+ * @param callable the callable object whose execution will be tracked
+ * @param <T> the result type of the callable
+ * @return the value returned by the callable
+ */
+ public static <T> T runWithDebuggingIfEnabled(
+ Environment env, Supplier<String> threadName, DebugCallable<T> callable)
+ throws EvalException, InterruptedException {
+ DebugServer server = instance.get();
+ return server != null
+ ? server.runWithDebugging(env, threadName.get(), callable)
+ : callable.call();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Debuggable.java b/src/main/java/com/google/devtools/build/lib/syntax/Debuggable.java
new file mode 100644
index 0000000000..4540f55dfb
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Debuggable.java
@@ -0,0 +1,80 @@
+// Copyright 2018 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.lib.syntax;
+
+import com.google.devtools.build.lib.events.Location;
+import java.util.Collection;
+import java.util.function.Predicate;
+import javax.annotation.Nullable;
+
+/** A context in which debugging can occur. Implemented by Skylark environments. */
+public interface Debuggable {
+
+ /** Evaluates a Skylark expression in the adapter's environment. */
+ Object evaluate(String expression) throws EvalException, InterruptedException;
+
+ /**
+ * Returns the stack frames corresponding of the context's current (paused) state.
+ *
+ * <p>For all stack frames except the innermost, location information is retrieved from the
+ * current context. The innermost frame's location must be supplied as {@code currentLocation} by
+ * the caller.
+ */
+ Collection<DebugFrame> listFrames(Location currentLocation);
+
+ /**
+ * Given a requested stepping behavior, returns a predicate over the context that tells the
+ * debugger when to pause.
+ *
+ * <p>The predicate will return true if we are at the next statement where execution should pause,
+ * and it will return false if we are not yet at that statement. No guarantee is made about the
+ * predicate's return value after we have reached the desired statement.
+ *
+ * <p>A null return value indicates that no further pausing should occur.
+ */
+ @Nullable
+ ReadyToPause stepControl(Stepping stepping);
+
+ /**
+ * When stepping, this determines whether or not the context has yet reached a state for which
+ * execution should be paused.
+ *
+ * <p>A single instance is only useful for advancing by one pause. A new instance may be required
+ * after that.
+ */
+ interface ReadyToPause extends Predicate<Environment> {}
+
+ /** Describes the stepping behavior that should occur when execution of a thread is continued. */
+ enum Stepping {
+ /** Continue execution without stepping. */
+ NONE,
+ /**
+ * If the thread is paused on a statement that contains a function call, step into that
+ * function. Otherwise, this is the same as OVER.
+ */
+ INTO,
+ /**
+ * Step over the current statement and any functions that it may call, stopping at the next
+ * statement in the same frame. If no more statements are available in the current frame, same
+ * as OUT.
+ */
+ OVER,
+ /**
+ * Continue execution until the current frame has been exited and then pause. If we are
+ * currently in the outer-most frame, same as NONE.
+ */
+ OUT,
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
index c536a688b3..6f84188b4b 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
@@ -16,6 +16,7 @@ package com.google.devtools.build.lib.syntax;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.cmdline.Label;
@@ -31,10 +32,12 @@ import com.google.devtools.build.lib.syntax.Mutability.MutabilityException;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.util.SpellChecker;
+import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -75,7 +78,7 @@ import javax.annotation.Nullable;
* that the words "dynamic" and "static" refer to the point of view of the source code, and here we
* have a dual point of view.
*/
-public final class Environment implements Freezable {
+public final class Environment implements Freezable, Debuggable {
/**
* A phase for enabling or disabling certain builtin functions
@@ -1150,6 +1153,91 @@ public final class Environment implements Freezable {
return vars;
}
+ private static final class EvalEventHandler implements EventHandler {
+ List<String> messages = new ArrayList<>();
+
+ @Override
+ public void handle(Event event) {
+ if (event.getKind() == EventKind.ERROR) {
+ messages.add(event.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public Object evaluate(String expression) throws EvalException, InterruptedException {
+ ParserInputSource inputSource =
+ ParserInputSource.create(expression, PathFragment.create("<debug eval>"));
+ EvalEventHandler eventHandler = new EvalEventHandler();
+ Expression expr = Parser.parseExpression(inputSource, eventHandler);
+ if (!eventHandler.messages.isEmpty()) {
+ throw new EvalException(expr.getLocation(), eventHandler.messages.get(0));
+ }
+ return expr.eval(this);
+ }
+
+ @Override
+ public ImmutableList<DebugFrame> listFrames(Location currentLocation) {
+ ImmutableList.Builder<DebugFrame> frameListBuilder = ImmutableList.builder();
+
+ Continuation currentContinuation = continuation;
+ Frame currentFrame = currentFrame();
+
+ // if there's a continuation then the current frame is a lexical frame
+ while (currentContinuation != null) {
+ frameListBuilder.add(
+ DebugFrame.builder()
+ .setLexicalFrameBindings(ImmutableMap.copyOf(currentFrame.getTransitiveBindings()))
+ .setGlobalBindings(ImmutableMap.copyOf(getGlobals().getTransitiveBindings()))
+ .setFunctionName(currentContinuation.function.getFullName())
+ .setLocation(currentLocation)
+ .build());
+
+ currentFrame = currentContinuation.lexicalFrame;
+ currentLocation = currentContinuation.caller.getLocation();
+ currentContinuation = currentContinuation.continuation;
+ }
+
+ frameListBuilder.add(
+ DebugFrame.builder()
+ .setGlobalBindings(ImmutableMap.copyOf(getGlobals().getTransitiveBindings()))
+ .setFunctionName("<top level>")
+ .setLocation(currentLocation)
+ .build());
+
+ return frameListBuilder.build();
+ }
+
+ @Override
+ @Nullable
+ public ReadyToPause stepControl(Stepping stepping) {
+ final Continuation pausedContinuation = continuation;
+
+ switch (stepping) {
+ case NONE:
+ return null;
+ case INTO:
+ // pause at the very next statement
+ return env -> true;
+ case OVER:
+ return env -> isAt(env, pausedContinuation) || isOutside(env, pausedContinuation);
+ case OUT:
+ // if we're at the outer-most frame, same as NONE
+ return pausedContinuation == null ? null : env -> isOutside(env, pausedContinuation);
+ }
+ throw new IllegalArgumentException("Unsupported stepping type: " + stepping);
+ }
+
+ /** Returns true if {@code env} is in a parent frame of {@code pausedContinuation}. */
+ private static boolean isOutside(Environment env, @Nullable Continuation pausedContinuation) {
+ return pausedContinuation != null && env.continuation == pausedContinuation.continuation;
+ }
+
+ /** Returns true if {@code env} is at the same frame as {@code pausedContinuation. */
+ private static boolean isAt(Environment env, @Nullable Continuation pausedContinuation) {
+ return env.continuation == pausedContinuation;
+ }
+
@Override
public int hashCode() {
throw new UnsupportedOperationException(); // avoid nondeterminism
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Eval.java b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
index 98130ae3ff..56c98a7140 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
@@ -18,13 +18,14 @@ import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.function.Function;
/**
* Evaluation code for the Skylark AST. At the moment, it can execute only statements (and defers to
* Expression.eval for evaluating expressions).
*/
public class Eval {
- private final Environment env;
+ protected final Environment env;
/** An exception that signals changes in the control flow (e.g. break or continue) */
private static class FlowException extends EvalException {
@@ -38,11 +39,31 @@ public class Eval {
}
}
+ public static Eval fromEnvironment(Environment env) {
+ return evalSupplier.apply(env);
+ }
+
+ public static void setEvalSupplier(Function<Environment, Eval> evalSupplier) {
+ Eval.evalSupplier = evalSupplier;
+ }
+
+ /** Reset Eval supplier to the default. */
+ public static void removeCustomEval() {
+ evalSupplier = Eval::new;
+ }
+
+ // TODO(bazel-team): remove this static state in favor of storing Eval instances in Environment
+ private static Function<Environment, Eval> evalSupplier = Eval::new;
+
private static final FlowException breakException = new FlowException("FlowException - break");
private static final FlowException continueException =
new FlowException("FlowException - continue");
- public Eval(Environment env) {
+ /**
+ * This constructor should never be called directly. Call {@link #fromEnvironment(Environment)}
+ * instead.
+ */
+ protected Eval(Environment env) {
this.env = env;
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
index eda6c3d0b8..4db184eceb 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
@@ -76,7 +76,7 @@ public class UserDefinedFunction extends BaseFunction {
env.update(name, arguments[i++]);
}
- Eval eval = new Eval(env);
+ Eval eval = Eval.fromEnvironment(env);
try {
for (Statement stmt : statements) {
if (stmt instanceof ReturnStatement) {