Add support for --convert-to-jdiff <signature> <xml>
am: de6bb6f932

Change-Id: I8878854a0d9ef096692823aa6b4047f10ef72e5a
diff --git a/.idea/dictionaries/metalava.xml b/.idea/dictionaries/metalava.xml
index 8c70194..9841166 100644
--- a/.idea/dictionaries/metalava.xml
+++ b/.idea/dictionaries/metalava.xml
@@ -8,6 +8,7 @@
       <w>bytecode</w>
       <w>canonicalized</w>
       <w>codebases</w>
+      <w>compat</w>
       <w>ctor</w>
       <w>dataname</w>
       <w>devsite</w>
@@ -28,6 +29,7 @@
       <w>interop</w>
       <w>jaif</w>
       <w>javadocs</w>
+      <w>jdiff</w>
       <w>jvmstatic</w>
       <w>knowntags</w>
       <w>kotlinx</w>
@@ -43,6 +45,7 @@
       <w>nullness</w>
       <w>offlinemode</w>
       <w>parsecomments</w>
+      <w>proguard</w>
       <w>realtime</w>
       <w>referenceonly</w>
       <w>resourcesdir</w>
diff --git a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
index 5f5938f..9cae0ff 100644
--- a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
+++ b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
@@ -1,7 +1,6 @@
 package com.android.tools.metalava
 
 import com.android.SdkConstants.ATTR_VALUE
-import com.android.SdkConstants.VALUE_TRUE
 import com.android.sdklib.SdkVersionInfo
 import com.android.sdklib.repository.AndroidSdkHandler
 import com.android.tools.lint.LintCliClient
