Include ignored configuration files options in library rules validation
Fixes: b/486828368
Change-Id: I9ae34fc6c47be1df68bb4f7731228ada8099408b
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java b/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java
index 86a507c..a7426d4 100644
--- a/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java
+++ b/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java
@@ -154,8 +154,13 @@
@Override
public void addIgnoredOption(
String option, ProguardConfigurationSourceParser parser, TextPosition positionStart) {
- ensureNewlineAfterComment();
- write(parser, positionStart);
+ writeComment(parser, positionStart);
+ }
+
+ @Override
+ public void addUnsupportedOption(
+ String option, ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+ writeComment(parser, positionStart);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java b/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java
index c4a5b3a..26d19bc 100644
--- a/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java
+++ b/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java
@@ -101,7 +101,16 @@
@Override
public void addIgnoredOption(
- String option, ProguardConfigurationSourceParser parser, TextPosition positionStart) {}
+ String option, ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+ assert !option.isEmpty() && option.charAt(0) != '-';
+ handleRule(parser, positionStart, "-" + option);
+ }
+
+ @Override
+ public void addUnsupportedOption(
+ String option, ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+ handleRule(parser, positionStart, "-" + option);
+ }
@Override
public void addInclude(
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 485ed07..b88e994 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.shaking.ProguardConfigurationParser.ProguardConfigurationSourceParser;
import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -123,6 +124,14 @@
}
@Override
+ public void addUnsupportedOption(
+ String option, ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+ reporter.error(
+ new StringDiagnostic(
+ "Unsupported option: -" + option, parser.getOrigin(), positionStart));
+ }
+
+ @Override
public void addInclude(
Path includePath, ProguardConfigurationSourceParser parser, TextPosition positionStart) {
IncludeWorkItem include = new IncludeWorkItem(includePath, positionStart, parser.getOffset());
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 13101ed..e04eba7 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -153,6 +153,48 @@
return ImmutableList.copyOf(builder.build().getRules());
}
+ public static List<String> getUnsupportedOptions() {
+ return ImmutableList.<String>builder().addAll(UNSUPPORTED_FLAG_OPTIONS).build();
+ }
+
+ public static List<String> getIgnoredOptions() {
+ return ImmutableList.<String>builder()
+ .addAll(IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS)
+ .addAll(IGNORED_FLAG_OPTIONS)
+ .addAll(WARNED_OPTIONAL_SINGLE_ARG_OPTIONS)
+ .addAll(WARNED_FLAG_OPTIONS)
+ .build();
+ }
+
+ public static List<String> getIgnoredOptionsSingleArg() {
+ return ImmutableList.<String>builder()
+ .addAll(IGNORED_SINGLE_ARG_OPTIONS)
+ .addAll(WARNED_SINGLE_ARG_OPTIONS)
+ .add("optimizationpasses")
+ .add("optimizations")
+ .build();
+ }
+
+ public static List<String> getIgnoredOptionsClassDescriptor() {
+ return ImmutableList.<String>builder()
+ .addAll(IGNORED_CLASS_DESCRIPTOR_OPTIONS)
+ .addAll(WARNED_CLASS_DESCRIPTOR_OPTIONS)
+ .build();
+ }
+
+ public static List<String> getIgnoredOptionsWithWarning() {
+ return ImmutableList.<String>builder()
+ .addAll(WARNED_OPTIONAL_SINGLE_ARG_OPTIONS)
+ .addAll(WARNED_FLAG_OPTIONS)
+ .addAll(WARNED_SINGLE_ARG_OPTIONS)
+ .addAll(WARNED_CLASS_DESCRIPTOR_OPTIONS)
+ .build();
+ }
+
+ public static List<String> getIgnoredOptionsWithInfo() {
+ return ImmutableList.<String>builder().add("optimizationpasses").add("optimizations").build();
+ }
+
public ProguardConfigurationParser(
DexItemFactory dexItemFactory,
Reporter reporter,
@@ -353,7 +395,7 @@
if (parseIgnoredOption(optionStart)
|| parseIgnoredOptionAndWarn(optionStart)
|| parseTestingOption(optionStart)
- || parseUnsupportedOptionAndErr(optionStart)) {
+ || parseUnsupportedOption(optionStart)) {
// Intentionally left empty.
} else if (acceptString("keepkotlinmetadata")) {
configurationConsumer.addKeepKotlinMetadata(this, getPosition(optionStart), optionStart);
@@ -669,14 +711,19 @@
getPosition(optionStart)));
}
- private boolean parseUnsupportedOptionAndErr(TextPosition optionStart) {
- String option = Iterables.find(UNSUPPORTED_FLAG_OPTIONS, this::skipFlag, null);
- if (option != null) {
- reporter.error(new StringDiagnostic(
- "Unsupported option: -" + option, origin, getPosition(optionStart)));
- return true;
- }
- return false;
+ private boolean parseUnsupportedOption(TextPosition optionStart) {
+ String option =
+ Iterables.find(
+ UNSUPPORTED_FLAG_OPTIONS,
+ name -> {
+ if (acceptString(name)) {
+ configurationConsumer.addUnsupportedOption(name, this, optionStart);
+ return true;
+ }
+ return false;
+ },
+ null);
+ return option != null;
}
private boolean parseIgnoredOptionAndWarn(TextPosition optionStart) {
@@ -801,7 +848,9 @@
}
private boolean skipFlag(String name) {
+ TextPosition start = getPosition();
if (acceptString(name)) {
+ configurationConsumer.addIgnoredOption(name, this, start);
return true;
}
return false;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java
index a47e4a2..ee55534 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java
@@ -19,6 +19,9 @@
void addIgnoredOption(
String option, ProguardConfigurationSourceParser parser, TextPosition positionStart);
+ void addUnsupportedOption(
+ String option, ProguardConfigurationSourceParser parser, TextPosition positionStart);
+
void addInclude(
Path includePath, ProguardConfigurationSourceParser parser, TextPosition positionStart);
diff --git a/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java
index e3b6656..750917aa 100644
--- a/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java
+++ b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java
@@ -7,21 +7,31 @@
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestDiagnosticMessagesImpl;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
-import com.google.common.collect.ImmutableMap;
+import com.android.tools.r8.shaking.ProguardConfigurationParser;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedMap;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import org.hamcrest.Matcher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -31,83 +41,132 @@
@RunWith(Parameterized.class)
public class ProcessKeepRulesCommandTest extends TestBase {
- private static final Map<String, String> testRules =
- ImmutableMap.<String, String>builder()
- .put("-dontoptimize", "-dontoptimize not allowed in library consumer rules.")
- .put("-dontobfuscate", "-dontobfuscate not allowed in library consumer rules.")
- .put("-dontshrink", "-dontshrink not allowed in library consumer rules.")
- .put("-repackageclasses", "-repackageclasses not allowed in library consumer rules.")
- .put("-applymapping foo", "-applymapping not allowed in library consumer rules.")
- .put("-injars foo", "-injars not allowed in library consumer rules.")
- .put("-libraryjars foo", "-libraryjars not allowed in library consumer rules.")
- .put("-printconfiguration", "-printconfiguration not allowed in library consumer rules.")
- .put("-printmapping", "-printmapping not allowed in library consumer rules.")
- .put("-printseeds", "-printseeds not allowed in library consumer rules.")
- .put("-printusage", "-printusage not allowed in library consumer rules.")
- .put(
- "-obfuscationdictionary foo",
- "-obfuscationdictionary not allowed in library consumer rules.")
- .put(
- "-classobfuscationdictionary foo",
- "-classobfuscationdictionary not allowed in library consumer rules.")
- .put(
- "-packageobfuscationdictionary foo",
- "-packageobfuscationdictionary not allowed in library consumer rules.")
- .put(
- "-flattenpackagehierarchy",
- "-flattenpackagehierarchy not allowed in library consumer rules.")
- .put(
- "-allowaccessmodification",
- "-allowaccessmodification not allowed in library consumer rules.")
- .put(
- "-keepattributes LineNumberTable",
- "Illegal attempt to keep the attribute 'LineNumberTable' in library consumer rules.")
- .put(
- "-keepattributes RuntimeInvisibleAnnotations",
- "Illegal attempt to keep the attribute 'RuntimeInvisibleAnnotations' in library"
- + " consumer rules.")
- .put(
- "-keepattributes RuntimeInvisibleTypeAnnotations",
- "Illegal attempt to keep the attribute 'RuntimeInvisibleTypeAnnotations' in library"
- + " consumer rules.")
- .put(
- "-keepattributes RuntimeInvisibleParameterAnnotations",
- "Illegal attempt to keep the attribute 'RuntimeInvisibleParameterAnnotations' in"
- + " library consumer rules.")
- .put(
- "-keepattributes SourceFile",
- "Illegal attempt to keep the attribute 'SourceFile' in library consumer rules.")
- .put(
- "-maximumremovedandroidloglevel 2",
- "-maximumremovedandroidloglevel <int> not allowed in library consumer rules.")
- .put(
- "-renamesourcefileattribute",
- "-renamesourcefileattribute not allowed in library consumer rules.")
- .put(
- "-shrinkunusedprotofields",
- "-shrinkunusedprotofields not allowed in library consumer rules.")
- .put(
- "-whyareyoukeeping class *",
- "-whyareyoukeeping not allowed in library consumer rules.")
- .put(
- "-whyareyounotobfuscating class *",
- "-whyareyounotobfuscating not allowed in library consumer rules.")
- .put(
- "-whyareyounotinlining class * { *; }",
- "-whyareyounotinlining not allowed in library consumer rules.")
- .put(
- "-processkotlinnullchecks",
- "-processkotlinnullchecks not allowed in library consumer rules.")
- .put(
- "-processkotlinnullchecks keep",
- "-processkotlinnullchecks not allowed in library consumer rules.")
- .put(
- "-processkotlinnullchecks remove_message",
- "-processkotlinnullchecks not allowed in library consumer rules.")
- .put(
- "-processkotlinnullchecks remove",
- "-processkotlinnullchecks not allowed in library consumer rules.")
- .build();
+ private static final Map<String, String> testRules;
+ private static final Set<String> testRulesWithInfo;
+ private static final Set<String> testRulesWithWarning;
+ private static final Set<String> testRulesUnsupported;
+
+ static {
+ ImmutableSortedMap.Builder<String, String> builder = ImmutableSortedMap.naturalOrder();
+ builder
+ .put("-dontoptimize", "-dontoptimize not allowed in library consumer rules.")
+ .put("-dontobfuscate", "-dontobfuscate not allowed in library consumer rules.")
+ .put("-dontshrink", "-dontshrink not allowed in library consumer rules.")
+ .put("-repackageclasses", "-repackageclasses not allowed in library consumer rules.")
+ .put("-applymapping foo", "-applymapping not allowed in library consumer rules.")
+ .put("-injars foo", "-injars not allowed in library consumer rules.")
+ .put("-libraryjars foo", "-libraryjars not allowed in library consumer rules.")
+ .put("-printconfiguration", "-printconfiguration not allowed in library consumer rules.")
+ .put("-printmapping", "-printmapping not allowed in library consumer rules.")
+ .put("-printseeds", "-printseeds not allowed in library consumer rules.")
+ .put("-printusage", "-printusage not allowed in library consumer rules.")
+ .put(
+ "-obfuscationdictionary foo",
+ "-obfuscationdictionary not allowed in library consumer rules.")
+ .put(
+ "-classobfuscationdictionary foo",
+ "-classobfuscationdictionary not allowed in library consumer rules.")
+ .put(
+ "-packageobfuscationdictionary foo",
+ "-packageobfuscationdictionary not allowed in library consumer rules.")
+ .put(
+ "-flattenpackagehierarchy",
+ "-flattenpackagehierarchy not allowed in library consumer rules.")
+ .put(
+ "-allowaccessmodification",
+ "-allowaccessmodification not allowed in library consumer rules.")
+ .put(
+ "-keepattributes LineNumberTable",
+ "Illegal attempt to keep the attribute 'LineNumberTable' in library consumer rules.")
+ .put(
+ "-keepattributes RuntimeInvisibleAnnotations",
+ "Illegal attempt to keep the attribute 'RuntimeInvisibleAnnotations' in library"
+ + " consumer rules.")
+ .put(
+ "-keepattributes RuntimeInvisibleTypeAnnotations",
+ "Illegal attempt to keep the attribute 'RuntimeInvisibleTypeAnnotations' in library"
+ + " consumer rules.")
+ .put(
+ "-keepattributes RuntimeInvisibleParameterAnnotations",
+ "Illegal attempt to keep the attribute 'RuntimeInvisibleParameterAnnotations' in"
+ + " library consumer rules.")
+ .put(
+ "-keepattributes SourceFile",
+ "Illegal attempt to keep the attribute 'SourceFile' in library consumer rules.")
+ .put(
+ "-maximumremovedandroidloglevel 2",
+ "-maximumremovedandroidloglevel <int> not allowed in library consumer rules.")
+ .put(
+ "-renamesourcefileattribute",
+ "-renamesourcefileattribute not allowed in library consumer rules.")
+ .put(
+ "-shrinkunusedprotofields",
+ "-shrinkunusedprotofields not allowed in library consumer rules.")
+ .put(
+ "-whyareyoukeeping class *", "-whyareyoukeeping not allowed in library consumer rules.")
+ .put(
+ "-whyareyounotobfuscating class *",
+ "-whyareyounotobfuscating not allowed in library consumer rules.")
+ .put(
+ "-whyareyounotinlining class * { *; }",
+ "-whyareyounotinlining not allowed in library consumer rules.")
+ .put(
+ "-processkotlinnullchecks",
+ "-processkotlinnullchecks not allowed in library consumer rules.")
+ .put(
+ "-processkotlinnullchecks keep",
+ "-processkotlinnullchecks not allowed in library consumer rules.")
+ .put(
+ "-processkotlinnullchecks remove_message",
+ "-processkotlinnullchecks not allowed in library consumer rules.")
+ .put(
+ "-processkotlinnullchecks remove",
+ "-processkotlinnullchecks not allowed in library consumer rules.");
+ // Test ignored options and collect info the checking info/warning diagnostics.
+ ImmutableSet.Builder<String> withInfoBuilder = ImmutableSet.builder();
+ ImmutableSet.Builder<String> withWarningBuilder = ImmutableSet.builder();
+ ImmutableSet.Builder<String> unsupportedBuilder = ImmutableSet.builder();
+
+ List<String> ignoredOptionsWithInfo = ProguardConfigurationParser.getIgnoredOptionsWithInfo();
+ List<String> ignoredOptionsWithWarning =
+ ProguardConfigurationParser.getIgnoredOptionsWithWarning();
+
+ BiConsumer<String, String> updateInfoWarningsSets =
+ (option, key) -> {
+ if (ignoredOptionsWithInfo.contains(option)) {
+ withInfoBuilder.add(key);
+ }
+ if (ignoredOptionsWithWarning.contains(option)) {
+ withWarningBuilder.add(key);
+ }
+ };
+
+ for (String option : ProguardConfigurationParser.getIgnoredOptions()) {
+ String rule = "-" + option;
+ builder.put(rule, rule + " not allowed in library consumer rules.");
+ updateInfoWarningsSets.accept(option, rule);
+ }
+ for (String option : ProguardConfigurationParser.getIgnoredOptionsSingleArg()) {
+ String rule = "-" + option + (option.equals("optimizationpasses") ? " 3" : " arg");
+ builder.put(rule, "-" + option + " not allowed in library consumer rules.");
+ updateInfoWarningsSets.accept(option, rule);
+ }
+ for (String option : ProguardConfigurationParser.getIgnoredOptionsClassDescriptor()) {
+ String rule = "-" + option + " class X";
+ builder.put(rule, "-" + option + " not allowed in library consumer rules.");
+ updateInfoWarningsSets.accept(option, rule);
+ }
+ for (String option : ProguardConfigurationParser.getUnsupportedOptions()) {
+ String rule = "-" + option;
+ builder.put(rule, rule + " not allowed in library consumer rules.");
+ unsupportedBuilder.add(rule);
+ }
+
+ testRules = builder.build();
+ testRulesWithInfo = withInfoBuilder.build();
+ testRulesWithWarning = withWarningBuilder.build();
+ testRulesUnsupported = unsupportedBuilder.build();
+ }
@Parameter(1)
public TestParameters parameters;
@@ -122,50 +181,84 @@
@Test
public void test() throws Exception {
- String rules = configAndExpectedDiagnostic.getKey();
+ String rule = configAndExpectedDiagnostic.getKey();
Origin origin = new PathOrigin(Paths.get("keep.txt"));
try {
validate(
- rules,
+ rule,
origin,
- diagnostics ->
- diagnostics.assertErrorsMatch(
- allOf(
- rules.startsWith("-keepattributes")
- ? diagnosticType(KeepAttributeLibraryConsumerRuleDiagnostic.class)
- : diagnosticType(LibraryConsumerRuleDiagnostic.class),
- diagnosticOrigin(equalTo(origin)),
- diagnosticMessage(equalTo(configAndExpectedDiagnostic.getValue())))));
+ diagnostics -> {
+ diagnostics.assertInfosCount(testRulesWithInfo.contains(rule) ? 1 : 0);
+ if (testRulesWithWarning.contains(rule)) {
+ diagnostics.assertWarningsMatch(ignoringOptionMatcher(origin));
+ } else {
+ diagnostics.assertNoWarnings();
+ }
+ for (Diagnostic error : diagnostics.getErrors()) {
+ if (error instanceof StringDiagnostic) {
+ assertTrue(testRulesUnsupported.contains(rule));
+ assertThat(error, unsupportedOptionMatcher(origin));
+ } else {
+ assertThat(
+ error,
+ allOf(
+ rule.startsWith("-keepattributes")
+ ? diagnosticType(KeepAttributeLibraryConsumerRuleDiagnostic.class)
+ : diagnosticType(LibraryConsumerRuleDiagnostic.class),
+ diagnosticOrigin(equalTo(origin)),
+ diagnosticMessage(equalTo(configAndExpectedDiagnostic.getValue()))));
+ }
+ }
+ });
fail("Expect the compilation to fail.");
} catch (CompilationFailedException e) {
// Expected.
}
- // Rerun validation after filtering. This should succeed without diagnostics.
- validate(filter(rules, origin), origin, TestDiagnosticMessagesImpl::assertNoMessages);
+ validate(filter(rule, origin), origin, TestDiagnosticMessagesImpl::assertNoMessages);
}
- private String filter(String rules, Origin origin) throws CompilationFailedException {
+ private static Matcher<Diagnostic> ignoringOptionMatcher(Origin origin) {
+ return allOf(
+ diagnosticType(StringDiagnostic.class),
+ diagnosticOrigin(equalTo(origin)),
+ diagnosticMessage(containsString("Ignoring option: ")));
+ }
+
+ private static Matcher<Diagnostic> unsupportedOptionMatcher(Origin origin) {
+ return allOf(
+ diagnosticType(StringDiagnostic.class),
+ diagnosticOrigin(equalTo(origin)),
+ diagnosticMessage(containsString("Unsupported option: ")));
+ }
+
+ private String filter(String rule, Origin origin) throws CompilationFailedException {
TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
StringBuilder result = new StringBuilder();
ProcessKeepRulesCommand command =
ProcessKeepRulesCommand.builder(diagnostics)
- .addKeepRules(rules, origin)
+ .addKeepRules(rule, origin)
.setFilteredKeepRulesConsumer((s, h) -> result.append(s))
.build();
ProcessKeepRules.run(command);
- diagnostics.assertNoMessages();
+ if (testRulesWithWarning.contains(rule)) {
+ diagnostics.assertAllWarningsMatch(ignoringOptionMatcher(origin)).assertOnlyWarnings();
+ } else if (testRulesWithInfo.contains(rule)) {
+ diagnostics.assertAllInfosMatch(ignoringOptionMatcher(origin)).assertOnlyInfos();
+ } else {
+ diagnostics.assertNoMessages();
+ }
return result.toString();
}
private void validate(
- String rules, Origin origin, Consumer<TestDiagnosticMessagesImpl> diagnosticsInspector)
+ String rule, Origin origin, Consumer<TestDiagnosticMessagesImpl> diagnosticsInspector)
throws CompilationFailedException {
TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
try {
ProcessKeepRulesCommand command =
ProcessKeepRulesCommand.builder(diagnostics)
- .addKeepRules(rules, origin)
+ .addKeepRules(rule, origin)
.setLibraryConsumerRuleValidation(true)
.build();
ProcessKeepRules.run(command);