aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib
diff options
context:
space:
mode:
authorGravatar John Cater <jcater@google.com>2018-06-05 11:09:01 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-06-05 11:10:48 -0700
commitee45c6610ae213c12d57bf9e8e5ff674d7a9d7fc (patch)
treea564f798205c25ec3987b00fdc38a19e4bbfdd65 /src/main/java/com/google/devtools/build/lib
parent41bf88da4e4bf7d0ecfba2d935736cae9f93cf19 (diff)
Allow rules and targets to specify extra execution platform constraints.
RuleClass.Builder now allows authors to specify whether a rule's targets can add additional constraints on the execution platform, and to declare additional constraints for all targets of that rule. Targets which support this now have an attribute, "exec_compatible_with", which supports specifying additional constraints that the execution platform used must match. This attribute is non-configurable. It will only affect execution platforms used during toolchain resolution. Part of #5217. Change-Id: Id2400dbf869a00aa2be3e3d2f085c2850cd6dc00 Closes #5227. Change-Id: If7d55f08f7f44bc7d7f6dfec86a3e6bcd68574b9 PiperOrigin-RevId: 199326255
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib')
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/PlatformSemantics.java1
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/RuleClass.java110
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java1
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java26
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/ToolchainUtil.java134
6 files changed, 273 insertions, 5 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PlatformSemantics.java b/src/main/java/com/google/devtools/build/lib/analysis/PlatformSemantics.java
index bd3e89f97b..65c1047187 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/PlatformSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/PlatformSemantics.java
@@ -24,6 +24,7 @@ import com.google.devtools.build.lib.packages.RuleClass;
public class PlatformSemantics {
public static final String TOOLCHAINS_ATTR = "$toolchains";
+ public static final String EXEC_COMPATIBLE_WITH_ATTR = "exec_compatible_with";
public static RuleClass.Builder platformAttributes(RuleClass.Builder builder) {
return builder
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
index d83246871d..330278f1aa 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
@@ -202,6 +202,23 @@ public class RuleClass {
}
/**
+ * Describes in which way a rule implementation allows additional execution platform constraints.
+ */
+ public enum ExecutionPlatformConstraintsAllowed {
+ /**
+ * Allows additional execution platform constraints to be added in the rule definition, which
+ * apply to all targets of that rule.
+ */
+ PER_RULE,
+ /**
+ * Users are allowed to specify additional execution platform constraints for each target, using
+ * the 'exec_compatible_with' attribute. This also allows setting constraints in the rule
+ * definition, like PER_RULE.
+ */
+ PER_TARGET;
+ }
+
+ /**
* For Bazel's constraint system: the attribute that declares the set of environments a rule
* supports, overriding the defaults for their respective groups.
*/
@@ -615,6 +632,9 @@ public class RuleClass {
private final Map<String, Attribute> attributes = new LinkedHashMap<>();
private final Set<Label> requiredToolchains = new HashSet<>();
private boolean supportsPlatforms = true;
+ private ExecutionPlatformConstraintsAllowed executionPlatformConstraintsAllowed =
+ ExecutionPlatformConstraintsAllowed.PER_RULE;
+ private Set<Label> executionPlatformConstraints = new HashSet<>();
private OutputFile.Kind outputFileKind = OutputFile.Kind.FILE;
/**
@@ -648,6 +668,8 @@ public class RuleClass {
addRequiredToolchains(parent.getRequiredToolchains());
supportsPlatforms = parent.supportsPlatforms;
+ // executionPlatformConstraintsAllowed is not inherited and takes the default.
+ addExecutionPlatformConstraints(parent.getExecutionPlatformConstraints());
for (Attribute attribute : parent.getAttributes()) {
String attrName = attribute.getName();
@@ -656,6 +678,13 @@ public class RuleClass {
"Attribute %s is inherited multiple times in %s ruleclass",
attrName,
name);
+ if (attrName.equals("exec_compatible_with")
+ && parent.executionPlatformConstraintsAllowed
+ == ExecutionPlatformConstraintsAllowed.PER_TARGET) {
+ // This attribute should not be inherited because executionPlatformConstraintsAllowed is
+ // not inherited.
+ continue;
+ }
attributes.put(attrName, attribute);
}
@@ -708,6 +737,19 @@ public class RuleClass {
if (type == RuleClassType.PLACEHOLDER) {
Preconditions.checkNotNull(ruleDefinitionEnvironmentHashCode, this.name);
}
+ if (executionPlatformConstraintsAllowed == ExecutionPlatformConstraintsAllowed.PER_TARGET) {
+ // Only rules that allow per target execution constraints need this attribute.
+ Preconditions.checkState(
+ !this.attributes.containsKey("exec_compatible_with"),
+ "Rule should not already define the attribute \"exec_compatible_with\""
+ + " if executionPlatformConstraintsAllowed is set to PER_TARGET");
+ this.add(
+ attr("exec_compatible_with", BuildType.LABEL_LIST)
+ .allowedFileTypes()
+ .nonconfigurable("Used in toolchain resolution")
+ .value(ImmutableList.of()));
+ }
+
return new RuleClass(
name,
key,
@@ -735,6 +777,8 @@ public class RuleClass {
supportsConstraintChecking,
requiredToolchains,
supportsPlatforms,
+ executionPlatformConstraintsAllowed,
+ executionPlatformConstraints,
outputFileKind,
attributes.values());
}
@@ -912,8 +956,8 @@ public class RuleClass {
/**
* Applies the given transition factory to all incoming edges for this rule class.
*
- * <p>Unlike{@link #cfg(PatchTransition)}, the factory can examine the rule when
- * deciding what transition to use.
+ * <p>Unlike {@link #cfg(PatchTransition)}, the factory can examine the rule when deciding what
+ * transition to use.
*/
public Builder cfg(RuleTransitionFactory transitionFactory) {
Preconditions.checkState(type != RuleClassType.ABSTRACT,
@@ -1161,22 +1205,66 @@ public class RuleClass {
return this;
}
+ /**
+ * Causes rules of this type to require the specified toolchains be available via toolchain
+ * resolution when a target is configured.
+ */
public Builder addRequiredToolchains(Iterable<Label> toolchainLabels) {
Iterables.addAll(this.requiredToolchains, toolchainLabels);
return this;
}
+ /**
+ * Causes rules of this type to require the specified toolchains be available via toolchain
+ * resolution when a target is configured.
+ */
public Builder addRequiredToolchains(Label... toolchainLabels) {
- Iterables.addAll(this.requiredToolchains, Lists.newArrayList(toolchainLabels));
- return this;
+ return this.addRequiredToolchains(Lists.newArrayList(toolchainLabels));
}
+ /**
+ * Rules that support platforms can use toolchains and execution platforms. Rules that are part
+ * of configuring toolchains and platforms should set this to {@code false}.
+ */
public Builder supportsPlatforms(boolean flag) {
this.supportsPlatforms = flag;
return this;
}
/**
+ * Specifies whether targets of this rule can add additional constraints on the execution
+ * platform selected. If this is {@link ExecutionPlatformConstraintsAllowed#PER_TARGET}, there
+ * will be an attribute named {@code exec_compatible_with} that can be used to add these
+ * constraints.
+ *
+ * <p>Please note that this value is not inherited by child rules, and must be re-set on them if
+ * the same behavior is required.
+ */
+ public Builder executionPlatformConstraintsAllowed(ExecutionPlatformConstraintsAllowed value) {
+ this.executionPlatformConstraintsAllowed = value;
+ return this;
+ }
+
+ /**
+ * Adds additional execution platform constraints that apply for all targets from this rule.
+ *
+ * <p>Please note that this value is inherited by child rules.
+ */
+ public Builder addExecutionPlatformConstraints(Label... constraints) {
+ return this.addExecutionPlatformConstraints(Lists.newArrayList(constraints));
+ }
+
+ /**
+ * Adds additional execution platform constraints that apply for all targets from this rule.
+ *
+ * <p>Please note that this value is inherited by child rules.
+ */
+ public Builder addExecutionPlatformConstraints(Iterable<Label> constraints) {
+ Iterables.addAll(this.executionPlatformConstraints, constraints);
+ return this;
+ }
+
+ /**
* Returns an Attribute.Builder object which contains a replica of the
* same attribute in the parent rule if exists.
*
@@ -1294,6 +1382,8 @@ public class RuleClass {
private final ImmutableSet<Label> requiredToolchains;
private final boolean supportsPlatforms;
+ private final ExecutionPlatformConstraintsAllowed executionPlatformConstraintsAllowed;
+ private final ImmutableSet<Label> executionPlatformConstraints;
/**
* Constructs an instance of RuleClass whose name is 'name', attributes are 'attributes'. The
@@ -1343,6 +1433,8 @@ public class RuleClass {
boolean supportsConstraintChecking,
Set<Label> requiredToolchains,
boolean supportsPlatforms,
+ ExecutionPlatformConstraintsAllowed executionPlatformConstraintsAllowed,
+ Set<Label> executionPlatformConstraints,
OutputFile.Kind outputFileKind,
Collection<Attribute> attributes) {
this.name = name;
@@ -1375,6 +1467,8 @@ public class RuleClass {
this.supportsConstraintChecking = supportsConstraintChecking;
this.requiredToolchains = ImmutableSet.copyOf(requiredToolchains);
this.supportsPlatforms = supportsPlatforms;
+ this.executionPlatformConstraintsAllowed = executionPlatformConstraintsAllowed;
+ this.executionPlatformConstraints = ImmutableSet.copyOf(executionPlatformConstraints);
// Create the index and collect non-configurable attributes.
int index = 0;
@@ -2191,6 +2285,14 @@ public class RuleClass {
return supportsPlatforms;
}
+ public ExecutionPlatformConstraintsAllowed executionPlatformConstraintsAllowed() {
+ return executionPlatformConstraintsAllowed;
+ }
+
+ public ImmutableSet<Label> getExecutionPlatformConstraints() {
+ return executionPlatformConstraints;
+ }
+
@Nullable
public OutputFile.Kind getOutputFileKind() {
return outputFileKind;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
index 1e39c7693c..79498d734b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
@@ -419,6 +419,7 @@ public final class AspectFunction implements SkyFunction {
aspect.getDescriptor().getDescription(),
associatedConfiguredTargetAndData.getTarget().toString()),
requiredToolchains,
+ /* execConstraintLabels= */ ImmutableSet.of(),
key.getAspectConfigurationKey());
} catch (ToolchainContextException e) {
// TODO(katre): better error handling
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
index 8363ff7144..f1df1166bc 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
@@ -31,6 +31,7 @@ import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.ConfiguredTargetFactory;
import com.google.devtools.build.lib.analysis.Dependency;
import com.google.devtools.build.lib.analysis.DependencyResolver.InconsistentAspectOrderException;
+import com.google.devtools.build.lib.analysis.PlatformSemantics;
import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
import com.google.devtools.build.lib.analysis.ToolchainContext;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
@@ -57,9 +58,11 @@ import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.NoSuchTargetException;
import com.google.devtools.build.lib.packages.NoSuchThingException;
+import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.RawAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleClass.ExecutionPlatformConstraintsAllowed;
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.packages.RuleTransitionFactory;
import com.google.devtools.build.lib.packages.Target;
@@ -272,11 +275,15 @@ public final class ConfiguredTargetFunction implements SkyFunction {
if (rule.getRuleClassObject().supportsPlatforms()) {
ImmutableSet<Label> requiredToolchains =
rule.getRuleClassObject().getRequiredToolchains();
+
+ // Collect local (target, rule) constraints for filtering out execution platforms.
+ ImmutableSet<Label> execConstraintLabels = getExecutionPlatformConstraints(rule);
toolchainContext =
ToolchainUtil.createToolchainContext(
env,
rule.toString(),
requiredToolchains,
+ execConstraintLabels,
configuredTargetKey.getConfigurationKey());
if (env.valuesMissing()) {
return null;
@@ -384,6 +391,25 @@ public final class ConfiguredTargetFunction implements SkyFunction {
}
/**
+ * Returns the target-specific execution platform constraints, based on the rule definition and
+ * any constraints added by the target.
+ */
+ private static ImmutableSet<Label> getExecutionPlatformConstraints(Rule rule) {
+ NonconfigurableAttributeMapper mapper = NonconfigurableAttributeMapper.of(rule);
+ ImmutableSet.Builder<Label> execConstraintLabels = new ImmutableSet.Builder<>();
+
+ execConstraintLabels.addAll(rule.getRuleClassObject().getExecutionPlatformConstraints());
+
+ if (rule.getRuleClassObject().executionPlatformConstraintsAllowed()
+ == ExecutionPlatformConstraintsAllowed.PER_TARGET) {
+ execConstraintLabels.addAll(
+ mapper.get(PlatformSemantics.EXEC_COMPATIBLE_WITH_ATTR, BuildType.LABEL_LIST));
+ }
+
+ return execConstraintLabels.build();
+ }
+
+ /**
* Computes the direct dependencies of a node in the configured target graph (a configured target
* or an aspects).
*
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index 906924fa9f..6cf2bd5d77 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -968,7 +968,11 @@ public abstract class SkyframeExecutor implements WalkableGraphFactory {
SkyFunctionEnvironmentForTesting env =
new SkyFunctionEnvironmentForTesting(buildDriver, eventHandler, this);
return ToolchainUtil.createToolchainContext(
- env, "", requiredToolchains, config == null ? null : BuildConfigurationValue.key(config));
+ env,
+ "",
+ requiredToolchains,
+ /* execConstraintLabels= */ ImmutableSet.of(),
+ config == null ? null : BuildConfigurationValue.key(config));
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainUtil.java b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainUtil.java
index 40d6701c33..39149f54e4 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainUtil.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.lib.skyframe;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.stream.Collectors.joining;
import com.google.auto.value.AutoValue;
@@ -28,6 +29,7 @@ import com.google.devtools.build.lib.analysis.PlatformConfiguration;
import com.google.devtools.build.lib.analysis.PlatformOptions;
import com.google.devtools.build.lib.analysis.ToolchainContext;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.platform.ConstraintValueInfo;
import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
import com.google.devtools.build.lib.analysis.platform.PlatformProviderUtils;
import com.google.devtools.build.lib.cmdline.Label;
@@ -52,17 +54,26 @@ import javax.annotation.Nullable;
/**
* Common code to create a {@link ToolchainContext} given a set of required toolchain type labels.
*/
+// TODO(katre): Refactor this and ToolchainContext into something nicer to work with and with
+// fewer static methods everywhere.
public class ToolchainUtil {
/**
* Returns a new {@link ToolchainContext}, with the correct toolchain labels based on the results
* of the {@link ToolchainResolutionFunction}.
+ *
+ * @param env the Skyframe environment to use to acquire dependencies
+ * @param targetDescription a description of the target use, for error and debug message context
+ * @param requiredToolchains the required toolchain types that must be resolved
+ * @param execConstraintLabels extra constraints on the execution platform to select
+ * @param configurationKey the build configuration to use for resolving other targets
*/
@Nullable
static ToolchainContext createToolchainContext(
Environment env,
String targetDescription,
Set<Label> requiredToolchains,
+ Set<Label> execConstraintLabels,
@Nullable BuildConfigurationValue.Key configurationKey)
throws ToolchainContextException, InterruptedException {
@@ -91,6 +102,11 @@ public class ToolchainUtil {
ConfiguredTargetKey hostPlatformKey = ConfiguredTargetKey.of(hostPlatformLabel, configuration);
ConfiguredTargetKey targetPlatformKey =
ConfiguredTargetKey.of(targetPlatformLabel, configuration);
+ ImmutableList<ConfiguredTargetKey> execConstraintKeys =
+ execConstraintLabels
+ .stream()
+ .map(label -> ConfiguredTargetKey.of(label, configuration))
+ .collect(toImmutableList());
// Load the host and target platforms early, to check for errors.
getPlatformInfo(ImmutableList.of(hostPlatformKey, targetPlatformKey), env);
@@ -108,6 +124,14 @@ public class ToolchainUtil {
.addAll(registeredExecutionPlatforms.registeredExecutionPlatformKeys())
.add(hostPlatformKey)
.build();
+
+ // Filter out execution platforms that don't satisfy the extra constraints.
+ availableExecutionPlatformKeys =
+ filterPlatforms(availableExecutionPlatformKeys, execConstraintKeys, env);
+ if (availableExecutionPlatformKeys == null) {
+ return null;
+ }
+
Optional<ResolvedToolchains> resolvedToolchains =
resolveToolchainLabels(
env,
@@ -421,6 +445,94 @@ public class ToolchainUtil {
return labels.build();
}
+ @Nullable
+ private static ImmutableList<ConfiguredTargetKey> filterPlatforms(
+ ImmutableList<ConfiguredTargetKey> platformKeys,
+ ImmutableList<ConfiguredTargetKey> constraintKeys,
+ Environment env)
+ throws ToolchainContextException, InterruptedException {
+
+ // Short circuit if not needed.
+ if (constraintKeys.isEmpty()) {
+ return platformKeys;
+ }
+
+ Map<ConfiguredTargetKey, PlatformInfo> platformInfoMap = getPlatformInfo(platformKeys, env);
+ if (platformInfoMap == null) {
+ return null;
+ }
+ List<ConstraintValueInfo> constraints = getConstraintValueInfo(constraintKeys, env);
+ if (constraints == null) {
+ return null;
+ }
+
+ return platformKeys
+ .stream()
+ .filter(key -> filterPlatform(platformInfoMap.get(key), constraints))
+ .collect(toImmutableList());
+ }
+
+ @Nullable
+ private static List<ConstraintValueInfo> getConstraintValueInfo(
+ ImmutableList<ConfiguredTargetKey> constraintKeys, Environment env)
+ throws InterruptedException, ToolchainContextException {
+
+ Map<SkyKey, ValueOrException<ConfiguredValueCreationException>> values =
+ env.getValuesOrThrow(constraintKeys, ConfiguredValueCreationException.class);
+ boolean valuesMissing = env.valuesMissing();
+ List<ConstraintValueInfo> constraintValues = valuesMissing ? null : new ArrayList<>();
+ try {
+ for (ConfiguredTargetKey key : constraintKeys) {
+ ConstraintValueInfo constraintValueInfo = findConstraintValueInfo(values.get(key));
+ if (!valuesMissing && constraintValueInfo != null) {
+ constraintValues.add(constraintValueInfo);
+ }
+ }
+ } catch (ConfiguredValueCreationException e) {
+ throw new ToolchainContextException(e);
+ }
+ if (valuesMissing) {
+ return null;
+ }
+ return constraintValues;
+ }
+
+ @Nullable
+ private static ConstraintValueInfo findConstraintValueInfo(
+ ValueOrException<ConfiguredValueCreationException> valueOrException)
+ throws ConfiguredValueCreationException, ToolchainContextException {
+
+ ConfiguredTargetValue configuredTargetValue = (ConfiguredTargetValue) valueOrException.get();
+ if (configuredTargetValue == null) {
+ return null;
+ }
+
+ ConfiguredTarget configuredTarget = configuredTargetValue.getConfiguredTarget();
+ ConstraintValueInfo constraintValueInfo =
+ PlatformProviderUtils.constraintValue(configuredTarget);
+ if (constraintValueInfo == null) {
+ throw new ToolchainContextException(
+ new InvalidConstraintValueException(configuredTarget.getLabel()));
+ }
+
+ return constraintValueInfo;
+ }
+
+ private static boolean filterPlatform(
+ PlatformInfo platformInfo, List<ConstraintValueInfo> constraints) {
+ for (ConstraintValueInfo filterConstraint : constraints) {
+ ConstraintValueInfo platformInfoConstraint =
+ platformInfo.getConstraint(filterConstraint.constraint());
+ if (platformInfoConstraint == null || !platformInfoConstraint.equals(filterConstraint)) {
+ // The value for this setting is not present in the platform, or doesn't match the expected
+ // value.
+ return false;
+ }
+ }
+
+ return true;
+ }
+
/**
* Exception used when an error occurs in {@link #expandTargetPatterns(Environment, List,
* FilteringPolicy)}.
@@ -460,6 +572,24 @@ public class ToolchainUtil {
}
}
+ /** Exception used when a constraint value label is not a valid constraint value. */
+ static final class InvalidConstraintValueException extends Exception {
+ InvalidConstraintValueException(Label label) {
+ super(formatError(label));
+ }
+
+ InvalidConstraintValueException(Label label, ConfiguredValueCreationException e) {
+ super(formatError(label), e);
+ }
+
+ private static String formatError(Label label) {
+ return String.format(
+ "Target %s was referenced as a constraint_value,"
+ + " but does not provide ConstraintValueInfo",
+ label);
+ }
+ }
+
/** Exception used when a toolchain type is required but no matching toolchain is found. */
public static final class UnresolvedToolchainsException extends Exception {
private final ImmutableList<Label> missingToolchainTypes;
@@ -483,6 +613,10 @@ public class ToolchainUtil {
super(e);
}
+ public ToolchainContextException(InvalidConstraintValueException e) {
+ super(e);
+ }
+
public ToolchainContextException(UnresolvedToolchainsException e) {
super(e);
}