@@ -29,6 +28,13 @@
 import java.util.regex.Pattern
 
 /**
+ * Whether to include textual descriptions of the API requirements instead
+ * of just inserting a since-tag. This should be off if there is post-processing
+ * to convert since tags in the documentation tool used.
+ */
+const val ADD_API_LEVEL_TEXT = false
+
+/**
  * Walk over the API and apply tweaks to the documentation, such as
  *     - Looking for annotations and converting them to auxiliary tags
  *       that will be processed by the documentation tools later.
@@ -603,6 +609,7 @@
     }
 
     fun applyApiLevels(applyApiLevelsXml: File) {
+        @Suppress("DEPRECATION") // still using older lint-api when building with soong
         val client = object : LintCliClient() {
             override fun findResource(relativePath: String): File? {
                 if (relativePath == ApiLookup.XML_FILE_PATH) {
@@ -616,7 +623,7 @@
             }
 
             override fun getCacheDir(name: String?, create: Boolean): File? {
-                if (create && System.getProperty(ENV_VAR_METALAVA_TESTS_RUNNING) == VALUE_TRUE) {
+                if (create && java.lang.Boolean.getBoolean(ENV_VAR_METALAVA_TESTS_RUNNING)) {
                     // Pick unique directory during unit tests
                     return Files.createTempDir()
                 }
@@ -654,7 +661,10 @@
 
     private fun addApiLevelDocumentation(level: Int, item: Item) {
         if (level > 1) {
-            appendDocumentation("Requires API level ${describeApiLevel(level)}", item, false)
+            @Suppress("ConstantConditionIf")
+            if (ADD_API_LEVEL_TEXT) { // See 113933920: Remove "Requires API level" from method comment
+                appendDocumentation("Requires API level ${describeApiLevel(level)}", item, false)
+            }
             // Also add @since tag, unless already manually entered.
             // TODO: Override it everywhere in case the existing doc is wrong (we know
             // better), and at least for OpenJDK sources we *should* since the since tags
diff --git a/src/main/java/com/android/tools/metalava/Driver.kt b/src/main/java/com/android/tools/metalava/Driver.kt
index 56e8f56..351805e 100644
--- a/src/main/java/com/android/tools/metalava/Driver.kt
+++ b/src/main/java/com/android/tools/metalava/Driver.kt
@@ -181,27 +181,7 @@
 private fun processFlags() {
     val stopwatch = Stopwatch.createStarted()
 
-    // --copy-annotations?
-    val privateAnnotationsSource = options.privateAnnotationsSource
-    val privateAnnotationsTarget = options.privateAnnotationsTarget
-    if (privateAnnotationsSource != null && privateAnnotationsTarget != null) {
-        val rewrite = RewriteAnnotations()
-        // Support pointing to both stub-annotations and stub-annotations/src/main/java
-        val src = File(privateAnnotationsSource, "src${File.separator}main${File.separator}java")
-        val source = if (src.isDirectory) src else privateAnnotationsSource
-        source.listFiles()?.forEach { file ->
-            rewrite.modifyAnnotationSources(null, file, File(privateAnnotationsTarget, file.name))
-        }
-    }
-
-    // --rewrite-annotations?
-    options.rewriteAnnotations?.let { RewriteAnnotations().rewriteAnnotations(it) }
-
-    // Convert android.jar files?
-    options.androidJarSignatureFiles?.let { root ->
-        // Generate API signature files for all the historical JAR files
-        ConvertJarsToSignatureFiles().convertJars(root)
-    }
+    processNonCodebaseFlags()
 
     val codebase =
         if (options.sources.size == 1 && options.sources[0].path.endsWith(SdkConstants.DOT_TXT)) {
@@ -451,6 +431,46 @@
     invokeDocumentationTool()
 }
 
+fun processNonCodebaseFlags() {
+    // --copy-annotations?
+    val privateAnnotationsSource = options.privateAnnotationsSource
+    val privateAnnotationsTarget = options.privateAnnotationsTarget
+    if (privateAnnotationsSource != null && privateAnnotationsTarget != null) {
+        val rewrite = RewriteAnnotations()
+        // Support pointing to both stub-annotations and stub-annotations/src/main/java
+        val src = File(privateAnnotationsSource, "src${File.separator}main${File.separator}java")
+        val source = if (src.isDirectory) src else privateAnnotationsSource
+        source.listFiles()?.forEach { file ->
+            rewrite.modifyAnnotationSources(null, file, File(privateAnnotationsTarget, file.name))
+        }
+    }
+
+    // --rewrite-annotations?
+    options.rewriteAnnotations?.let { RewriteAnnotations().rewriteAnnotations(it) }
+
+    // Convert android.jar files?
+    options.androidJarSignatureFiles?.let { root ->
+        // Generate API signature files for all the historical JAR files
+        ConvertJarsToSignatureFiles().convertJars(root)
+    }
+
+    for ((signatureFile, jDiffFile) in options.convertToXmlFiles) {
+        val apiType = ApiType.ALL
+        val apiEmit = apiType.getEmitFilter()
+        val apiReference = apiType.getReferenceFilter()
+
+        val signatureApi = SignatureFileLoader.load(
+            file = signatureFile,
+            kotlinStyleNulls = options.inputKotlinStyleNulls,
+            supportsStagedNullability = true
+        )
+
+        createReportFile(signatureApi, jDiffFile, "JDiff File") { printWriter ->
+            JDiffXmlWriter(printWriter, apiEmit, apiReference, signatureApi.preFiltered)
+        }
+    }
+}
+
 /**
  * Checks compatibility of the given codebase with the codebase described in the
  * signature file.
@@ -611,20 +631,18 @@
 }
 
 private fun migrateNulls(codebase: Codebase, previous: Codebase) {
-    if (options.migrateNulls) {
-        val codebaseSupportsNullability = previous.supportsStagedNullability
-        val prevSupportsNullability = previous.supportsStagedNullability
-        try {
-            previous.supportsStagedNullability = true
-            codebase.supportsStagedNullability = true
-            previous.compareWith(
-                NullnessMigration(), codebase,
-                ApiPredicate()
-            )
-        } finally {
-            previous.supportsStagedNullability = prevSupportsNullability
-            codebase.supportsStagedNullability = codebaseSupportsNullability
-        }
+    val codebaseSupportsNullability = codebase.supportsStagedNullability
+    val prevSupportsNullability = previous.supportsStagedNullability
+    try {
+        previous.supportsStagedNullability = true
+        codebase.supportsStagedNullability = true
+        previous.compareWith(
+            NullnessMigration(), codebase,
+            ApiPredicate()
+        )
+    } finally {
+        previous.supportsStagedNullability = prevSupportsNullability
+        codebase.supportsStagedNullability = codebaseSupportsNullability
     }
 }
 
diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt
index e120aec..a4a2472 100644
--- a/src/main/java/com/android/tools/metalava/Options.kt
+++ b/src/main/java/com/android/tools/metalava/Options.kt
@@ -38,86 +38,86 @@
 
 private const val MAX_LINE_WIDTH = 90
 
-private const val ARGS_COMPAT_OUTPUT = "--compatible-output"
-private const val ARG_HELP = "--help"
-private const val ARG_VERSION = "--version"
-private const val ARG_QUIET = "--quiet"
-private const val ARG_VERBOSE = "--verbose"
-private const val ARG_CLASS_PATH = "--classpath"
-private const val ARG_SOURCE_PATH = "--source-path"
-private const val ARG_SOURCE_FILES = "--source-files"
-private const val ARG_API = "--api"
-private const val ARG_XML_API = "--api-xml"
-private const val ARG_PRIVATE_API = "--private-api"
-private const val ARG_DEX_API = "--dex-api"
-private const val ARG_PRIVATE_DEX_API = "--private-dex-api"
-private const val ARG_SDK_VALUES = "--sdk-values"
-private const val ARG_REMOVED_API = "--removed-api"
-private const val ARG_REMOVED_DEX_API = "--removed-dex-api"
-private const val ARG_MERGE_ANNOTATIONS = "--merge-annotations"
-private const val ARG_INPUT_API_JAR = "--input-api-jar"
-private const val ARG_EXACT_API = "--exact-api"
-private const val ARG_STUBS = "--stubs"
-private const val ARG_DOC_STUBS = "--doc-stubs"
-private const val ARG_STUBS_SOURCE_LIST = "--write-stubs-source-list"
-private const val ARG_DOC_STUBS_SOURCE_LIST = "--write-doc-stubs-source-list"
-private const val ARG_PROGUARD = "--proguard"
-private const val ARG_EXTRACT_ANNOTATIONS = "--extract-annotations"
-private const val ARG_EXCLUDE_ANNOTATIONS = "--exclude-annotations"
-private const val ARG_HIDE_PACKAGE = "--hide-package"
-private const val ARG_MANIFEST = "--manifest"
-private const val ARG_PREVIOUS_API = "--previous-api"
-private const val ARG_CURRENT_API = "--current-api"
-private const val ARG_MIGRATE_NULLNESS = "--migrate-nullness"
-private const val ARG_CHECK_COMPATIBILITY = "--check-compatibility"
-private const val ARG_CHECK_COMPATIBILITY_API_CURRENT = "--check-compatibility:api:current"
-private const val ARG_CHECK_COMPATIBILITY_API_RELEASED = "--check-compatibility:api:released"
-private const val ARG_CHECK_COMPATIBILITY_REMOVED_CURRENT = "--check-compatibility:removed:current"
-private const val ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED = "--check-compatibility:removed:released"
-private const val ARG_INPUT_KOTLIN_NULLS = "--input-kotlin-nulls"
-private const val ARG_OUTPUT_KOTLIN_NULLS = "--output-kotlin-nulls"
-private const val ARG_OUTPUT_DEFAULT_VALUES = "--output-default-values"
-private const val ARG_ANNOTATION_COVERAGE_STATS = "--annotation-coverage-stats"
-private const val ARG_ANNOTATION_COVERAGE_OF = "--annotation-coverage-of"
-private const val ARG_WRITE_CLASS_COVERAGE_TO = "--write-class-coverage-to"
-private const val ARG_WRITE_MEMBER_COVERAGE_TO = "--write-member-coverage-to"
-private const val ARG_WARNINGS_AS_ERRORS = "--warnings-as-errors"
-private const val ARG_LINTS_AS_ERRORS = "--lints-as-errors"
-private const val ARG_SHOW_ANNOTATION = "--show-annotation"
-private const val ARG_SHOW_UNANNOTATED = "--show-unannotated"
-private const val ARG_COLOR = "--color"
-private const val ARG_NO_COLOR = "--no-color"
-private const val ARG_OMIT_COMMON_PACKAGES = "--omit-common-packages"
-private const val ARG_SKIP_JAVA_IN_COVERAGE_REPORT = "--skip-java-in-coverage-report"
-private const val ARG_NO_BANNER = "--no-banner"
-private const val ARG_ERROR = "--error"
-private const val ARG_WARNING = "--warning"
-private const val ARG_LINT = "--lint"
-private const val ARG_HIDE = "--hide"
-private const val ARG_UNHIDE_CLASSPATH_CLASSES = "--unhide-classpath-classes"
-private const val ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES = "--allow-referencing-unknown-classes"
-private const val ARG_NO_UNKNOWN_CLASSES = "--no-unknown-classes"
-private const val ARG_APPLY_API_LEVELS = "--apply-api-levels"
-private const val ARG_GENERATE_API_LEVELS = "--generate-api-levels"
-private const val ARG_ANDROID_JAR_PATTERN = "--android-jar-pattern"
-private const val ARG_CURRENT_VERSION = "--current-version"
-private const val ARG_CURRENT_CODENAME = "--current-codename"
-private const val ARG_CURRENT_JAR = "--current-jar"
-private const val ARG_CHECK_KOTLIN_INTEROP = "--check-kotlin-interop"
-private const val ARG_PUBLIC = "--public"
-private const val ARG_PROTECTED = "--protected"
-private const val ARG_PACKAGE = "--package"
-private const val ARG_PRIVATE = "--private"
-private const val ARG_HIDDEN = "--hidden"
-private const val ARG_NO_DOCS = "--no-docs"
-private const val ARG_JAVA_SOURCE = "--java-source"
-private const val ARG_REGISTER_ARTIFACT = "--register-artifact"
-private const val ARG_COPY_ANNOTATIONS = "--copy-annotations"
-private const val ARG_INCLUDE_ANNOTATION_CLASSES = "--include-annotation-classes"
-private const val ARG_REWRITE_ANNOTATIONS = "--rewrite-annotations"
-private const val ARG_INCLUDE_SOURCE_RETENTION = "--include-source-retention"
-private const val ARG_INCLUDE_SIG_VERSION = "--include-signature-version"
-private const val ARG_UPDATE_API = "--update-api"
+const val ARGS_COMPAT_OUTPUT = "--compatible-output"
+const val ARG_HELP = "--help"
+const val ARG_VERSION = "--version"
+const val ARG_QUIET = "--quiet"
+const val ARG_VERBOSE = "--verbose"
+const val ARG_CLASS_PATH = "--classpath"
+const val ARG_SOURCE_PATH = "--source-path"
+const val ARG_SOURCE_FILES = "--source-files"
+const val ARG_API = "--api"
+const val ARG_XML_API = "--api-xml"
+const val ARG_CONVERT_TO_JDIFF = "--convert-to-jdiff"
+const val ARG_PRIVATE_API = "--private-api"
+const val ARG_DEX_API = "--dex-api"
+const val ARG_PRIVATE_DEX_API = "--private-dex-api"
+const val ARG_SDK_VALUES = "--sdk-values"
+const val ARG_REMOVED_API = "--removed-api"
+const val ARG_REMOVED_DEX_API = "--removed-dex-api"
+const val ARG_MERGE_ANNOTATIONS = "--merge-annotations"
+const val ARG_INPUT_API_JAR = "--input-api-jar"
+const val ARG_EXACT_API = "--exact-api"
+const val ARG_STUBS = "--stubs"
+const val ARG_DOC_STUBS = "--doc-stubs"
+const val ARG_STUBS_SOURCE_LIST = "--write-stubs-source-list"
+const val ARG_DOC_STUBS_SOURCE_LIST = "--write-doc-stubs-source-list"
+const val ARG_PROGUARD = "--proguard"
+const val ARG_EXTRACT_ANNOTATIONS = "--extract-annotations"
+const val ARG_EXCLUDE_ANNOTATIONS = "--exclude-annotations"
+const val ARG_HIDE_PACKAGE = "--hide-package"
+const val ARG_MANIFEST = "--manifest"
+const val ARG_MIGRATE_NULLNESS = "--migrate-nullness"
+const val ARG_CHECK_COMPATIBILITY = "--check-compatibility"
+const val ARG_CHECK_COMPATIBILITY_API_CURRENT = "--check-compatibility:api:current"
+const val ARG_CHECK_COMPATIBILITY_API_RELEASED = "--check-compatibility:api:released"
+const val ARG_CHECK_COMPATIBILITY_REMOVED_CURRENT = "--check-compatibility:removed:current"
+const val ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED = "--check-compatibility:removed:released"
+const val ARG_INPUT_KOTLIN_NULLS = "--input-kotlin-nulls"
+const val ARG_OUTPUT_KOTLIN_NULLS = "--output-kotlin-nulls"
+const val ARG_OUTPUT_DEFAULT_VALUES = "--output-default-values"
+const val ARG_ANNOTATION_COVERAGE_STATS = "--annotation-coverage-stats"
+const val ARG_ANNOTATION_COVERAGE_OF = "--annotation-coverage-of"
+const val ARG_WRITE_CLASS_COVERAGE_TO = "--write-class-coverage-to"
+const val ARG_WRITE_MEMBER_COVERAGE_TO = "--write-member-coverage-to"
+const val ARG_WARNINGS_AS_ERRORS = "--warnings-as-errors"
+const val ARG_LINTS_AS_ERRORS = "--lints-as-errors"
+const val ARG_SHOW_ANNOTATION = "--show-annotation"
+const val ARG_SHOW_UNANNOTATED = "--show-unannotated"
+const val ARG_COLOR = "--color"
+const val ARG_NO_COLOR = "--no-color"
+const val ARG_OMIT_COMMON_PACKAGES = "--omit-common-packages"
+const val ARG_SKIP_JAVA_IN_COVERAGE_REPORT = "--skip-java-in-coverage-report"
+const val ARG_NO_BANNER = "--no-banner"
+const val ARG_ERROR = "--error"
+const val ARG_WARNING = "--warning"
+const val ARG_LINT = "--lint"
+const val ARG_HIDE = "--hide"
+const val ARG_UNHIDE_CLASSPATH_CLASSES = "--unhide-classpath-classes"
+const val ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES = "--allow-referencing-unknown-classes"
+const val ARG_NO_UNKNOWN_CLASSES = "--no-unknown-classes"
+const val ARG_APPLY_API_LEVELS = "--apply-api-levels"
+const val ARG_GENERATE_API_LEVELS = "--generate-api-levels"
+const val ARG_ANDROID_JAR_PATTERN = "--android-jar-pattern"
+const val ARG_CURRENT_VERSION = "--current-version"
+const val ARG_CURRENT_CODENAME = "--current-codename"
+const val ARG_CURRENT_JAR = "--current-jar"
+const val ARG_CHECK_KOTLIN_INTEROP = "--check-kotlin-interop"
+const val ARG_PUBLIC = "--public"
+const val ARG_PROTECTED = "--protected"
+const val ARG_PACKAGE = "--package"
+const val ARG_PRIVATE = "--private"
+const val ARG_HIDDEN = "--hidden"
+const val ARG_NO_DOCS = "--no-docs"
+const val ARG_JAVA_SOURCE = "--java-source"
+const val ARG_REGISTER_ARTIFACT = "--register-artifact"
+const val ARG_INCLUDE_ANNOTATIONS = "--include-annotations"
+const val ARG_COPY_ANNOTATIONS = "--copy-annotations"
+const val ARG_INCLUDE_ANNOTATION_CLASSES = "--include-annotation-classes"
+const val ARG_REWRITE_ANNOTATIONS = "--rewrite-annotations"
+const val ARG_INCLUDE_SOURCE_RETENTION = "--include-source-retention"
+const val ARG_INCLUDE_SIG_VERSION = "--include-signature-version"
+const val ARG_UPDATE_API = "--update-api"
 const val ARG_DEX_API_MAPPING = "--dex-api-mapping"
 const val ARG_GENERATE_DOCUMENTATION = "--generate-documentation"
 
@@ -149,6 +149,8 @@
     private val mutableHidePackages: MutableList<String> = mutableListOf()
     /** Internal list backing [skipEmitPackages] */
     private val mutableSkipEmitPackages: MutableList<String> = mutableListOf()
