Merge "Fix: recovery module is disabled on 32-bit targets"
diff --git a/README.md b/README.md
index 3549b02..9f427c4 100644
--- a/README.md
+++ b/README.md
@@ -217,6 +217,18 @@
 or [external/llvm/soong/llvm.go](https://android.googlesource.com/platform/external/llvm/+/master/soong/llvm.go)
 for examples of more complex conditionals on product variables or environment variables.
 
+## Developing for Soong
+
+To load Soong code in a Go-aware IDE, create a directory outside your android tree and then:
+```bash
+apt install bindfs
+export GOPATH=<path to the directory you created>
+build/soong/scripts/setup_go_workspace_for_soong.sh
+```
+
+This will bind mount the Soong source directories into the directory in the layout expected by
+the IDE.
+
 ## Contact
 
 Email android-building@googlegroups.com (external) for any questions, or see
diff --git a/androidmk/cmd/androidmk/android.go b/androidmk/cmd/androidmk/android.go
index ded9efa..e3eb82a 100644
--- a/androidmk/cmd/androidmk/android.go
+++ b/androidmk/cmd/androidmk/android.go
@@ -85,6 +85,7 @@
 			"LOCAL_MULTILIB":                "compile_multilib",
 			"LOCAL_ARM_MODE_HACK":           "instruction_set",
 			"LOCAL_SDK_VERSION":             "sdk_version",
+			"LOCAL_MIN_SDK_VERSION":         "min_sdk_version",
 			"LOCAL_NDK_STL_VARIANT":         "stl",
 			"LOCAL_JAR_MANIFEST":            "manifest",
 			"LOCAL_JARJAR_RULES":            "jarjar_rules",
diff --git a/cmd/pom2bp/pom2bp.go b/cmd/pom2bp/pom2bp.go
index 078a07d..7b06035 100644
--- a/cmd/pom2bp/pom2bp.go
+++ b/cmd/pom2bp/pom2bp.go
@@ -15,6 +15,7 @@
 package main
 
 import (
+	"archive/zip"
 	"bufio"
 	"bytes"
 	"encoding/xml"
@@ -138,9 +139,10 @@
 type Pom struct {
 	XMLName xml.Name `xml:"http://maven.apache.org/POM/4.0.0 project"`
 
-	PomFile      string `xml:"-"`
-	ArtifactFile string `xml:"-"`
-	BpTarget     string `xml:"-"`
+	PomFile       string `xml:"-"`
+	ArtifactFile  string `xml:"-"`
+	BpTarget      string `xml:"-"`
+	MinSdkVersion string `xml:"-"`
 
 	GroupId    string `xml:"groupId"`
 	ArtifactId string `xml:"artifactId"`
@@ -215,11 +217,61 @@
 	}
 }
 
+// ExtractMinSdkVersion extracts the minSdkVersion from the AndroidManifest.xml file inside an aar file, or sets it
+// to "current" if it is not present.
+func (p *Pom) ExtractMinSdkVersion() error {
+	aar, err := zip.OpenReader(p.ArtifactFile)
+	if err != nil {
+		return err
+	}
+	defer aar.Close()
+
+	var manifest *zip.File
+	for _, f := range aar.File {
+		if f.Name == "AndroidManifest.xml" {
+			manifest = f
+			break
+		}
+	}
+
+	if manifest == nil {
+		return fmt.Errorf("failed to find AndroidManifest.xml in %s", p.ArtifactFile)
+	}
+
+	r, err := manifest.Open()
+	if err != nil {
+		return err
+	}
+	defer r.Close()
+
+	decoder := xml.NewDecoder(r)
+
+	manifestData := struct {
+		XMLName  xml.Name `xml:"manifest"`
+		Uses_sdk struct {
+			MinSdkVersion string `xml:"http://schemas.android.com/apk/res/android minSdkVersion,attr"`
+		} `xml:"uses-sdk"`
+	}{}
+
+	err = decoder.Decode(&manifestData)
+	if err != nil {
+		return err
+	}
+
+	p.MinSdkVersion = manifestData.Uses_sdk.MinSdkVersion
+	if p.MinSdkVersion == "" {
+		p.MinSdkVersion = "current"
+	}
+
+	return nil
+}
+
 var bpTemplate = template.Must(template.New("bp").Parse(`
 {{if .IsAar}}android_library_import{{else}}java_import{{end}} {
     name: "{{.BpName}}-nodeps",
     {{if .IsAar}}aars{{else}}jars{{end}}: ["{{.ArtifactFile}}"],
     sdk_version: "{{.SdkVersion}}",{{if .IsAar}}
+    min_sdk_version: "{{.MinSdkVersion}}",
     static_libs: [{{range .BpAarDeps}}
         "{{.}}",{{end}}{{range .BpExtraDeps}}
         "{{.}}",{{end}}
@@ -229,7 +281,8 @@
 {{if .IsAar}}android_library{{else}}java_library_static{{end}} {
     name: "{{.BpName}}",
     sdk_version: "{{.SdkVersion}}",{{if .IsAar}}
-    manifest: "manifests/{{.BpName}}/AndroidManifest.xml",{{end}}
+    min_sdk_version: "{{.MinSdkVersion}}",{{end}}
+    manifest: "manifests/{{.BpName}}/AndroidManifest.xml",
     static_libs: [
         "{{.BpName}}-nodeps",{{range .BpJarDeps}}
         "{{.}}",{{end}}{{range .BpAarDeps}}
@@ -302,7 +355,7 @@
 
 	// Append all current command line args except -regen <file> to the ones from the file
 	for i := 1; i < len(os.Args); i++ {
-		if os.Args[i] == "-regen" {
+		if os.Args[i] == "-regen" || os.Args[i] == "--regen" {
 			i++
 		} else {
 			args = append(args, os.Args[i])
@@ -468,6 +521,13 @@
 	}
 
 	for _, pom := range poms {
+		if pom.IsAar() {
+			err := pom.ExtractMinSdkVersion()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, "Error reading manifest for %s: %s", pom.ArtifactFile, err)
+				os.Exit(1)
+			}
+		}
 		pom.FixDeps(modules)
 	}
 
diff --git a/java/aar.go b/java/aar.go
index da353e0..506f39f 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -74,7 +74,7 @@
 	return a.exportPackage
 }
 
-func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkVersion string) (flags []string, deps android.Paths,
+func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext sdkContext) (flags []string, deps android.Paths,
 	resDirs, overlayDirs []globbedResourceDir, overlayFiles, rroDirs android.Paths, manifestPath android.Path) {
 
 	hasVersionCode := false
@@ -125,20 +125,17 @@
 	linkFlags = append(linkFlags, android.JoinWithPrefix(assetDirs.Strings(), "-A "))
 	linkDeps = append(linkDeps, assetFiles...)
 
-	transitiveStaticLibs, libDeps, libFlags := aaptLibs(ctx, sdkVersion)
+	transitiveStaticLibs, libDeps, libFlags := aaptLibs(ctx, sdkContext)
 
 	overlayFiles = append(overlayFiles, transitiveStaticLibs...)
 	linkDeps = append(linkDeps, libDeps...)
 	linkFlags = append(linkFlags, libFlags...)
 
 	// SDK version flags
-	switch sdkVersion {
-	case "", "current", "system_current", "test_current":
-		sdkVersion = proptools.NinjaEscape([]string{ctx.Config().DefaultAppTargetSdk()})[0]
-	}
+	minSdkVersion := sdkVersionOrDefault(ctx, sdkContext.minSdkVersion())
 
-	linkFlags = append(linkFlags, "--min-sdk-version "+sdkVersion)
-	linkFlags = append(linkFlags, "--target-sdk-version "+sdkVersion)
+	linkFlags = append(linkFlags, "--min-sdk-version "+minSdkVersion)
+	linkFlags = append(linkFlags, "--target-sdk-version "+minSdkVersion)
 
 	// Version code
 	if !hasVersionCode {
@@ -162,17 +159,17 @@
 	return linkFlags, linkDeps, resDirs, overlayDirs, overlayFiles, rroDirs, manifestPath
 }
 
-func (a *aapt) deps(ctx android.BottomUpMutatorContext, sdkVersion string) {
+func (a *aapt) deps(ctx android.BottomUpMutatorContext, sdkContext sdkContext) {
 	if !ctx.Config().UnbundledBuild() {
-		sdkDep := decodeSdkDep(ctx, sdkVersion)
+		sdkDep := decodeSdkDep(ctx, sdkContext)
 		if sdkDep.frameworkResModule != "" {
 			ctx.AddDependency(ctx.Module(), frameworkResTag, sdkDep.frameworkResModule)
 		}
 	}
 }
 
-func (a *aapt) buildActions(ctx android.ModuleContext, sdkVersion string, extraLinkFlags ...string) {
-	linkFlags, linkDeps, resDirs, overlayDirs, overlayFiles, rroDirs, manifestPath := a.aapt2Flags(ctx, sdkVersion)
+func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, extraLinkFlags ...string) {
+	linkFlags, linkDeps, resDirs, overlayDirs, overlayFiles, rroDirs, manifestPath := a.aapt2Flags(ctx, sdkContext)
 
 	linkFlags = append(linkFlags, extraLinkFlags...)
 
@@ -206,12 +203,12 @@
 }
 
 // aaptLibs collects libraries from dependencies and sdk_version and converts them into paths
-func aaptLibs(ctx android.ModuleContext, sdkVersion string) (transitiveStaticLibs, deps android.Paths,
+func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStaticLibs, deps android.Paths,
 	flags []string) {
 
 	var sharedLibs android.Paths
 
-	sdkDep := decodeSdkDep(ctx, sdkVersion)
+	sdkDep := decodeSdkDep(ctx, sdkContext)
 	if sdkDep.useFiles {
 		sharedLibs = append(sharedLibs, sdkDep.jars...)
 	}
@@ -277,12 +274,12 @@
 func (a *AndroidLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
 	a.Module.deps(ctx)
 	if !Bool(a.properties.No_framework_libs) && !Bool(a.properties.No_standard_libs) {
-		a.aapt.deps(ctx, String(a.deviceProperties.Sdk_version))
+		a.aapt.deps(ctx, sdkContext(a))
 	}
 }
 
 func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	a.aapt.buildActions(ctx, String(a.deviceProperties.Sdk_version), "--static-lib")
+	a.aapt.buildActions(ctx, sdkContext(a), "--static-lib")
 
 	ctx.CheckbuildFile(a.proguardOptionsFile)
 	ctx.CheckbuildFile(a.exportPackage)
@@ -359,6 +356,14 @@
 	exportedStaticPackages android.Paths
 }
 
+func (a *AARImport) sdkVersion() string {
+	return String(a.properties.Sdk_version)
+}
+
+func (a *AARImport) minSdkVersion() string {
+	return a.sdkVersion()
+}
+
 var _ AndroidLibraryDependency = (*AARImport)(nil)
 
 func (a *AARImport) ExportPackage() android.Path {
@@ -383,7 +388,7 @@
 
 func (a *AARImport) DepsMutator(ctx android.BottomUpMutatorContext) {
 	if !ctx.Config().UnbundledBuild() {
-		sdkDep := decodeSdkDep(ctx, String(a.properties.Sdk_version))
+		sdkDep := decodeSdkDep(ctx, sdkContext(a))
 		if sdkDep.useModule && sdkDep.frameworkResModule != "" {
 			ctx.AddDependency(ctx.Module(), frameworkResTag, sdkDep.frameworkResModule)
 		}
@@ -450,7 +455,7 @@
 	linkFlags = append(linkFlags, "--manifest "+a.manifest.String())
 	linkDeps = append(linkDeps, a.manifest)
 
-	transitiveStaticLibs, libDeps, libFlags := aaptLibs(ctx, String(a.properties.Sdk_version))
+	transitiveStaticLibs, libDeps, libFlags := aaptLibs(ctx, sdkContext(a))
 
 	linkDeps = append(linkDeps, libDeps...)
 	linkFlags = append(linkFlags, libFlags...)
diff --git a/java/androidmk.go b/java/androidmk.go
index d6095ae..79cf317 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -56,7 +56,7 @@
 						fmt.Fprintln(w, "LOCAL_DEX_PREOPT_PROFILE_CLASS_LISTING := $(LOCAL_PATH)/"+*library.deviceProperties.Dex_preopt.Profile)
 					}
 				}
-				fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", String(library.deviceProperties.Sdk_version))
+				fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", library.sdkVersion())
 				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", library.headerJarFile.String())
 
 				if library.jacocoReportClassesFile != nil {
@@ -121,7 +121,7 @@
 			func(w io.Writer, outputFile android.Path) {
 				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := ", !Bool(prebuilt.properties.Installable))
 				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", prebuilt.combinedClasspathFile.String())
-				fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", String(prebuilt.properties.Sdk_version))
+				fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", prebuilt.sdkVersion())
 			},
 		},
 	}
@@ -141,7 +141,7 @@
 				fmt.Fprintln(w, "LOCAL_SOONG_EXPORT_PROGUARD_FLAGS :=", prebuilt.proguardFlags.String())
 				fmt.Fprintln(w, "LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES :=", prebuilt.extraAaptPackagesFile.String())
 				fmt.Fprintln(w, "LOCAL_FULL_MANIFEST_FILE :=", prebuilt.manifest.String())
-				fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", String(prebuilt.properties.Sdk_version))
+				fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", prebuilt.sdkVersion())
 			},
 		},
 	}
diff --git a/java/app.go b/java/app.go
index 37109b5..f4de419 100644
--- a/java/app.go
+++ b/java/app.go
@@ -81,7 +81,7 @@
 func (a *AndroidApp) DepsMutator(ctx android.BottomUpMutatorContext) {
 	a.Module.deps(ctx)
 	if !Bool(a.properties.No_framework_libs) && !Bool(a.properties.No_standard_libs) {
-		a.aapt.deps(ctx, String(a.deviceProperties.Sdk_version))
+		a.aapt.deps(ctx, sdkContext(a))
 	}
 }
 
@@ -117,7 +117,7 @@
 	// TODO: LOCAL_PACKAGE_OVERRIDES
 	//    $(addprefix --rename-manifest-package , $(PRIVATE_MANIFEST_PACKAGE_NAME)) \
 
-	a.aapt.buildActions(ctx, String(a.deviceProperties.Sdk_version), linkFlags...)
+	a.aapt.buildActions(ctx, sdkContext(a), linkFlags...)
 
 	// apps manifests are handled by aapt, don't let Module see them
 	a.properties.Manifest = nil
diff --git a/java/dex.go b/java/dex.go
index f729bad..06ee272 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -70,7 +70,12 @@
 			"--verbose")
 	}
 
-	flags = append(flags, "--min-api "+j.minSdkVersionNumber(ctx))
+	minSdkVersion, err := sdkVersionToNumberAsString(ctx, j.minSdkVersion())
+	if err != nil {
+		ctx.PropertyErrorf("min_sdk_version", "%s", err)
+	}
+
+	flags = append(flags, "--min-api "+minSdkVersion)
 	return flags
 }
 
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 1eb935f..dbceed8 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -332,9 +332,17 @@
 	return module
 }
 
+func (j *Javadoc) sdkVersion() string {
+	return String(j.properties.Sdk_version)
+}
+
+func (j *Javadoc) minSdkVersion() string {
+	return j.sdkVersion()
+}
+
 func (j *Javadoc) addDeps(ctx android.BottomUpMutatorContext) {
 	if ctx.Device() {
-		sdkDep := decodeSdkDep(ctx, String(j.properties.Sdk_version))
+		sdkDep := decodeSdkDep(ctx, sdkContext(j))
 		if sdkDep.useDefaultLibs {
 			ctx.AddDependency(ctx.Module(), bootClasspathTag, config.DefaultBootclasspathLibraries...)
 			if ctx.Config().TargetOpenJDK9() {
@@ -432,7 +440,7 @@
 func (j *Javadoc) collectDeps(ctx android.ModuleContext) deps {
 	var deps deps
 
-	sdkDep := decodeSdkDep(ctx, String(j.properties.Sdk_version))
+	sdkDep := decodeSdkDep(ctx, sdkContext(j))
 	if sdkDep.invalidVersion {
 		ctx.AddMissingDependencies(sdkDep.modules)
 	} else if sdkDep.useFiles {
@@ -455,7 +463,7 @@
 			case Dependency:
 				deps.classpath = append(deps.classpath, dep.ImplementationJars()...)
 			case SdkLibraryDependency:
-				sdkVersion := String(j.properties.Sdk_version)
+				sdkVersion := j.sdkVersion()
 				linkType := javaSdk
 				if strings.HasPrefix(sdkVersion, "system_") || strings.HasPrefix(sdkVersion, "test_") {
 					linkType = javaSystem
@@ -539,7 +547,7 @@
 
 	var bootClasspathArgs, classpathArgs string
 
-	javaVersion := getJavaVersion(ctx, String(j.properties.Java_version), String(j.properties.Sdk_version))
+	javaVersion := getJavaVersion(ctx, String(j.properties.Java_version), sdkContext(j))
 	if len(deps.bootClasspath) > 0 {
 		var systemModules classpath
 		if deps.systemModules != nil {
@@ -639,7 +647,7 @@
 	implicits = append(implicits, deps.classpath...)
 
 	var bootClasspathArgs string
-	javaVersion := getJavaVersion(ctx, String(d.Javadoc.properties.Java_version), String(d.Javadoc.properties.Sdk_version))
+	javaVersion := getJavaVersion(ctx, String(d.Javadoc.properties.Java_version), sdkContext(d))
 	// Doclava has problem with "-source 1.9", so override javaVersion when Doclava
 	// is running with EXPERIMENTAL_USE_OPENJDK9=true. And eventually Doclava will be
 	// replaced by Metalava.
diff --git a/java/java.go b/java/java.go
index e87a990..5d75b1f 100644
--- a/java/java.go
+++ b/java/java.go
@@ -168,9 +168,14 @@
 	// list of module-specific flags that will be used for dex compiles
 	Dxflags []string `android:"arch_variant"`
 
-	// if not blank, set to the version of the sdk to compile against
+	// if not blank, set to the version of the sdk to compile against.  Defaults to compiling against the current
+	// sdk if platform_apis is not set.
 	Sdk_version *string
 
+	// if not blank, set the minimum version of the sdk that the compiled artifacts will run against.
+	// Defaults to sdk_version if not set.
+	Min_sdk_version *string
+
 	// if true, compile against the platform APIs instead of an SDK.
 	Platform_apis *bool
 
@@ -192,6 +197,9 @@
 	// If true, export a copy of the module as a -hostdex module for host testing.
 	Hostdex *bool
 
+	// If set to true, compile dex regardless of installable.  Defaults to false.
+	Compile_dex *bool
+
 	Dex_preopt struct {
 		// If false, prevent dexpreopting and stripping the dex file from the final jar.  Defaults to
 		// true.
@@ -352,20 +360,6 @@
 	aidl android.Path
 }
 
-func sdkStringToNumber(ctx android.BaseContext, v string) int {
-	switch v {
-	case "", "current", "system_current", "test_current", "core_current":
-		return android.FutureApiLevel
-	default:
-		if i, err := strconv.Atoi(android.GetNumericSdkVersion(v)); err != nil {
-			ctx.PropertyErrorf("sdk_version", "invalid sdk version")
-			return -1
-		} else {
-			return i
-		}
-	}
-}
-
 func (j *Module) shouldInstrument(ctx android.BaseContext) bool {
 	return j.properties.Instrument && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT")
 }
@@ -376,10 +370,62 @@
 			ctx.Config().UnbundledBuild())
 }
 
-func decodeSdkDep(ctx android.BaseContext, v string) sdkDep {
-	i := sdkStringToNumber(ctx, v)
-	if i == -1 {
-		// Invalid sdk version, error handled by sdkStringToNumber.
+func (j *Module) sdkVersion() string {
+	return String(j.deviceProperties.Sdk_version)
+}
+
+func (j *Module) minSdkVersion() string {
+	if j.deviceProperties.Min_sdk_version != nil {
+		return *j.deviceProperties.Min_sdk_version
+	}
+	return j.sdkVersion()
+}
+
+type sdkContext interface {
+	// sdkVersion eturns the sdk_version property of the current module, or an empty string if it is not set.
+	sdkVersion() string
+	// minSdkVersion returns the min_sdk_version property of the current module, or sdkVersion() if it is not set.
+	minSdkVersion() string
+}
+
+func sdkVersionOrDefault(ctx android.BaseContext, v string) string {
+	switch v {
+	case "", "current", "system_current", "test_current", "core_current":
+		return ctx.Config().DefaultAppTargetSdk()
+	default:
+		return v
+	}
+}
+
+// Returns a sdk version as a number.  For modules targeting an unreleased SDK (meaning it does not yet have a number)
+// it returns android.FutureApiLevel (10000).
+func sdkVersionToNumber(ctx android.BaseContext, v string) (int, error) {
+	switch v {
+	case "", "current", "test_current", "system_current", "core_current":
+		return ctx.Config().DefaultAppTargetSdkInt(), nil
+	default:
+		n := android.GetNumericSdkVersion(v)
+		if i, err := strconv.Atoi(n); err != nil {
+			return -1, fmt.Errorf("invalid sdk version %q", n)
+		} else {
+			return i, nil
+		}
+	}
+}
+
+func sdkVersionToNumberAsString(ctx android.BaseContext, v string) (string, error) {
+	n, err := sdkVersionToNumber(ctx, v)
+	if err != nil {
+		return "", err
+	}
+	return strconv.Itoa(n), nil
+}
+
+func decodeSdkDep(ctx android.BaseContext, sdkContext sdkContext) sdkDep {
+	v := sdkContext.sdkVersion()
+	i, err := sdkVersionToNumber(ctx, v)
+	if err != nil {
+		ctx.PropertyErrorf("sdk_version", "%s", err)
 		return sdkDep{}
 	}
 
@@ -482,7 +528,7 @@
 func (j *Module) deps(ctx android.BottomUpMutatorContext) {
 	if ctx.Device() {
 		if !Bool(j.properties.No_standard_libs) {
-			sdkDep := decodeSdkDep(ctx, String(j.deviceProperties.Sdk_version))
+			sdkDep := decodeSdkDep(ctx, sdkContext(j))
 			if sdkDep.useDefaultLibs {
 				ctx.AddDependency(ctx.Module(), bootClasspathTag, config.DefaultBootclasspathLibraries...)
 				if ctx.Config().TargetOpenJDK9() {
@@ -635,7 +681,7 @@
 )
 
 func getLinkType(m *Module, name string) linkType {
-	ver := String(m.deviceProperties.Sdk_version)
+	ver := m.sdkVersion()
 	noStdLibs := Bool(m.properties.No_standard_libs)
 	switch {
 	case name == "core.current.stubs" || ver == "core_current" || noStdLibs || name == "stub-annotations":
@@ -694,7 +740,7 @@
 	var deps deps
 
 	if ctx.Device() {
-		sdkDep := decodeSdkDep(ctx, String(j.deviceProperties.Sdk_version))
+		sdkDep := decodeSdkDep(ctx, sdkContext(j))
 		if sdkDep.invalidVersion {
 			ctx.AddMissingDependencies(sdkDep.modules)
 		} else if sdkDep.useFiles {
@@ -805,16 +851,19 @@
 	return deps
 }
 
-func getJavaVersion(ctx android.ModuleContext, javaVersion, sdkVersion string) string {
+func getJavaVersion(ctx android.ModuleContext, javaVersion string, sdkContext sdkContext) string {
 	var ret string
-	sdk := sdkStringToNumber(ctx, sdkVersion)
+	sdk, err := sdkVersionToNumber(ctx, sdkContext.sdkVersion())
+	if err != nil {
+		ctx.PropertyErrorf("sdk_version", "%s", err)
+	}
 	if javaVersion != "" {
 		ret = javaVersion
 	} else if ctx.Device() && sdk <= 23 {
 		ret = "1.7"
 	} else if ctx.Device() && sdk <= 26 || !ctx.Config().TargetOpenJDK9() {
 		ret = "1.8"
-	} else if ctx.Device() && sdkVersion != "" && sdk == android.FutureApiLevel {
+	} else if ctx.Device() && sdkContext.sdkVersion() != "" && sdk == android.FutureApiLevel {
 		// TODO(ccross): once we generate stubs we should be able to use 1.9 for sdk_version: "current"
 		ret = "1.8"
 	} else {
@@ -861,8 +910,7 @@
 	}
 
 	// javaVersion flag.
-	flags.javaVersion = getJavaVersion(ctx,
-		String(j.properties.Java_version), String(j.deviceProperties.Sdk_version))
+	flags.javaVersion = getJavaVersion(ctx, String(j.properties.Java_version), sdkContext(j))
 
 	// classpath
 	flags.bootClasspath = append(flags.bootClasspath, deps.bootClasspath...)
@@ -1134,11 +1182,15 @@
 		outputFile = j.instrument(ctx, flags, outputFile, jarName)
 	}
 
-	if ctx.Device() && j.installable() {
-		outputFile = j.compileDex(ctx, flags, outputFile, jarName)
+	if ctx.Device() && j.createDexRule() {
+		var dexOutputFile android.Path
+		dexOutputFile = j.compileDex(ctx, flags, outputFile, jarName)
 		if ctx.Failed() {
 			return
 		}
+		if j.installable() {
+			outputFile = dexOutputFile
+		}
 	}
 	ctx.CheckbuildFile(outputFile)
 	j.outputFile = outputFile
@@ -1198,21 +1250,14 @@
 	return instrumentedJar
 }
 
-// Returns a sdk version as a string that is guaranteed to be a parseable as a number.  For
-// modules targeting an unreleased SDK (meaning it does not yet have a number) it returns "10000".
-func (j *Module) minSdkVersionNumber(ctx android.ModuleContext) string {
-	switch String(j.deviceProperties.Sdk_version) {
-	case "", "current", "test_current", "system_current", "core_current":
-		return strconv.Itoa(ctx.Config().DefaultAppTargetSdkInt())
-	default:
-		return android.GetNumericSdkVersion(String(j.deviceProperties.Sdk_version))
-	}
-}
-
 func (j *Module) installable() bool {
 	return BoolDefault(j.properties.Installable, true)
 }
 
+func (j *Module) createDexRule() bool {
+	return Bool(j.deviceProperties.Compile_dex) || j.installable()
+}
+
 var _ Dependency = (*Library)(nil)
 
 func (j *Module) HeaderJars() android.Paths {
@@ -1448,6 +1493,14 @@
 	exportedSdkLibs       []string
 }
 
+func (j *Import) sdkVersion() string {
+	return String(j.properties.Sdk_version)
+}
+
+func (j *Import) minSdkVersion() string {
+	return j.sdkVersion()
+}
+
 func (j *Import) Prebuilt() *android.Prebuilt {
 	return &j.prebuilt
 }
diff --git a/ui/build/test_build.go b/ui/build/test_build.go
index 940f0c8..4bc4c97 100644
--- a/ui/build/test_build.go
+++ b/ui/build/test_build.go
@@ -18,6 +18,7 @@
 	"bufio"
 	"path/filepath"
 	"runtime"
+	"sort"
 	"strings"
 )
 
@@ -56,7 +57,7 @@
 	bootstrapDir := filepath.Join(outDir, "soong", ".bootstrap")
 	miniBootstrapDir := filepath.Join(outDir, "soong", ".minibootstrap")
 
-	var danglingRules []string
+	danglingRules := make(map[string]bool)
 
 	scanner := bufio.NewScanner(stdout)
 	for scanner.Scan() {
@@ -70,16 +71,22 @@
 			// full build rules in the primary build.ninja file.
 			continue
 		}
-		danglingRules = append(danglingRules, line)
+		danglingRules[line] = true
 	}
 
 	cmd.WaitOrFatal()
 
-	if len(danglingRules) > 0 {
+	var danglingRulesList []string
+	for rule := range danglingRules {
+		danglingRulesList = append(danglingRulesList, rule)
+	}
+	sort.Strings(danglingRulesList)
+
+	if len(danglingRulesList) > 0 {
 		ctx.Println("Dependencies in out found with no rule to create them:")
-		for _, dep := range danglingRules {
-			ctx.Println(dep)
+		for _, dep := range danglingRulesList {
+			ctx.Println("  ", dep)
 		}
-		ctx.Fatal("")
+		ctx.Fatal("stopping")
 	}
 }