aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar tomlu <tomlu@google.com>2017-08-23 00:00:40 +0200
committerGravatar Damien Martin-Guillerez <dmarting@google.com>2017-08-23 13:31:28 +0200
commitdc506f66eb02e883f95bbb295288b3663bc909ff (patch)
treeb4f6768bc807901cb496fbecd7f58d08087f1ada
parent5c1ab0815519000f35cb88e3aa07dc8fa4722de3 (diff)
Use CustomCommandLine directly instead of via SpawnAction.Builder.
This change forces use of CustomCommandLine.Builder, which has a richer interface for constructing memory-efficient command lines. It will also permit surveying the code base for inefficient patterns using an IDE. This change was done by hand and split using Rosie to assist with rollbacks in case of bugs. Reviewers, please pay particular attention to: * Each all to addInputArgument/addOutputArgument should come with a corresponding matching pair to SpawnAction.Builder#addInput and CustomCommandLine.Builder#addExecPath (eg.). * The commandLine must be set on the SpawnAction using SpawnAction.Builder#setCommandLine. Note that most calls to addPrefixed("arg=", val) should be more idiomatically expressed as add("arg", val), but this involves changing tests and making sure that the command line tools can accept the format. PiperOrigin-RevId: 166119282
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/java/ProguardHelper.java320
1 files changed, 170 insertions, 150 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/ProguardHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/ProguardHelper.java
index 8886997892..c046985658 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/ProguardHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/ProguardHelper.java
@@ -27,9 +27,9 @@ import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
-import com.google.devtools.build.lib.analysis.actions.SpawnAction.Builder;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
@@ -40,9 +40,7 @@ import com.google.devtools.build.lib.syntax.Type;
import java.util.Map;
import javax.annotation.Nullable;
-/**
- * Common code for proguarding Java binaries.
- */
+/** Common code for proguarding Java binaries. */
public abstract class ProguardHelper {
/**
@@ -50,9 +48,7 @@ public abstract class ProguardHelper {
*/
public static final String PROGUARD_SPECS = "proguard_specs";
- /**
- * A class collecting Proguard output artifacts.
- */
+ /** A class collecting Proguard output artifacts. */
@Immutable
public static final class ProguardOutput {
private final Artifact outputJar;
@@ -63,13 +59,14 @@ public abstract class ProguardHelper {
@Nullable private final Artifact constantStringObfuscatedMapping;
private final Artifact config;
- public ProguardOutput(Artifact outputJar,
- @Nullable Artifact mapping,
- @Nullable Artifact protoMapping,
- @Nullable Artifact seeds,
- @Nullable Artifact usage,
- @Nullable Artifact constantStringObfuscatedMapping,
- Artifact config) {
+ public ProguardOutput(
+ Artifact outputJar,
+ @Nullable Artifact mapping,
+ @Nullable Artifact protoMapping,
+ @Nullable Artifact seeds,
+ @Nullable Artifact usage,
+ @Nullable Artifact constantStringObfuscatedMapping,
+ Artifact config) {
this.outputJar = checkNotNull(outputJar);
this.mapping = mapping;
this.protoMapping = protoMapping;
@@ -117,8 +114,10 @@ public abstract class ProguardHelper {
addAllToSet(filesBuilder, null);
}
- /** Adds the output artifacts to the given set builder. If the progaurd map was updated
- * then add the updated map instead of the original proguard output map */
+ /**
+ * Adds the output artifacts to the given set builder. If the progaurd map was updated then add
+ * the updated map instead of the original proguard output map
+ */
public void addAllToSet(NestedSetBuilder<Artifact> filesBuilder, Artifact finalProguardMap) {
filesBuilder.add(outputJar);
if (protoMapping != null) {
@@ -213,7 +212,7 @@ public abstract class ProguardHelper {
}
/**
- * Returns the Proguard binary to invoke when using {@link #applyProguardIfRequested}. Returning
+ * Returns the Proguard binary to invoke when using {@link #applyProguardIfRequested}. Returning
* {@code null} from this method will generate an error in that method.
*
* @return Proguard binary or {@code null} if none is available
@@ -223,7 +222,7 @@ public abstract class ProguardHelper {
/**
* Returns rule-specific proguard specs not captured by {@link #PROGUARD_SPECS} attributes when
- * using {@link #applyProguardIfRequested}. Typically these are generated artifacts such as specs
+ * using {@link #applyProguardIfRequested}. Typically these are generated artifacts such as specs
* generated for android resources. This method is only called if Proguard will definitely used,
* so it's ok to generate files here.
*/
@@ -232,18 +231,18 @@ public abstract class ProguardHelper {
/**
* Retrieves the full set of proguard specs that should be applied to this binary, including the
- * specs passed in, if Proguard should run on the given rule. {@link #createProguardAction}
- * relies on this method returning an empty list if the given rule doesn't declare specs in
+ * specs passed in, if Proguard should run on the given rule. {@link #createProguardAction} relies
+ * on this method returning an empty list if the given rule doesn't declare specs in
* --java_optimization_mode=legacy.
*
* <p>If Proguard shouldn't be applied, or the legacy link mode is used and there are no
* proguard_specs on this rule, an empty list will be returned, regardless of any given specs or
- * specs from dependencies.
- * {@link com.google.devtools.build.lib.rules.android.AndroidBinary#createAndroidBinary} relies on
- * that behavior.
+ * specs from dependencies. {@link
+ * com.google.devtools.build.lib.rules.android.AndroidBinary#createAndroidBinary} relies on that
+ * behavior.
*/
- public static ImmutableList<Artifact> collectTransitiveProguardSpecs(RuleContext ruleContext,
- Iterable<Artifact> specsToInclude) {
+ public static ImmutableList<Artifact> collectTransitiveProguardSpecs(
+ RuleContext ruleContext, Iterable<Artifact> specsToInclude) {
JavaOptimizationMode optMode = getJavaOptimizationMode(ruleContext);
if (optMode == JavaOptimizationMode.NOOP) {
return ImmutableList.of();
@@ -263,9 +262,8 @@ public abstract class ProguardHelper {
ImmutableSortedSet.orderedBy(Artifact.EXEC_PATH_COMPARATOR)
.addAll(proguardSpecs)
.addAll(specsToInclude)
- .addAll(ruleContext
- .getPrerequisiteArtifacts(":extra_proguard_specs", Mode.TARGET)
- .list());
+ .addAll(
+ ruleContext.getPrerequisiteArtifacts(":extra_proguard_specs", Mode.TARGET).list());
for (ProguardSpecProvider dep :
ruleContext.getPrerequisites("deps", Mode.TARGET, ProguardSpecProvider.class)) {
builder.addAll(dep.getTransitiveProguardSpecs());
@@ -288,8 +286,8 @@ public abstract class ProguardHelper {
}
/**
- * Creates a proguard spec that tells proguard to keep the binary's entry point, ie., the
- * {@code main()} method to be invoked.
+ * Creates a proguard spec that tells proguard to keep the binary's entry point, ie., the {@code
+ * main()} method to be invoked.
*/
protected static Artifact generateSpecForJavaBinary(
RuleContext ruleContext, String mainClassName) {
@@ -305,17 +303,15 @@ public abstract class ProguardHelper {
return result;
}
- /**
- * @return true if proguard_generate_mapping is specified.
- */
+ /** @return true if proguard_generate_mapping is specified. */
public static final boolean genProguardMapping(AttributeMap rule) {
- return rule.has("proguard_generate_mapping", Type.BOOLEAN)
- && rule.get("proguard_generate_mapping", Type.BOOLEAN);
+ return rule.has("proguard_generate_mapping", Type.BOOLEAN)
+ && rule.get("proguard_generate_mapping", Type.BOOLEAN);
}
public static final boolean genObfuscatedConstantStringMap(AttributeMap rule) {
return rule.has("proguard_generate_obfuscated_constant_string_mapping", Type.BOOLEAN)
- && rule.get("proguard_generate_obfuscated_constant_string_mapping", Type.BOOLEAN);
+ && rule.get("proguard_generate_obfuscated_constant_string_mapping", Type.BOOLEAN);
}
public static ProguardOutput getProguardOutputs(
@@ -357,9 +353,9 @@ public abstract class ProguardHelper {
/**
* Creates an action to run Proguard over the given {@code programJar} with various other given
- * inputs to produce {@code proguardOutputJar}. If requested explicitly, or implicitly with
+ * inputs to produce {@code proguardOutputJar}. If requested explicitly, or implicitly with
* --java_optimization_mode, the action also produces a mapping file (which shows what methods and
- * classes in the output Jar correspond to which methods and classes in the input). The "pair"
+ * classes in the output Jar correspond to which methods and classes in the input). The "pair"
* returned by this method indicates whether a mapping is being produced.
*
* <p>See the Proguard manual for the meaning of the various artifacts in play.
@@ -369,10 +365,11 @@ public abstract class ProguardHelper {
* @param proguardMapping optional mapping file for Proguard to apply
* @param libraryJars any other Jar files that the {@code programJar} will run against
* @param optimizationPasses if not null specifies to break proguard up into multiple passes with
- * the given number of optimization passes.
+ * the given number of optimization passes.
* @param proguardOutputMap mapping generated by Proguard if requested. could be null.
*/
- public static ProguardOutput createOptimizationActions(RuleContext ruleContext,
+ public static ProguardOutput createOptimizationActions(
+ RuleContext ruleContext,
FilesToRunProvider proguard,
Artifact programJar,
ImmutableList<Artifact> proguardSpecs,
@@ -383,21 +380,26 @@ public abstract class ProguardHelper {
Artifact proguardOutputJar,
JavaSemantics semantics,
@Nullable Integer optimizationPasses,
- @Nullable Artifact proguardOutputMap) throws InterruptedException {
+ @Nullable Artifact proguardOutputMap)
+ throws InterruptedException {
JavaOptimizationMode optMode = getJavaOptimizationMode(ruleContext);
Preconditions.checkArgument(optMode != JavaOptimizationMode.NOOP);
Preconditions.checkArgument(optMode != JavaOptimizationMode.LEGACY || !proguardSpecs.isEmpty());
ProguardOutput output =
- getProguardOutputs(proguardOutputJar, proguardSeeds, proguardUsage, ruleContext, semantics,
+ getProguardOutputs(
+ proguardOutputJar,
+ proguardSeeds,
+ proguardUsage,
+ ruleContext,
+ semantics,
proguardOutputMap);
-
if (Iterables.size(libraryJars) > 1) {
- JavaTargetAttributes attributes = new JavaTargetAttributes.Builder(semantics)
- .build();
- Artifact combinedLibraryJar = getProguardTempArtifact(ruleContext,
- optMode.name().toLowerCase(), "combined_library_jars.jar");
+ JavaTargetAttributes attributes = new JavaTargetAttributes.Builder(semantics).build();
+ Artifact combinedLibraryJar =
+ getProguardTempArtifact(
+ ruleContext, optMode.name().toLowerCase(), "combined_library_jars.jar");
new DeployArchiveBuilder(semantics, ruleContext)
.setOutputJar(combinedLibraryJar)
.setAttributes(attributes)
@@ -408,7 +410,11 @@ public abstract class ProguardHelper {
if (optimizationPasses == null) {
// Run proguard as a single step.
- SpawnAction.Builder builder = defaultAction(
+ SpawnAction.Builder proguardAction = new SpawnAction.Builder();
+ CustomCommandLine.Builder commandLine = CustomCommandLine.builder();
+ defaultAction(
+ proguardAction,
+ commandLine,
proguard,
programJar,
proguardSpecs,
@@ -420,34 +426,40 @@ public abstract class ProguardHelper {
output.getSeeds(),
output.getUsage(),
output.getConstantStringObfuscatedMapping(),
- output.getConfig())
+ output.getConfig());
+ proguardAction
.setProgressMessage("Trimming binary with Proguard")
.addOutput(proguardOutputJar);
-
- ruleContext.registerAction(builder.build(ruleContext));
+ proguardAction.setCommandLine(commandLine.build());
+ ruleContext.registerAction(proguardAction.build(ruleContext));
} else {
// Optimization passes have been specified, so run proguard in multiple phases.
- Artifact lastStageOutput = getProguardTempArtifact(
- ruleContext, optMode.name().toLowerCase(), "proguard_preoptimization.jar");
- ruleContext.registerAction(
- defaultAction(
- proguard,
- programJar,
- proguardSpecs,
- proguardMapping,
- libraryJars,
- output.getOutputJar(),
- /* proguardOutputMap */ null,
- /* proguardOutputProtoMap */ null,
- output.getSeeds(), // ProGuard only prints seeds during INITIAL and NORMAL runtypes.
- /* proguardUsage */ null,
- /* constantStringObfuscatedMapping */ null,
- /* proguardConfigOutput */ null)
- .setProgressMessage("Trimming binary with Proguard: Verification/Shrinking Pass")
- .addArgument("-runtype INITIAL")
- .addArgument("-nextstageoutput")
- .addOutputArgument(lastStageOutput)
- .build(ruleContext));
+ Artifact lastStageOutput =
+ getProguardTempArtifact(
+ ruleContext, optMode.name().toLowerCase(), "proguard_preoptimization.jar");
+ SpawnAction.Builder initialAction = new SpawnAction.Builder();
+ CustomCommandLine.Builder initialCommandLine = CustomCommandLine.builder();
+ defaultAction(
+ initialAction,
+ initialCommandLine,
+ proguard,
+ programJar,
+ proguardSpecs,
+ proguardMapping,
+ libraryJars,
+ output.getOutputJar(),
+ /* proguardOutputMap */ null,
+ /* proguardOutputProtoMap */ null,
+ output.getSeeds(), // ProGuard only prints seeds during INITIAL and NORMAL runtypes.
+ /* proguardUsage */ null,
+ /* constantStringObfuscatedMapping */ null,
+ /* proguardConfigOutput */ null);
+ initialAction
+ .setProgressMessage("Trimming binary with Proguard: Verification/Shrinking Pass")
+ .addOutput(lastStageOutput);
+ initialCommandLine.add("-runtype INITIAL").addExecPath("-nextstageoutput", lastStageOutput);
+ initialAction.setCommandLine(initialCommandLine.build());
+ ruleContext.registerAction(initialAction.build(ruleContext));
for (int i = 1; i <= optimizationPasses; i++) {
// Run configured optimizers in order in each pass
@@ -468,35 +480,48 @@ public abstract class ProguardHelper {
checkState("Proguard".equals(mnemonic), "Need label to run %s", mnemonic);
executable = proguard;
}
- Artifact optimizationOutput = getProguardTempArtifact(
- ruleContext, optMode.name().toLowerCase(), mnemonic + "_optimization_" + i + ".jar");
- ruleContext.registerAction(
- defaultAction(
- checkNotNull(executable, "couldn't find optimizer %s", optimizer),
- programJar,
- proguardSpecs,
- proguardMapping,
- libraryJars,
- output.getOutputJar(),
- /* proguardOutputMap */ null,
- /* proguardOutputProtoMap */ null,
- /* proguardSeeds */ null,
- /* proguardUsage */ null,
- /* constantStringObfuscatedMapping */ null,
- /* proguardConfigOutput */ null)
- .setProgressMessage("Trimming binary with %s: Optimization Pass %d", mnemonic, +i)
- .setMnemonic(mnemonic)
- .addArgument("-runtype OPTIMIZATION")
- .addArgument("-laststageoutput")
- .addInputArgument(lastStageOutput)
- .addArgument("-nextstageoutput")
- .addOutputArgument(optimizationOutput)
- .build(ruleContext));
+ Artifact optimizationOutput =
+ getProguardTempArtifact(
+ ruleContext,
+ optMode.name().toLowerCase(),
+ mnemonic + "_optimization_" + i + ".jar");
+ SpawnAction.Builder optimizationAction = new SpawnAction.Builder();
+ CustomCommandLine.Builder optimizationCommandLine = CustomCommandLine.builder();
+ defaultAction(
+ optimizationAction,
+ optimizationCommandLine,
+ checkNotNull(executable, "couldn't find optimizer %s", optimizer),
+ programJar,
+ proguardSpecs,
+ proguardMapping,
+ libraryJars,
+ output.getOutputJar(),
+ /* proguardOutputMap */ null,
+ /* proguardOutputProtoMap */ null,
+ /* proguardSeeds */ null,
+ /* proguardUsage */ null,
+ /* constantStringObfuscatedMapping */ null,
+ /* proguardConfigOutput */ null);
+ optimizationAction
+ .setProgressMessage("Trimming binary with %s: Optimization Pass %d", mnemonic, +i)
+ .setMnemonic(mnemonic)
+ .addInput(lastStageOutput)
+ .addOutput(optimizationOutput);
+ optimizationCommandLine
+ .add("-runtype OPTIMIZATION")
+ .addExecPath("-laststageoutput", lastStageOutput)
+ .addExecPath("-nextstageoutput", optimizationOutput);
+ optimizationAction.setCommandLine(optimizationCommandLine.build());
+ ruleContext.registerAction(optimizationAction.build(ruleContext));
lastStageOutput = optimizationOutput;
}
}
- SpawnAction.Builder builder = defaultAction(
+ SpawnAction.Builder finalAction = new SpawnAction.Builder();
+ CustomCommandLine.Builder finalCommandLine = CustomCommandLine.builder();
+ defaultAction(
+ finalAction,
+ finalCommandLine,
proguard,
programJar,
proguardSpecs,
@@ -505,23 +530,25 @@ public abstract class ProguardHelper {
output.getOutputJar(),
output.getMapping(),
output.getProtoMapping(),
- /* proguardSeeds */ null, // runtype FINAL does not produce seeds.
+ /* proguardSeeds */ null, // runtype FINAL does not produce seeds.
output.getUsage(),
output.getConstantStringObfuscatedMapping(),
- output.getConfig())
+ output.getConfig());
+ finalAction
.setProgressMessage("Trimming binary with Proguard: Obfuscation and Final Output Pass")
- .addArgument("-runtype FINAL")
- .addArgument("-laststageoutput")
- .addInputArgument(lastStageOutput)
+ .addInput(lastStageOutput)
.addOutput(proguardOutputJar);
-
- ruleContext.registerAction(builder.build(ruleContext));
+ finalCommandLine.add("-runtype FINAL").addExecPath("-laststageoutput", lastStageOutput);
+ finalAction.setCommandLine(finalCommandLine.build());
+ ruleContext.registerAction(finalAction.build(ruleContext));
}
return output;
}
- private static SpawnAction.Builder defaultAction(
+ private static void defaultAction(
+ SpawnAction.Builder builder,
+ CustomCommandLine.Builder commandLine,
FilesToRunProvider executable,
Artifact programJar,
ImmutableList<Artifact> proguardSpecs,
@@ -535,104 +562,97 @@ public abstract class ProguardHelper {
@Nullable Artifact constantStringObfuscatedMapping,
@Nullable Artifact proguardConfigOutput) {
- Builder builder = new SpawnAction.Builder()
+ builder
.addInputs(libraryJars)
.addInputs(proguardSpecs)
.setExecutable(executable)
.setMnemonic("Proguard")
- .addArgument("-forceprocessing")
- .addArgument("-injars")
- .addInputArgument(programJar)
+ .addInput(programJar);
+
+ commandLine
+ .add("-forceprocessing")
+ .addExecPath("-injars", programJar)
// This is handled by the build system there is no need for proguard to check if things are
// up to date.
- .addArgument("-outjars")
+ .add("-outjars")
// Don't register the output jar as an output of the action, because multiple proguard
// actions will be created for optimization runs which will overwrite the jar, and only
// the final proguard action will declare the output jar as an output.
- .addArgument(proguardOutputJar.getExecPathString());
+ .addExecPath(proguardOutputJar);
for (Artifact libraryJar : libraryJars) {
- builder
- .addArgument("-libraryjars")
- .addArgument(libraryJar.getExecPathString());
+ commandLine.addExecPath("-libraryjars", libraryJar);
}
if (proguardMapping != null) {
- builder
- .addArgument("-applymapping")
- .addInputArgument(proguardMapping);
+ builder.addInput(proguardMapping);
+ commandLine.addExecPath("-applymapping", proguardMapping);
}
for (Artifact proguardSpec : proguardSpecs) {
- builder.addArgument("@" + proguardSpec.getExecPathString());
+ commandLine.addPrefixedExecPath("@", proguardSpec);
}
if (proguardOutputMap != null) {
- builder
- .addArgument("-printmapping")
- .addOutputArgument(proguardOutputMap);
+ builder.addOutput(proguardOutputMap);
+ commandLine.addExecPath("-printmapping", proguardOutputMap);
}
if (proguardOutputProtoMap != null) {
- builder
- .addArgument("-protomapping")
- .addOutputArgument(proguardOutputProtoMap);
+ builder.addOutput(proguardOutputProtoMap);
+ commandLine.addExecPath("-protomapping", proguardOutputProtoMap);
}
if (constantStringObfuscatedMapping != null) {
- builder
- .addArgument("-obfuscatedconstantstringoutputfile")
- .addOutputArgument(constantStringObfuscatedMapping);
+ builder.addOutput(constantStringObfuscatedMapping);
+ commandLine.addExecPath(
+ "-obfuscatedconstantstringoutputfile", constantStringObfuscatedMapping);
}
if (proguardSeeds != null) {
- builder
- .addArgument("-printseeds")
- .addOutputArgument(proguardSeeds);
+ builder.addOutput(proguardSeeds);
+ commandLine.addExecPath("-printseeds", proguardSeeds);
}
if (proguardUsage != null) {
- builder
- .addArgument("-printusage")
- .addOutputArgument(proguardUsage);
+ builder.addOutput(proguardUsage);
+ commandLine.addExecPath("-printusage", proguardUsage);
}
if (proguardConfigOutput != null) {
- builder
- .addArgument("-printconfiguration")
- .addOutputArgument(proguardConfigOutput);
+ builder.addOutput(proguardConfigOutput);
+ commandLine.addExecPath("-printconfiguration", proguardConfigOutput);
}
-
- return builder;
}
- /**
- * Returns an intermediate artifact used to run Proguard.
- */
+ /** Returns an intermediate artifact used to run Proguard. */
public static Artifact getProguardTempArtifact(
RuleContext ruleContext, String prefix, String name) {
// TODO(bazel-team): Remove the redundant inclusion of the rule name, as getUniqueDirectory
// includes the rulename as well.
- return Preconditions.checkNotNull(ruleContext.getUniqueDirectoryArtifact(
- "proguard",
- Joiner.on("_").join(prefix, ruleContext.getLabel().getName(), name),
- ruleContext.getBinOrGenfilesDirectory()));
+ return Preconditions.checkNotNull(
+ ruleContext.getUniqueDirectoryArtifact(
+ "proguard",
+ Joiner.on("_").join(prefix, ruleContext.getLabel().getName(), name),
+ ruleContext.getBinOrGenfilesDirectory()));
}
public static Artifact getProguardConfigArtifact(RuleContext ruleContext, String prefix) {
return getProguardTempArtifact(ruleContext, prefix, "proguard.cfg");
}
- /**
- * Returns {@link JavaConfiguration#getJavaOptimizationMode()}.
- */
+ /** Returns {@link JavaConfiguration#getJavaOptimizationMode()}. */
public static JavaOptimizationMode getJavaOptimizationMode(RuleContext ruleContext) {
- return ruleContext.getConfiguration().getFragment(JavaConfiguration.class)
+ return ruleContext
+ .getConfiguration()
+ .getFragment(JavaConfiguration.class)
.getJavaOptimizationMode();
}
private static Map<String, Optional<Label>> getBytecodeOptimizers(RuleContext ruleContext) {
- return ruleContext.getConfiguration().getFragment(JavaConfiguration.class)
+ return ruleContext
+ .getConfiguration()
+ .getFragment(JavaConfiguration.class)
.getBytecodeOptimizers();
}
}