+    /** Internal list backing [convertToXmlFiles] */
+    private val mutableConvertToXmlFiles: MutableList<Pair<File, File>> = mutableListOf()
 
     /** Ignored flags we've already warned about - store here such that we don't keep reporting them */
     private val alreadyWarned: MutableSet<String> = mutableSetOf()
@@ -342,9 +344,6 @@
     /** The list of compatibility checks to run */
     val compatibilityChecks: List<CheckRequest> = mutableCompatibilityChecks
 
-    /** Whether we should migrate nulls based on the previous API in [migrateNullsFrom] */
-    var migrateNulls: Boolean = false
-
     /** Existing external annotation files to merge in */
     var mergeAnnotations: List<File> = mutableMergeAnnotations
 
@@ -423,6 +422,9 @@
     /** Map from XML API descriptor file to corresponding artifact id name */
     val artifactRegistrations = ArtifactTagger()
 
+    /** List of signature files to export as JDiff files */
+    val convertToXmlFiles = mutableConvertToXmlFiles
+
     /** Temporary folder to use instead of the JDK default, if any */
     var tempFolder: File? = null
 
@@ -543,7 +545,7 @@
                 // Note that this only affects stub generation, not signature files.
                 // For signature files, clear the compatibility mode
                 // (--annotations-in-signatures)
-                "--include-annotations" -> generateAnnotations = true
+                ARG_INCLUDE_ANNOTATIONS -> generateAnnotations = true
 
                 // Flag used by test suite to avoid including locations in
                 // the output when diffing against golden files
@@ -593,17 +595,14 @@
                 ARG_INCLUDE_ANNOTATION_CLASSES -> copyStubAnnotationsFrom = stringToExistingDir(getValue(args, ++index))
                 ARG_INCLUDE_SOURCE_RETENTION -> includeSourceRetentionAnnotations = true
 
-                ARG_PREVIOUS_API -> {
+                "--previous-api" -> {
                     migrateNullsFrom = stringToExistingFile(getValue(args, ++index))
-                    /* Don't flag this yet: allow a short quiet grace period
                     reporter.report(Errors.DEPRECATED_OPTION, null as File?,
-                        "$ARG_PREVIOUS_API is deprecated; instead " +
+                        "--previous-api is deprecated; instead " +
                         "use $ARG_MIGRATE_NULLNESS $migrateNullsFrom")
-                    */
                 }
 
                 ARG_MIGRATE_NULLNESS -> {
-                    migrateNulls = true
                     // See if the next argument specifies the nullness API codebase
                     if (index < args.size - 1) {
                         val nextArg = args[index + 1]
@@ -617,14 +616,12 @@
                     }
                 }
 
-                ARG_CURRENT_API -> {
+                "--current-api" -> {
                     val file = stringToExistingFile(getValue(args, ++index))
                     mutableCompatibilityChecks.add(CheckRequest(file, ApiType.PUBLIC_API, ReleaseType.DEV))
-                    /* Don't flag this yet: allow a short quiet grace period
                     reporter.report(Errors.DEPRECATED_OPTION, null as File?,
-                        "$ARG_CURRENT_API is deprecated; instead " +
-                            "use $ARG_CHECK_COMPATIBILITY $checkCompatibilityApiCurrent")
-                    */
+                        "--current-api is deprecated; instead " +
+                            "use $ARG_CHECK_COMPATIBILITY_API_CURRENT")
                 }
 
                 ARG_CHECK_COMPATIBILITY -> {
@@ -797,6 +794,12 @@
                     artifactRegistrations.register(artifactId, descriptor)
                 }
 
+                ARG_CONVERT_TO_JDIFF -> {
+                    val signatureFile = stringToExistingFile(getValue(args, ++index))
+                    val jDiffFile = stringToNewFile(getValue(args, ++index))
+                    mutableConvertToXmlFiles.add(Pair(signatureFile, jDiffFile))
+                }
+
                 "--write-android-jar-signatures" -> {
                     val root = stringToExistingDir(getValue(args, ++index))
                     if (!File(root, "prebuilts/sdk").isDirectory) {
@@ -1127,10 +1130,6 @@
 
     /** Makes sure that the flag combinations make sense */
     private fun checkFlagConsistency() {
-        if (migrateNulls && migrateNullsFrom == null) {
-            throw DriverException(stderr = "$ARG_MIGRATE_NULLNESS requires $ARG_PREVIOUS_API")
-        }
-
         if (apiJar != null && sources.isNotEmpty()) {
             throw DriverException(stderr = "Specify either $ARG_SOURCE_FILES or $ARG_INPUT_API_JAR, not both")
         }
@@ -1203,6 +1202,7 @@
         return file
     }
 
+    @Suppress("unused")
     private fun stringToExistingDirs(value: String): List<File> {
         val files = mutableListOf<File>()
         for (path in value.split(File.pathSeparatorChar)) {
@@ -1247,6 +1247,7 @@
         return file
     }
 
+    @Suppress("unused")
     private fun stringToExistingFileOrDir(value: String): File {
         val file = fileForPath(value)
         if (!file.exists()) {
@@ -1425,7 +1426,6 @@
             "", "\nExtracting Signature Files:",
             // TODO: Document --show-annotation!
             "$ARG_API <file>", "Generate a signature descriptor file",
-            "$ARG_XML_API <file>", "Like $ARG_API, but emits the API in the JDiff XML format instead",
             "$ARG_PRIVATE_API <file>", "Generate a signature descriptor file listing the exact private APIs",
             "$ARG_DEX_API <file>", "Generate a DEX signature descriptor file listing the APIs",
             "$ARG_PRIVATE_DEX_API <file>", "Generate a DEX signature descriptor file listing the exact private APIs",
@@ -1488,6 +1488,11 @@
             "$ARG_LINT <id>", "Report issues of the given id as having lint-severity",
             "$ARG_HIDE <id>", "Hide/skip issues of the given id",
 
+            "", "\nJDiff:",
+            "$ARG_XML_API <file>", "Like $ARG_API, but emits the API in the JDiff XML format instead",
+            "$ARG_CONVERT_TO_JDIFF <sig> <xml>", "Reads in the given signature file, and writes it out " +
+                "in the JDiff XML format. Can be specified multiple times.",
+
             "", "\nStatistics:",
             ARG_ANNOTATION_COVERAGE_STATS, "Whether $PROGRAM_NAME should emit coverage statistics for " +
                 "annotations, listing the percentage of the API that has been annotated with nullness information.",
diff --git a/src/test/java/com/android/tools/metalava/AndroidApiChecksTest.kt b/src/test/java/com/android/tools/metalava/AndroidApiChecksTest.kt
index 93f2c8c..080e347 100644
--- a/src/test/java/com/android/tools/metalava/AndroidApiChecksTest.kt
+++ b/src/test/java/com/android/tools/metalava/AndroidApiChecksTest.kt
@@ -181,7 +181,7 @@
                 src/android/pkg/NullMentions.java:19: warning: Return value of 'method4' documentation mentions 'null' without declaring @NonNull or @Nullable [Nullable:123]
                 src/android/pkg/NullMentions.java:8: warning: Field 'field2' documentation mentions 'null' without declaring @NonNull or @Nullable [Nullable:123]
                 """,
-            extraArguments = arrayOf("--warning", "Nullable"), // Hidden by default
+            extraArguments = arrayOf(ARG_WARNING, "Nullable"), // Hidden by default
             sourceFiles = *arrayOf(
                 java(
                     """
@@ -225,7 +225,7 @@
             warnings = """
                 src/android/pkg/NullMentions.java:15: warning: Field 'field1' documentation mentions constants without declaring an @IntDef [IntDef:124]
                 """,
-            extraArguments = arrayOf("--warning", "IntDef"), // Hidden by default
+            extraArguments = arrayOf(ARG_WARNING, "IntDef"), // Hidden by default
             sourceFiles = *arrayOf(
                 java(
                     """
diff --git a/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt b/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt
index fec71c8..ddafb8e 100644
--- a/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt
+++ b/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt
@@ -24,7 +24,7 @@
     @Test
     fun `Test emitting annotation statistics`() {
         check(
-            extraArguments = arrayOf("--annotation-coverage-stats"),
+            extraArguments = arrayOf(ARG_ANNOTATION_COVERAGE_STATS),
             expectedOutput = """
                 Nullness Annotation Coverage Statistics:
                 0 out of 1 eligible fields (out of 2 total fields) were annotated (0%)
@@ -58,7 +58,7 @@
     @Test
     fun `Static final initialized fields are not nullable`() {
         check(
-            extraArguments = arrayOf("--annotation-coverage-stats"),
+            extraArguments = arrayOf(ARG_ANNOTATION_COVERAGE_STATS),
             expectedOutput = """
                 Nullness Annotation Coverage Statistics:
                 0 out of 0 eligible fields (out of 0 total fields) were annotated (100%)
diff --git a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
index b909a06..6471b92 100644
--- a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
+++ b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
@@ -56,9 +56,9 @@
             ),
             // Skip the annotations themselves from the output
             extraArguments = arrayOf(
-                "--hide-package", "android.annotation",
-                "--hide-package", "androidx.annotation",
-                "--hide-package", "android.support.annotation"
+                ARG_HIDE_PACKAGE, "android.annotation",
+                ARG_HIDE_PACKAGE, "androidx.annotation",
+                ARG_HIDE_PACKAGE, "android.support.annotation"
             ),
             api = """
                 package test.pkg {
diff --git a/src/test/java/com/android/tools/metalava/ApiFileTest.kt b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
index 9543d14..a7cb243 100644
--- a/src/test/java/com/android/tools/metalava/ApiFileTest.kt
+++ b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
@@ -106,7 +106,7 @@
                       }
                     }
                  """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             checkDoclava1 = false /* doesn't support parameter names */
         )
     }
@@ -141,7 +141,7 @@
                   }
                 }
                  """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             checkDoclava1 = false /* doesn't support default Values */
         )
     }
@@ -210,7 +210,7 @@
                   }
                 }
                 """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation", "--hide-package", "some.other.pkg"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation", ARG_HIDE_PACKAGE, "some.other.pkg"),
             includeSignatureVersion = true,
             checkDoclava1 = false /* doesn't support default Values */
         )
@@ -286,7 +286,7 @@
                   }
                 }
                 """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation", "--hide-package", "androidx.collection"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation", ARG_HIDE_PACKAGE, "androidx.collection"),
             includeSignatureVersion = true,
             checkDoclava1 = false /* doesn't support default Values */
         )
@@ -547,7 +547,7 @@
                   }
                 }
                 """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             checkDoclava1 = false /* doesn't support Kotlin... */
         )
     }
@@ -634,7 +634,7 @@
                   }
                 }
                 """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             checkDoclava1 = false /* doesn't support Kotlin... */
         )
     }
@@ -686,7 +686,7 @@
                   }
                 }
                 """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             checkDoclava1 = false /* doesn't support default Values */
         )
     }
@@ -778,7 +778,7 @@
                       }
                     }
                 """,
-            extraArguments = arrayOf("--hide", "KotlinKeyword")
+            extraArguments = arrayOf(ARG_HIDE, "KotlinKeyword")
         )
     }
 
@@ -1265,7 +1265,7 @@
             warnings = """
                 src/test/pkg/Foo.java:6: warning: method test.pkg.Foo.findViewById(int) should not be annotated @Nullable; it should be left unspecified to make it a platform type [ExpectedPlatformType:149]
                 """,
-            extraArguments = arrayOf("--warning", "ExpectedPlatformType"),
+            extraArguments = arrayOf(ARG_WARNING, "ExpectedPlatformType"),
             api = """
                 package test.pkg {
                   public abstract class Foo {
@@ -2408,7 +2408,7 @@
         check(
             checkDoclava1 = true,
             extraArguments = arrayOf(
-                "--hide-package", "com.squareup.okhttp"
+                ARG_HIDE_PACKAGE, "com.squareup.okhttp"
             ),
             sourceFiles = *arrayOf(
                 java(
diff --git a/src/test/java/com/android/tools/metalava/ArtifactTaggerTest.kt b/src/test/java/com/android/tools/metalava/ArtifactTaggerTest.kt
index 940447c..24b9595 100644
--- a/src/test/java/com/android/tools/metalava/ArtifactTaggerTest.kt
+++ b/src/test/java/com/android/tools/metalava/ArtifactTaggerTest.kt
@@ -80,7 +80,7 @@
                     }
                 """
             ),
-            extraArguments = arrayOf("--error", "NoArtifactData,BrokenArtifactFile"),
+            extraArguments = arrayOf(ARG_ERROR, "NoArtifactData,BrokenArtifactFile"),
             warnings = """
                 src/test/pkg/foo/Foo.java:2: error: Class test.pkg.foo.Foo belongs to multiple artifacts: my.library.group:foo:1.0.0 and my.library.group:bar:3.1.4 [BrokenArtifactFile:130]
                 src/test/pkg/foo/Foo.java:4: error: Class test.pkg.foo.Foo.Inner belongs to multiple artifacts: my.library.group:foo:1.0.0 and my.library.group:bar:3.1.4 [BrokenArtifactFile:130]
diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
index dde73d3..245406e 100644
--- a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
+++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
@@ -248,7 +248,7 @@
                 ),
                 supportParameterName
             ),
-            extraArguments = arrayOf("--hide-package", "androidx.annotation")
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
         )
     }
 
@@ -796,7 +796,7 @@
                 ),
                 suppressLintSource
             ),
-            extraArguments = arrayOf("--hide-package", "android.annotation")
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "android.annotation")
         )
     }
 
@@ -1728,9 +1728,9 @@
             ),
 
             extraArguments = arrayOf(
-                "--show-annotation", "android.annotation.TestApi",
-                "--hide-package", "android.annotation",
-                "--hide-package", "android.support.annotation"
+                ARG_SHOW_ANNOTATION, "android.annotation.TestApi",
+                ARG_HIDE_PACKAGE, "android.annotation",
+                ARG_HIDE_PACKAGE, "android.support.annotation"
             ),
 
             checkCompatibilityApi =
@@ -2165,7 +2165,7 @@
                 checkDoclava1 = false,
                 extraArguments = arrayOf(
                     "--omit-locations",
-                    "--hide",
+                    ARG_HIDE,
                     suppressLevels[apiLevel]
                         ?: "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated,RemovedField,RemovedClass,RemovedDeprecatedClass" +
                         (if ((apiLevel == 19 || apiLevel == 20) && loadPrevAsSignature) ",ChangedType" else "")
@@ -2192,7 +2192,7 @@
                     checkDoclava1 = false,
                     extraArguments = arrayOf(
                         "--omit-locations",
-                        "--hide",
+                        ARG_HIDE,
                         suppressLevels[apiLevel]
                             ?: "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated,RemovedField,RemovedClass,RemovedDeprecatedClass"
                     ),
diff --git a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
index 3a24c98..a61b43b 100644
--- a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
+++ b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
@@ -420,7 +420,6 @@
                 """
                 package android.widget;
                 /**
-                 * Requires API level 21 (Android 5.0, Lollipop)
                  * @since 21
                  */
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
@@ -431,8 +430,6 @@
                  * <br>
                  * This method must be called on the thread that originally created
                  * this UI element. This is typically the main thread of your app.
-                 * <br>
-                 * Requires API level 24 (Android 7.0, Nougat)
                  * @since 24
                  * @return blah blah blah
                  */
@@ -1072,7 +1069,6 @@
                 """
                 package android.widget;
                 /**
-                 * Requires API level 21 (Android 5.0, Lollipop)
                  * @since 21
                  */
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
@@ -1080,8 +1076,6 @@
                 public Toolbar() { throw new RuntimeException("Stub!"); }
                 /**
                  * Existing documentation for {@linkplain #getCurrentContentInsetEnd()} here.
-                 * <br>
-                 * Requires API level 24 (Android 7.0, Nougat)
                  * @since 24
                  * @return blah blah blah
                  */
@@ -1142,8 +1136,6 @@
                 public class Camera {
                 public Camera() { throw new RuntimeException("Stub!"); }
                 /**
-                 *
-                 * Requires API level 14 (Android 4.0, IceCreamSandwich)
                  * @deprecated
                  * <p class="caution"><strong>This class was deprecated in API level 21.</strong></p>
                  *  Use something else.
@@ -1280,7 +1272,6 @@
                 """
                 package test.pkg;
                 /**
-                 * Requires API level 21 (Android 5.0, Lollipop)
                  * @since 21
                  */
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
@@ -1495,9 +1486,9 @@
 
         check(
             extraArguments = arrayOf(
-                "--write-stubs-source-list",
+                ARG_STUBS_SOURCE_LIST,
                 sourceList,
-                "--generate-documentation",
+                ARG_GENERATE_DOCUMENTATION,
                 javadoc.path,
                 "-sourcepath",
                 "STUBS_DIR",
diff --git a/src/test/java/com/android/tools/metalava/DriverTest.kt b/src/test/java/com/android/tools/metalava/DriverTest.kt
index 252a0d5..21e5418 100644
--- a/src/test/java/com/android/tools/metalava/DriverTest.kt
+++ b/src/test/java/com/android/tools/metalava/DriverTest.kt
@@ -19,7 +19,6 @@
 import com.android.SdkConstants
 import com.android.SdkConstants.DOT_JAVA
 import com.android.SdkConstants.DOT_KT
-import com.android.SdkConstants.VALUE_TRUE
 import com.android.ide.common.process.DefaultProcessExecutor
 import com.android.ide.common.process.LoggedProcessOutputHandler
 import com.android.ide.common.process.ProcessException
@@ -45,6 +44,7 @@
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
 import org.junit.Assert.fail
+import org.junit.Before
 import org.junit.Rule
 import org.junit.rules.TemporaryFolder
 import java.io.File
@@ -60,6 +60,11 @@
     @get:Rule
     var temporaryFolder = TemporaryFolder()
 
+    @Before
+    fun setup() {
+        System.setProperty(ENV_VAR_METALAVA_TESTS_RUNNING, SdkConstants.VALUE_TRUE)
+    }
+
     private fun createProject(vararg files: TestFile): File {
         val dir = temporaryFolder.newFolder("project")
 
@@ -251,10 +256,13 @@
         /**
          * Whether to include the signature version in signatures
          */
-        includeSignatureVersion: Boolean = false
+        includeSignatureVersion: Boolean = false,
+        /**
+         * List of signature files to convert to JDiff XML and the
+         * expected XML output
+         */
+        convertToJDiff: List<Pair<String, String>> = emptyList()
     ) {
-        System.setProperty(ENV_VAR_METALAVA_TESTS_RUNNING, VALUE_TRUE)
-
         // Ensure different API clients don't interfere with each other
         try {
             val method = ApiLookup::class.java.getDeclaredMethod("dispose")
@@ -303,6 +311,9 @@
         val packages = sourceFiles.asSequence().map { findPackage(it.getContents()!!) }.filterNotNull().toSet()
 
         val sourcePathDir = File(project, "src")
+        if (!sourcePathDir.isDirectory) {
+            sourcePathDir.mkdirs()
+        }
         val sourcePath = sourcePathDir.path
         val sourceList =
             if (signatureSource != null) {
@@ -315,7 +326,7 @@
                 } else {
                     arrayOf(
                         signatureFile.path,
-                        "--hide",
+                        ARG_HIDE,
                         "HiddenSuperclass"
                     ) // Suppress warning #111
                 }
@@ -333,7 +344,7 @@
                 .map { it.path }
                 .joinToString(separator = File.pathSeparator) { it }
 
-            arrayOf("--classpath", classpathString)
+            arrayOf(ARG_CLASS_PATH, classpathString)
         } else {
             emptyArray()
         }
@@ -348,7 +359,7 @@
         val mergeAnnotationsArgs = if (mergeXmlAnnotations != null) {
             val merged = File(project, "merged-annotations.xml")
             Files.asCharSink(merged, Charsets.UTF_8).write(mergeXmlAnnotations.trimIndent())
-            arrayOf("--merge-annotations", merged.path)
+            arrayOf(ARG_MERGE_ANNOTATIONS, merged.path)
         } else {
             emptyArray()
         }
@@ -356,7 +367,7 @@
         val signatureAnnotationsArgs = if (mergeSignatureAnnotations != null) {
             val merged = File(project, "merged-annotations.txt")
             Files.asCharSink(merged, Charsets.UTF_8).write(mergeSignatureAnnotations.trimIndent())
-            arrayOf("--merge-annotations", merged.path)
+            arrayOf(ARG_MERGE_ANNOTATIONS, merged.path)
         } else {
             emptyArray()
         }
@@ -364,7 +375,7 @@
         val javaStubAnnotationsArgs = if (mergeJavaStubAnnotations != null) {
             val merged = File(project, "merged-annotations.java")
             Files.asCharSink(merged, Charsets.UTF_8).write(mergeJavaStubAnnotations.trimIndent())
-            arrayOf("--merge-annotations", merged.path)
+            arrayOf(ARG_MERGE_ANNOTATIONS, merged.path)
         } else {
             emptyArray()
         }
@@ -437,44 +448,44 @@
         val manifestFileArgs = if (manifest != null) {
             val file = File(project, "manifest.xml")
             Files.asCharSink(file, Charsets.UTF_8).write(manifest.trimIndent())
-            arrayOf("--manifest", file.path)
+            arrayOf(ARG_MANIFEST, file.path)
         } else {
             emptyArray()
         }
 
         val migrateNullsArguments = if (migrateNullsApiFile != null) {
-            arrayOf("--migrate-nullness", migrateNullsApiFile.path)
+            arrayOf(ARG_MIGRATE_NULLNESS, migrateNullsApiFile.path)
         } else {
             emptyArray()
         }
 
         val checkCompatibilityArguments = if (checkCompatibilityApiFile != null) {
-            arrayOf("--check-compatibility:api:current", checkCompatibilityApiFile.path)
+            arrayOf(ARG_CHECK_COMPATIBILITY_API_CURRENT, checkCompatibilityApiFile.path)
         } else {
             emptyArray()
         }
 
         val checkCompatibilityApiReleasedArguments = if (checkCompatibilityApiReleasedFile != null) {
-            arrayOf("--check-compatibility:api:released", checkCompatibilityApiReleasedFile.path)
+            arrayOf(ARG_CHECK_COMPATIBILITY_API_RELEASED, checkCompatibilityApiReleasedFile.path)
         } else {
             emptyArray()
         }
 
         val checkCompatibilityRemovedCurrentArguments = if (checkCompatibilityRemovedApiCurrentFile != null) {
-            arrayOf("--check-compatibility:removed:current", checkCompatibilityRemovedApiCurrentFile.path)
+            arrayOf(ARG_CHECK_COMPATIBILITY_REMOVED_CURRENT, checkCompatibilityRemovedApiCurrentFile.path)
         } else {
             emptyArray()
         }
 
         val checkCompatibilityRemovedReleasedArguments = if (checkCompatibilityRemovedApiReleasedFile != null) {
-            arrayOf("--check-compatibility:removed:released", checkCompatibilityRemovedApiReleasedFile.path)
+            arrayOf(ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED, checkCompatibilityRemovedApiReleasedFile.path)
         } else {
             emptyArray()
         }
 
-        val quiet = if (expectedOutput != null && !extraArguments.contains("--verbose")) {
+        val quiet = if (expectedOutput != null && !extraArguments.contains(ARG_VERBOSE)) {
             // If comparing output, avoid noisy output such as the banner etc
-            arrayOf("--quiet")
+            arrayOf(ARG_QUIET)
         } else {
             emptyArray()
         }
@@ -490,7 +501,7 @@
                 val file = jar.createFile(root)
                 sb.append(file.path)
             }
-            arrayOf("--annotation-coverage-of", sb.toString())
+            arrayOf(ARG_ANNOTATION_COVERAGE_OF, sb.toString())
         } else {
             emptyArray()
         }
@@ -498,7 +509,7 @@
         var proguardFile: File? = null
         val proguardKeepArguments = if (proguard != null) {
             proguardFile = File(project, "proguard.cfg")
-            arrayOf("--proguard", proguardFile.path)
+            arrayOf(ARG_PROGUARD, proguardFile.path)
         } else {
             emptyArray()
         }
@@ -506,15 +517,15 @@
         val showAnnotationArguments = if (showAnnotations.isNotEmpty() || includeSystemApiAnnotations) {
             val args = mutableListOf<String>()
             for (annotation in showAnnotations) {
-                args.add("--show-annotation")
+                args.add(ARG_SHOW_ANNOTATION)
                 args.add(annotation)
             }
             if (includeSystemApiAnnotations && !args.contains("android.annotation.SystemApi")) {
-                args.add("--show-annotation")
+                args.add(ARG_SHOW_ANNOTATION)
                 args.add("android.annotation.SystemApi")
             }
             if (includeSystemApiAnnotations && !args.contains("android.annotation.TestApi")) {
-                args.add("--show-annotation")
+                args.add(ARG_SHOW_ANNOTATION)
                 args.add("android.annotation.TestApi")
             }
             args.toTypedArray()
@@ -524,14 +535,14 @@
 
         val showUnannotatedArgs =
             if (showUnannotated) {
-                arrayOf("--show-unannotated")
+                arrayOf(ARG_SHOW_UNANNOTATED)
             } else {
                 emptyArray()
             }
 
         val includeSourceRetentionAnnotationArgs =
             if (includeSourceRetentionAnnotations) {
-                arrayOf("--include-source-retention")
+                arrayOf(ARG_INCLUDE_SOURCE_RETENTION)
             } else {
                 emptyArray()
             }
@@ -539,7 +550,7 @@
         var removedApiFile: File? = null
         val removedArgs = if (removedApi != null) {
             removedApiFile = temporaryFolder.newFile("removed.txt")
-            arrayOf("--removed-api", removedApiFile.path)
+            arrayOf(ARG_REMOVED_API, removedApiFile.path)
         } else {
             emptyArray()
         }
@@ -547,7 +558,7 @@
         var removedDexApiFile: File? = null
         val removedDexArgs = if (removedDexApi != null) {
             removedDexApiFile = temporaryFolder.newFile("removed-dex.txt")
-            arrayOf("--removed-dex-api", removedDexApiFile.path)
+            arrayOf(ARG_REMOVED_DEX_API, removedDexApiFile.path)
         } else {
             emptyArray()
         }
@@ -555,7 +566,7 @@
         var apiFile: File? = null
         val apiArgs = if (api != null) {
             apiFile = temporaryFolder.newFile("public-api.txt")
-            arrayOf("--api", apiFile.path)
+            arrayOf(ARG_API, apiFile.path)
         } else {
             emptyArray()
         }
@@ -563,7 +574,7 @@
         var exactApiFile: File? = null
         val exactApiArgs = if (exactApi != null) {
             exactApiFile = temporaryFolder.newFile("exact-api.txt")
-            arrayOf("--exact-api", exactApiFile.path)
+            arrayOf(ARG_EXACT_API, exactApiFile.path)
         } else {
             emptyArray()
         }
@@ -571,7 +582,7 @@
         var apiXmlFile: File? = null
         val apiXmlArgs = if (apiXml != null) {
             apiXmlFile = temporaryFolder.newFile("public-api-xml.txt")
-            arrayOf("--api-xml", apiXmlFile.path)
+            arrayOf(ARG_XML_API, apiXmlFile.path)
         } else {
             emptyArray()
         }
@@ -579,7 +590,7 @@
         var privateApiFile: File? = null
         val privateApiArgs = if (privateApi != null) {
             privateApiFile = temporaryFolder.newFile("private.txt")
-            arrayOf("--private-api", privateApiFile.path)
+            arrayOf(ARG_PRIVATE_API, privateApiFile.path)
         } else {
             emptyArray()
         }
@@ -587,7 +598,7 @@
         var dexApiFile: File? = null
         val dexApiArgs = if (dexApi != null) {
             dexApiFile = temporaryFolder.newFile("public-dex.txt")
-            arrayOf("--dex-api", dexApiFile.path)
+            arrayOf(ARG_DEX_API, dexApiFile.path)
         } else {
             emptyArray()
         }
@@ -595,7 +606,7 @@
         var dexApiMappingFile: File? = null
         val dexApiMappingArgs = if (dexApiMapping != null) {
             dexApiMappingFile = temporaryFolder.newFile("api-mapping.txt")
-            arrayOf("--dex-api-mapping", dexApiMappingFile.path)
+            arrayOf(ARG_DEX_API_MAPPING, dexApiMappingFile.path)
         } else {
             emptyArray()
         }
@@ -603,7 +614,27 @@
         var privateDexApiFile: File? = null
         val privateDexApiArgs = if (privateDexApi != null) {
             privateDexApiFile = temporaryFolder.newFile("private-dex.txt")
-            arrayOf("--private-dex-api", privateDexApiFile.path)
+            arrayOf(ARG_PRIVATE_DEX_API, privateDexApiFile.path)
+        } else {
+            emptyArray()
+        }
+
+        val convertToJDiffFiles = mutableListOf<Pair<File, File>>()
+        val convertToJDiffArgs = if (convertToJDiff.isNotEmpty()) {
+            val args = mutableListOf<String>()
+            var index = 1
+            for ((signatures, xml) in convertToJDiff) {
+                val convertSig = temporaryFolder.newFile("jdiff-signatures$index.txt")
+                convertSig.writeText(signatures.trimIndent(), Charsets.UTF_8)
+                val output = temporaryFolder.newFile("jdiff-output$index.xml")
+                convertToJDiffFiles += Pair(convertSig, output)
+                index++
+
+                args += ARG_CONVERT_TO_JDIFF
+                args += convertSig.path
+                args += output.path
+            }
+            args.toTypedArray()
         } else {
             emptyArray()
         }
@@ -612,9 +643,9 @@
         val stubsArgs = if (stubs.isNotEmpty()) {
             stubsDir = temporaryFolder.newFolder("stubs")
             if (docStubs) {
-                arrayOf("--doc-stubs", stubsDir.path)
+                arrayOf(ARG_DOC_STUBS, stubsDir.path)
             } else {
-                arrayOf("--stubs", stubsDir.path)
+                arrayOf(ARG_STUBS, stubsDir.path)
             }
         } else {
             emptyArray()
@@ -623,7 +654,7 @@
         var stubsSourceListFile: File? = null
         val stubsSourceListArgs = if (stubsSourceList != null) {
             stubsSourceListFile = temporaryFolder.newFile("droiddoc-src-list")
-            arrayOf("--write-stubs-source-list", stubsSourceListFile.path)
+            arrayOf(ARG_STUBS_SOURCE_LIST, stubsSourceListFile.path)
         } else {
             emptyArray()
         }
@@ -633,7 +664,7 @@
             ApiLookup::class.java.getDeclaredMethod("dispose").apply { isAccessible = true }.invoke(null)
             applyApiLevelsXmlFile = temporaryFolder.newFile("api-versions.xml")
             Files.asCharSink(applyApiLevelsXmlFile!!, Charsets.UTF_8).write(applyApiLevelsXml.trimIndent())
-            arrayOf("--apply-api-levels", applyApiLevelsXmlFile.path)
+            arrayOf(ARG_APPLY_API_LEVELS, applyApiLevelsXmlFile.path)
         } else {
             emptyArray()
         }
@@ -655,7 +686,7 @@
             if (kotlinPath.isNotEmpty() &&
                 sourceList.asSequence().any { it.endsWith(DOT_KT) }
             ) {
-                arrayOf("--classpath", kotlinPath.joinToString(separator = File.pathSeparator) { it })
+                arrayOf(ARG_CLASS_PATH, kotlinPath.joinToString(separator = File.pathSeparator) { it })
             } else {
                 emptyArray()
             }
@@ -670,7 +701,7 @@
             sdk_widgets != null
         ) {
             val dir = File(project, "sdk-files")
-            sdkFilesArgs = arrayOf("--sdk-values", dir.path)
+            sdkFilesArgs = arrayOf(ARG_SDK_VALUES, dir.path)
             sdkFilesDir = dir
         } else {
             sdkFilesArgs = emptyArray()
@@ -685,7 +716,7 @@
                 Files.asCharSink(signatureFile, Charsets.UTF_8).write(signatures.trimIndent())
                 index++
 
-                args.add("--register-artifact")
+                args.add(ARG_REGISTER_ARTIFACT)
                 args.add(signatureFile.path)
                 args.add(artifactId)
             }
@@ -697,15 +728,15 @@
         val extractedAnnotationsZip: File?
         val extractAnnotationsArgs = if (extractAnnotations != null) {
             extractedAnnotationsZip = temporaryFolder.newFile("extracted-annotations.zip")
-            arrayOf("--extract-annotations", extractedAnnotationsZip.path)
+            arrayOf(ARG_EXTRACT_ANNOTATIONS, extractedAnnotationsZip.path)
         } else {
             extractedAnnotationsZip = null
             emptyArray()
         }
 
         val actualOutput = runDriver(
-            "--no-color",
-            "--no-banner",
+            ARG_NO_COLOR,
+            ARG_NO_BANNER,
 
             // Tell metalava where to store temp folder: place them under the
             // test root folder such that we clean up the output strings referencing
@@ -716,16 +747,16 @@
             // For the tests we want to treat references to APIs like java.io.Closeable
             // as a class that is part of the API surface, not as a hidden class as would
             // be the case when analyzing a complete API surface
-            // "--unhide-classpath-classes",
-            "--allow-referencing-unknown-classes",
+            // ARG_UNHIDE_CLASSPATH_CLASSES,
+            ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES,
 
             // Annotation generation temporarily turned off by default while integrating with
             // SDK builds; tests need these
-            "--include-annotations",
+            ARG_INCLUDE_ANNOTATIONS,
 
-            "--sourcepath",
+            ARG_SOURCE_PATH,
             sourcePath,
-            "--classpath",
+            ARG_CLASS_PATH,
             androidJar.path,
             *classpathArgs,
             *kotlinPathArgs,
@@ -740,11 +771,11 @@
             *dexApiMappingArgs,
             *stubsArgs,
             *stubsSourceListArgs,
-            "--compatible-output=${if (compatibilityMode) "yes" else "no"}",
-            "--output-kotlin-nulls=${if (outputKotlinStyleNulls) "yes" else "no"}",
-            "--input-kotlin-nulls=${if (inputKotlinStyleNulls) "yes" else "no"}",
-            "--omit-common-packages=${if (omitCommonPackages) "yes" else "no"}",
-            "--include-signature-version=${if (includeSignatureVersion) "yes" else "no"}",
+            "$ARGS_COMPAT_OUTPUT=${if (compatibilityMode) "yes" else "no"}",
+            "$ARG_OUTPUT_KOTLIN_NULLS=${if (outputKotlinStyleNulls) "yes" else "no"}",
+            "$ARG_INPUT_KOTLIN_NULLS=${if (inputKotlinStyleNulls) "yes" else "no"}",
+            "$ARG_OMIT_COMMON_PACKAGES=${if (omitCommonPackages) "yes" else "no"}",
+            "$ARG_INCLUDE_SIG_VERSION=${if (includeSignatureVersion) "yes" else "no"}",
             *coverageStats,
             *quiet,
             *mergeAnnotationsArgs,
@@ -757,6 +788,7 @@
             *checkCompatibilityRemovedReleasedArguments,
             *proguardKeepArguments,
             *manifestFileArgs,
+            *convertToJDiffArgs,
             *applyApiLevelsXmlArgs,
             *showAnnotationArguments,
             *showUnannotatedArgs,
@@ -777,27 +809,41 @@
 
         if (api != null && apiFile != null) {
             assertTrue("${apiFile.path} does not exist even though --api was used", apiFile.exists())
-            val expectedText = readFile(apiFile, stripBlankLines, trim)
-            assertEquals(stripComments(api, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(apiFile, stripBlankLines, trim)
+            assertEquals(stripComments(api, stripLineComments = false).trimIndent(), actualText)
             // Make sure we can read back the files we write
             ApiFile.parseApi(apiFile, options.outputKotlinStyleNulls, true)
         }
 
         if (apiXml != null && apiXmlFile != null) {
-            assertTrue("${apiXmlFile.path} does not exist even though --api-xml was used", apiXmlFile.exists())
-            val expectedText = readFile(apiXmlFile, stripBlankLines, trim)
-            assertEquals(stripComments(apiXml, stripLineComments = false).trimIndent(), expectedText)
+            assertTrue("${apiXmlFile.path} does not exist even though $ARG_XML_API was used",
+                apiXmlFile.exists())
+            val actualText = readFile(apiXmlFile, stripBlankLines, trim)
+            assertEquals(stripComments(apiXml, stripLineComments = false).trimIndent(), actualText)
             // Make sure we can read back the files we write
             XmlUtils.parseDocument(apiXmlFile.readText(Charsets.UTF_8), false)
         }
 
+        if (convertToJDiffFiles.isNotEmpty()) {
+            for (i in 0 until convertToJDiff.size) {
+                val expected = convertToJDiff[i].second
+                val converted = convertToJDiffFiles[i].second
+                assertTrue("${converted.path} does not exist even though $ARG_CONVERT_TO_JDIFF was used",
+                    converted.exists())
+                val actualText = readFile(converted, stripBlankLines, trim)
+                assertEquals(stripComments(expected, stripLineComments = false).trimIndent(), actualText)
+                // Make sure we can read back the files we write
+                XmlUtils.parseDocument(converted.readText(Charsets.UTF_8), false)
+            }
+        }
+
         if (removedApi != null && removedApiFile != null) {
             assertTrue(
                 "${removedApiFile.path} does not exist even though --removed-api was used",
                 removedApiFile.exists()
             )
-            val expectedText = readFile(removedApiFile, stripBlankLines, trim)
-            assertEquals(stripComments(removedApi, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(removedApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(removedApi, stripLineComments = false).trimIndent(), actualText)
             // Make sure we can read back the files we write
             ApiFile.parseApi(removedApiFile, options.outputKotlinStyleNulls, true)
         }
@@ -807,8 +853,8 @@
                 "${removedDexApiFile.path} does not exist even though --removed-dex-api was used",
                 removedDexApiFile.exists()
             )
-            val expectedText = readFile(removedDexApiFile, stripBlankLines, trim)
-            assertEquals(stripComments(removedDexApi, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(removedDexApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(removedDexApi, stripLineComments = false).trimIndent(), actualText)
         }
 
         if (exactApi != null && exactApiFile != null) {
@@ -816,8 +862,8 @@
                 "${exactApiFile.path} does not exist even though --exact-api was used",
                 exactApiFile.exists()
             )
-            val expectedText = readFile(exactApiFile, stripBlankLines, trim)
-            assertEquals(stripComments(exactApi, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(exactApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(exactApi, stripLineComments = false).trimIndent(), actualText)
             // Make sure we can read back the files we write
             ApiFile.parseApi(exactApiFile, options.outputKotlinStyleNulls, true)
         }
@@ -827,8 +873,8 @@
                 "${privateApiFile.path} does not exist even though --private-api was used",
                 privateApiFile.exists()
             )
-            val expectedText = readFile(privateApiFile, stripBlankLines, trim)
-            assertEquals(stripComments(privateApi, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(privateApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(privateApi, stripLineComments = false).trimIndent(), actualText)
             // Make sure we can read back the files we write
             ApiFile.parseApi(privateApiFile, options.outputKotlinStyleNulls, true)
         }
@@ -838,8 +884,8 @@
                 "${dexApiFile.path} does not exist even though --dex-api was used",
                 dexApiFile.exists()
             )
-            val expectedText = readFile(dexApiFile, stripBlankLines, trim)
-            assertEquals(stripComments(dexApi, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(dexApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(dexApi, stripLineComments = false).trimIndent(), actualText)
         }
 
         if (privateDexApi != null && privateDexApiFile != null) {
@@ -847,8 +893,8 @@
                 "${privateDexApiFile.path} does not exist even though --private-dex-api was used",
                 privateDexApiFile.exists()
             )
-            val expectedText = readFile(privateDexApiFile, stripBlankLines, trim)
-            assertEquals(stripComments(privateDexApi, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(privateDexApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(privateDexApi, stripLineComments = false).trimIndent(), actualText)
         }
 
         if (dexApiMapping != null && dexApiMappingFile != null) {
@@ -856,8 +902,8 @@
                 "${dexApiMappingFile.path} does not exist even though --dex-api-maping was used",
                 dexApiMappingFile.exists()
             )
-            val expectedText = readFile(dexApiMappingFile, stripBlankLines, trim)
-            assertEquals(stripComments(dexApiMapping, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(dexApiMappingFile, stripBlankLines, trim)
+            assertEquals(stripComments(dexApiMapping, stripLineComments = false).trimIndent(), actualText)
         }
 
         if (proguard != null && proguardFile != null) {
@@ -953,8 +999,8 @@
                         )
                     }
                 }
-                val expectedText = readFile(stubFile, stripBlankLines, trim)
-                assertEquals(stub, expectedText)
+                val actualText = readFile(stubFile, stripBlankLines, trim)
+                assertEquals(stub, actualText)
             }
         }
 
@@ -963,8 +1009,8 @@
                 "${stubsSourceListFile.path} does not exist even though --write-stubs-source-list was used",
                 stubsSourceListFile.exists()
             )
-            val expectedText = readFile(stubsSourceListFile, stripBlankLines, trim)
-            assertEquals(stripComments(stubsSourceList, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(stubsSourceListFile, stripBlankLines, trim)
+            assertEquals(stripComments(stubsSourceList, stripLineComments = false).trimIndent(), actualText)
         }
 
         if (checkCompilation && stubsDir != null && CHECK_STUB_COMPILATION) {
@@ -1318,13 +1364,13 @@
         // separately on each test; slower but reliable.
 
         val doclavaArg = when (argument) {
-            "--api" -> "-api"
-            "--removed-api" -> "-removedApi"
+            ARG_API -> "-api"
+            ARG_REMOVED_API -> "-removedApi"
             else -> if (argument.startsWith("--")) argument.substring(1) else argument
         }
 
         val showAnnotationArgsDoclava1: Array<String> = if (showAnnotationArgs.isNotEmpty()) {
-            showAnnotationArgs.map { if (it == "--show-annotation") "-showAnnotation" else it }.toTypedArray()
+            showAnnotationArgs.map { if (it == ARG_SHOW_ANNOTATION) "-showAnnotation" else it }.toTypedArray()
         } else {
             emptyArray()
         }
diff --git a/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt b/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt
index 3f6597e..44649e5 100644
--- a/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt
+++ b/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt
@@ -474,4 +474,70 @@
             """
         )
     }
+
+    @Test
+    fun `Test conversion flag`() {
+        check(
+            compatibilityMode = true,
+            convertToJDiff = listOf(
+                Pair(
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                      }
+                    }
+                    """,
+                    """
+                    <api>
+                    <package name="test.pkg"
+                    >
+                    <class name="MyTest1"
+                     extends="java.lang.Object"
+                     abstract="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <constructor name="MyTest1"
+                     type="test.pkg.MyTest1"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    </constructor>
+                    </class>
+                    </package>
+                    </api>
+                    """
+                ),
+                Pair(
+                    """
+                    package test.pkg {
+                      public class MyTest2 {
+                      }
+                    }
+                    """,
+                    """
+                    <api>
+                    <package name="test.pkg"
+                    >
+                    <class name="MyTest2"
+                     extends="java.lang.Object"
+                     abstract="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    </class>
+                    </package>
+                    </api>
+                    """
+                )
+            )
+        )
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt b/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt
index e5233fa..4b218f9 100644
--- a/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt
+++ b/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt
@@ -48,7 +48,7 @@
                   }
                 }
                 """,
-            extraArguments = arrayOf("--java-source", "1.9")
+            extraArguments = arrayOf(ARG_JAVA_SOURCE, "1.9")
         )
     }
 
@@ -143,7 +143,7 @@
                   }
                 }
                 """,
-            extraArguments = arrayOf("--java-source", "1.9")
+            extraArguments = arrayOf(ARG_JAVA_SOURCE, "1.9")
         )
     }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/KeepFileTest.kt b/src/test/java/com/android/tools/metalava/KeepFileTest.kt
index 442b2c1..960d825 100644
--- a/src/test/java/com/android/tools/metalava/KeepFileTest.kt
+++ b/src/test/java/com/android/tools/metalava/KeepFileTest.kt
@@ -79,7 +79,7 @@
                     <init>();
                 }
                 """,
-            extraArguments = arrayOf("--hide", "KotlinKeyword")
+            extraArguments = arrayOf(ARG_HIDE, "KotlinKeyword")
         )
     }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt b/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt
index 2360fb2..4e32d1d 100644
--- a/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt
+++ b/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt
@@ -22,7 +22,7 @@
     @Test
     fun `Hard Kotlin keywords`() {
         check(
-            extraArguments = arrayOf("--check-kotlin-interop"),
+            extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
             warnings = """
                 src/test/pkg/Test.java:5: warning: Avoid method names that are Kotlin hard keywords ("fun"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords [KotlinKeyword:141]
                 src/test/pkg/Test.java:6: warning: Avoid parameter names that are Kotlin hard keywords ("typealias"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords [KotlinKeyword:141]
@@ -49,7 +49,7 @@
     @Test
     fun `Sam-compatible parameters should be last`() {
         check(
-            extraArguments = arrayOf("--check-kotlin-interop"),
+            extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
             warnings = """
                 src/test/pkg/Test.java:10: warning: SAM-compatible parameters (such as parameter 1, "run", in test.pkg.Test.error) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions [SamShouldBeLast:142]
                 src/test/pkg/test.kt:7: warning: lambda parameters (such as parameter 1, "bar", in test.pkg.TestKt.error) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions [SamShouldBeLast:142]
@@ -88,7 +88,7 @@
     @Test
     fun `Companion object methods should be marked with JvmStatic`() {
         check(
-            extraArguments = arrayOf("--check-kotlin-interop"),
+            extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
             warnings = """
                 src/test/pkg/Foo.kt:7: warning: Companion object constants like INTEGER_ONE should be marked @JvmField for Java interoperability; see https://android.github.io/kotlin-guides/interop.html#companion-constants [MissingJvmstatic:143]
                 src/test/pkg/Foo.kt:13: warning: Companion object methods like missing should be marked @JvmStatic for Java interoperability; see https://android.github.io/kotlin-guides/interop.html#companion-functions [MissingJvmstatic:143]
@@ -123,7 +123,7 @@
     @Test
     fun `Methods with default parameters should specify JvmOverloads`() {
         check(
-            extraArguments = arrayOf("--check-kotlin-interop"),
+            extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
             warnings = """
                 src/test/pkg/Foo.kt:8: warning: A Kotlin method with default parameter values should be annotated with @JvmOverloads for better Java interoperability; see https://android.github.io/kotlin-guides/interop.html#function-overloads-for-defaults [MissingJvmstatic:143]
                 """,
@@ -150,7 +150,7 @@
     @Test
     fun `Methods which throw exceptions should document them`() {
         check(
-            extraArguments = arrayOf("--check-kotlin-interop"),
+            extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
             warnings = """
                 src/test/pkg/Foo.kt:6: error: Method Foo.error_throws_multiple_times appears to be throwing java.io.FileNotFoundException; this should be recorded with a @Throws annotation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions [DocumentExceptions:145]
                 src/test/pkg/Foo.kt:16: error: Method Foo.error_throwsCheckedExceptionWithWrongExceptionClassInThrows appears to be throwing java.io.FileNotFoundException; this should be recorded with a @Throws annotation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions [DocumentExceptions:145]
diff --git a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
index 36cac2d..cdc8f25 100644
--- a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
+++ b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
@@ -273,7 +273,7 @@
                 androidxNonNullSource,
                 androidxNullableSource
             ),
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             api = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
                 """
                 package test.pkg {
@@ -323,7 +323,7 @@
                 androidxNonNullSource,
                 androidxNullableSource
             ),
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             api = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
                 """
                 package test.pkg {
@@ -371,7 +371,7 @@
                 androidxNonNullSource,
                 androidxNullableSource
             ),
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             // TODO: Handle multiple nullness annotations
             migrateNullsApi =
             """
@@ -439,7 +439,7 @@
                 androidxNonNullSource,
                 androidxNullableSource
             ),
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             // TODO: Handle multiple nullness annotations
             migrateNullsApi =
             """
@@ -517,7 +517,7 @@
                   }
                 }
             """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             stubs = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
                 arrayOf(
                     """
diff --git a/src/test/java/com/android/tools/metalava/OptionsTest.kt b/src/test/java/com/android/tools/metalava/OptionsTest.kt
index cb5f34c..34f4761 100644
--- a/src/test/java/com/android/tools/metalava/OptionsTest.kt
+++ b/src/test/java/com/android/tools/metalava/OptionsTest.kt
@@ -87,8 +87,6 @@
 
 Extracting Signature Files:
 --api <file>                             Generate a signature descriptor file
---api-xml <file>                         Like --api, but emits the API in the JDiff XML
-                                         format instead
 --private-api <file>                     Generate a signature descriptor file listing the
                                          exact private APIs
 --dex-api <file>                         Generate a DEX signature descriptor file listing
@@ -176,6 +174,13 @@
                                          lint-severity
 --hide <id>                              Hide/skip issues of the given id
 
+JDiff:
+--api-xml <file>                         Like --api, but emits the API in the JDiff XML
+                                         format instead
+--convert-to-jdiff <sig> <xml>           Reads in the given signature file, and writes it
+                                         out in the JDiff XML format. Can be specified
+                                         multiple times.
+
 Statistics:
 --annotation-coverage-stats              Whether metalava should emit coverage statistics
                                          for annotations, listing the percentage of the
@@ -245,7 +250,7 @@
 
     @Test
     fun `Test invalid arguments`() {
-        val args = listOf("--no-color", "--blah-blah-blah")
+        val args = listOf(ARG_NO_COLOR, "--blah-blah-blah")
 
         val stdout = StringWriter()
         val stderr = StringWriter()
@@ -268,7 +273,7 @@
 
     @Test
     fun `Test help`() {
-        val args = listOf("--no-color", "--help")
+        val args = listOf(ARG_NO_COLOR, "--help")
 
         val stdout = StringWriter()
         val stderr = StringWriter()
diff --git a/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt
index dc9b24a..b8c2a41 100644
--- a/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt
+++ b/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt
@@ -34,10 +34,10 @@
         assertTrue(source.path, source.isDirectory)
         val target = temporaryFolder.newFolder()
         runDriver(
-            "--no-color",
-            "--no-banner",
+            ARG_NO_COLOR,
+            ARG_NO_BANNER,
 
-            "--copy-annotations",
+            ARG_COPY_ANNOTATIONS,
             source.path,
             target.path
         )
@@ -102,10 +102,10 @@
         bytecode.createFile(compiledStubs)
 
         runDriver(
-            "--no-color",
-            "--no-banner",
+            ARG_NO_COLOR,
+            ARG_NO_BANNER,
 
-            "--rewrite-annotations",
+            ARG_REWRITE_ANNOTATIONS,
             compiledStubs.path
         )
 
@@ -139,10 +139,10 @@
         val jarFile = jarDesc.createFile(temporaryFolder.root)
 
         runDriver(
-            "--no-color",
-            "--no-banner",
+            ARG_NO_COLOR,
+            ARG_NO_BANNER,
 
-            "--rewrite-annotations",
+            ARG_REWRITE_ANNOTATIONS,
             jarFile.path
         )
 
diff --git a/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
index 9bc5daf..4ef3a20 100644
--- a/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
+++ b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
@@ -47,9 +47,9 @@
             ),
 
             extraArguments = arrayOf(
-                "--error", "UnhiddenSystemApi",
-                "--hide-package", "android.annotation",
-                "--hide-package", "android.support.annotation"
+                ARG_ERROR, "UnhiddenSystemApi",
+                ARG_HIDE_PACKAGE, "android.annotation",
+                ARG_HIDE_PACKAGE, "android.support.annotation"
             ),
 
             api = """
@@ -106,9 +106,9 @@
             ),
 
             extraArguments = arrayOf(
-                "--error", "UnhiddenSystemApi",
-                "--hide-package", "android.annotation",
-                "--hide-package", "android.support.annotation"
+                ARG_ERROR, "UnhiddenSystemApi",
+                ARG_HIDE_PACKAGE, "android.annotation",
+                ARG_HIDE_PACKAGE, "android.support.annotation"
             ),
 
             api = """
@@ -167,9 +167,9 @@
             ),
 
             extraArguments = arrayOf(
-                "--show-annotation", "android.annotation.TestApi",
-                "--hide-package", "android.annotation",
-                "--hide-package", "android.support.annotation"
+                ARG_SHOW_ANNOTATION, "android.annotation.TestApi",
+                ARG_HIDE_PACKAGE, "android.annotation",
+                ARG_HIDE_PACKAGE, "android.support.annotation"
             ),
 
             api = """
@@ -249,9 +249,9 @@
                 """,
             includeSystemApiAnnotations = true,
             extraArguments = arrayOf(
-                "--show-annotation", "android.annotation.TestApi",
-                "--hide-package", "android.annotation",
-                "--hide-package", "android.support.annotation"
+                ARG_SHOW_ANNOTATION, "android.annotation.TestApi",
+                ARG_HIDE_PACKAGE, "android.annotation",
+                ARG_HIDE_PACKAGE, "android.support.annotation"
             )
         )
     }
diff --git a/src/test/java/com/android/tools/metalava/StubsTest.kt b/src/test/java/com/android/tools/metalava/StubsTest.kt
index ac0e50d..25dca98 100644
--- a/src/test/java/com/android/tools/metalava/StubsTest.kt
+++ b/src/test/java/com/android/tools/metalava/StubsTest.kt
@@ -3332,7 +3332,7 @@
                 @androidx.annotation.Nullable
                 package test.pkg;
                 """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation")
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
         )
     }
 
@@ -3410,14 +3410,14 @@
                 }
                 """
             ),
-            extraArguments = arrayOf("--hide-package", "androidx.annotation")
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
         )
     }
 
     @Test
     fun `Ensure we emit both deprecated javadoc and annotation with exclude-annotations`() {
         check(
-            extraArguments = arrayOf("--exclude-annotations"),
+            extraArguments = arrayOf(ARG_EXCLUDE_ANNOTATIONS),
             compatibilityMode = false,
             sourceFiles = *arrayOf(
                 java(
@@ -3455,7 +3455,7 @@
     @Test
     fun `Ensure we emit runtime and deprecated annotations in stubs with exclude-annotations`() {
         check(
-            extraArguments = arrayOf("--exclude-annotations"),
+            extraArguments = arrayOf(ARG_EXCLUDE_ANNOTATIONS),
             compatibilityMode = false,
             sourceFiles = *arrayOf(
                 java(
@@ -3592,8 +3592,8 @@
     fun `Test update-api should not generate stubs`() {
         check(
             extraArguments = arrayOf(
-                "--update-api",
-                "--exclude-annotations"
+                ARG_UPDATE_API,
+                ARG_EXCLUDE_ANNOTATIONS
             ),
             compatibilityMode = false,
             sourceFiles = *arrayOf(
diff --git a/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt b/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt
index 8ea2750..c62331d 100644
--- a/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt
+++ b/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.tools.metalava.apilevels
 
+import com.android.tools.metalava.ARG_ANDROID_JAR_PATTERN
+import com.android.tools.metalava.ARG_GENERATE_API_LEVELS
 import com.android.tools.metalava.DriverTest
 import com.android.utils.XmlUtils
 import com.google.common.truth.Truth
@@ -46,11 +48,11 @@
 
         check(
             extraArguments = arrayOf(
-                "--generate-api-levels",
+                ARG_GENERATE_API_LEVELS,
                 outputPath,
-                "--android-jar-pattern",
+                ARG_ANDROID_JAR_PATTERN,
                 "${oldSdkJars.path}/android-%/android.jar",
-                "--android-jar-pattern",
+                ARG_ANDROID_JAR_PATTERN,
                 "${platformJars.path}/%/public/android.jar"
             ),
             checkDoclava1 = false,