Merge "Report reverse dependencies for dangling rules."
diff --git a/Android.bp b/Android.bp
index fe776df..97233f5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -132,6 +132,7 @@
     pkgPath: "android/soong/cc/config",
     deps: [
         "soong-android",
+        "soong-remoteexec",
     ],
     srcs: [
         "cc/config/clang.go",
@@ -547,6 +548,22 @@
     pluginFor: ["soong_build"],
 }
 
+bootstrap_go_package {
+    name: "soong-remoteexec",
+    pkgPath: "android/soong/remoteexec",
+    deps: [
+        "blueprint",
+        "soong-android",
+    ],
+    srcs: [
+        "remoteexec/remoteexec.go",
+    ],
+    testSrcs: [
+        "remoteexec/remoteexec_test.go",
+    ],
+    pluginFor: ["soong_build"],
+}
+
 //
 // Defaults to enable various configurations of host bionic
 //
diff --git a/apex/apex.go b/apex/apex.go
index fd3fb2b..8463ac1 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -836,10 +836,13 @@
 // Mark the direct and transitive dependencies of apex bundles so that they
 // can be built for the apex bundles.
 func apexDepsMutator(mctx android.TopDownMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
 	var apexBundles []android.ApexInfo
 	var directDep bool
 	if a, ok := mctx.Module().(*apexBundle); ok && !a.vndkApex {
-		apexBundles = []android.ApexInfo{android.ApexInfo{
+		apexBundles = []android.ApexInfo{{
 			ApexName:      mctx.ModuleName(),
 			MinSdkVersion: a.minSdkVersion(mctx),
 		}}
@@ -877,6 +880,9 @@
 
 // Create apex variations if a module is included in APEX(s).
 func apexMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
 	if am, ok := mctx.Module().(android.ApexModule); ok && am.CanHaveApexVariants() {
 		am.CreateApexVariations(mctx)
 	} else if a, ok := mctx.Module().(*apexBundle); ok && !a.vndkApex {
@@ -914,6 +920,9 @@
 }
 
 func apexFlattenedMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
 	if ab, ok := mctx.Module().(*apexBundle); ok {
 		var variants []string
 		switch proptools.StringDefault(ab.properties.Payload_type, "image") {
@@ -1732,15 +1741,16 @@
 func apexFileForAndroidApp(ctx android.BaseModuleContext, aapp interface {
 	android.Module
 	Privileged() bool
+	InstallApkName() string
 	OutputFile() android.Path
 	JacocoReportClassesFile() android.Path
 	Certificate() java.Certificate
-}, pkgName string) apexFile {
+}) apexFile {
 	appDir := "app"
 	if aapp.Privileged() {
 		appDir = "priv-app"
 	}
-	dirInApex := filepath.Join(appDir, pkgName)
+	dirInApex := filepath.Join(appDir, aapp.InstallApkName())
 	fileToCopy := aapp.OutputFile()
 	af := newApexFile(ctx, fileToCopy, aapp.Name(), dirInApex, app, aapp)
 	af.jacocoReportClassesFile = aapp.JacocoReportClassesFile()
@@ -2031,14 +2041,13 @@
 					ctx.PropertyErrorf("java_libs", "%q of type %q is not supported", depName, ctx.OtherModuleType(child))
 				}
 			case androidAppTag:
-				pkgName := ctx.DeviceConfig().OverridePackageNameFor(depName)
 				if ap, ok := child.(*java.AndroidApp); ok {
-					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap, pkgName))
+					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
 					return true // track transitive dependencies
 				} else if ap, ok := child.(*java.AndroidAppImport); ok {
-					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap, pkgName))
+					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
 				} else if ap, ok := child.(*java.AndroidTestHelperApp); ok {
-					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap, pkgName))
+					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
 				} else {
 					ctx.PropertyErrorf("apps", "%q is not an android_app module", depName)
 				}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 0c8937e..9f55728 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -3438,6 +3438,7 @@
 			dex_preopt: {
 				enabled: false,
 			},
+			filename: "AwesomePrebuiltAppFooPriv.apk",
 		}
 	`)
 
@@ -3446,7 +3447,47 @@
 	copyCmds := apexRule.Args["copy_commands"]
 
 	ensureContains(t, copyCmds, "image.apex/app/AppFooPrebuilt/AppFooPrebuilt.apk")
-	ensureContains(t, copyCmds, "image.apex/priv-app/AppFooPrivPrebuilt/AppFooPrivPrebuilt.apk")
+	ensureContains(t, copyCmds, "image.apex/priv-app/AppFooPrivPrebuilt/AwesomePrebuiltAppFooPriv.apk")
+}
+
+func TestApexWithAppImportsPrefer(t *testing.T) {
+	ctx, _ := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			apps: [
+				"AppFoo",
+			],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		android_app {
+			name: "AppFoo",
+			srcs: ["foo/bar/MyClass.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			apex_available: [ "myapex" ],
+		}
+
+		android_app_import {
+			name: "AppFoo",
+			apk: "AppFooPrebuilt.apk",
+			filename: "AppFooPrebuilt.apk",
+			presigned: true,
+			prefer: true,
+		}
+	`, withFiles(map[string][]byte{
+		"AppFooPrebuilt.apk": nil,
+	}))
+
+	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
+		"app/AppFoo/AppFooPrebuilt.apk",
+	})
 }
 
 func TestApexWithTestHelperApp(t *testing.T) {
@@ -3779,7 +3820,7 @@
 	copyCmds := apexRule.Args["copy_commands"]
 
 	ensureNotContains(t, copyCmds, "image.apex/app/app/app.apk")
-	ensureContains(t, copyCmds, "image.apex/app/app/override_app.apk")
+	ensureContains(t, copyCmds, "image.apex/app/override_app/override_app.apk")
 
 	apexBundle := module.Module().(*apexBundle)
 	name := apexBundle.Name()
@@ -4151,6 +4192,27 @@
 	})
 }
 
+func TestApexMutatorsDontRunIfDisabled(t *testing.T) {
+	ctx, _ := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`, func(fs map[string][]byte, config android.Config) {
+		delete(config.Targets, android.Android)
+		config.AndroidCommonTarget = android.Target{}
+	})
+
+	if expected, got := []string{""}, ctx.ModuleVariantsForTests("myapex"); !reflect.DeepEqual(expected, got) {
+		t.Errorf("Expected variants: %v, but got: %v", expected, got)
+	}
+}
+
 func TestApexWithJniLibs_Errors(t *testing.T) {
 	testApexError(t, `jni_libs: "xxx" is not a cc_library`, `
 		apex {
diff --git a/cc/builder.go b/cc/builder.go
index 136263b..b583586 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -29,6 +29,7 @@
 
 	"android/soong/android"
 	"android/soong/cc/config"
+	"android/soong/remoteexec"
 )
 
 const (
@@ -62,7 +63,7 @@
 		},
 		"ccCmd", "cFlags")
 
-	ld = pctx.AndroidStaticRule("ld",
+	ld, ldRE = remoteexec.StaticRules(pctx, "ld",
 		blueprint.RuleParams{
 			Command: "$ldCmd ${crtBegin} @${out}.rsp " +
 				"${libFlags} ${crtEnd} -o ${out} ${ldFlags} ${extraLibFlags}",
@@ -72,16 +73,28 @@
 			// clang -Wl,--out-implib doesn't update its output file if it hasn't changed.
 			Restat: true,
 		},
-		"ldCmd", "crtBegin", "libFlags", "crtEnd", "ldFlags", "extraLibFlags")
+		&remoteexec.REParams{Labels: map[string]string{"type": "link", "tool": "clang"},
+			ExecStrategy:    "${config.RECXXLinksExecStrategy}",
+			Inputs:          []string{"${out}.rsp"},
+			RSPFile:         "${out}.rsp",
+			OutputFiles:     []string{"${out}"},
+			ToolchainInputs: []string{"$ldCmd"},
+			Platform:        map[string]string{remoteexec.PoolKey: "${config.RECXXLinksPool}"},
+		}, []string{"ldCmd", "crtBegin", "libFlags", "crtEnd", "ldFlags", "extraLibFlags"}, nil)
 
-	partialLd = pctx.AndroidStaticRule("partialLd",
+	partialLd, partialLdRE = remoteexec.StaticRules(pctx, "partialLd",
 		blueprint.RuleParams{
 			// Without -no-pie, clang 7.0 adds -pie to link Android files,
 			// but -r and -pie cannot be used together.
 			Command:     "$ldCmd -fuse-ld=lld -nostdlib -no-pie -Wl,-r ${in} -o ${out} ${ldFlags}",
 			CommandDeps: []string{"$ldCmd"},
-		},
-		"ldCmd", "ldFlags")
+		}, &remoteexec.REParams{
+			Labels:       map[string]string{"type": "link", "tool": "clang"},
+			ExecStrategy: "${config.RECXXLinksExecStrategy}", Inputs: []string{"$inCommaList"},
+			OutputFiles:     []string{"${out}"},
+			ToolchainInputs: []string{"$ldCmd"},
+			Platform:        map[string]string{remoteexec.PoolKey: "${config.RECXXLinksPool}"},
+		}, []string{"ldCmd", "ldFlags"}, []string{"inCommaList"})
 
 	ar = pctx.AndroidStaticRule("ar",
 		blueprint.RuleParams{
@@ -262,6 +275,7 @@
 	}
 
 	pctx.HostBinToolVariable("SoongZipCmd", "soong_zip")
+	pctx.Import("android/soong/remoteexec")
 }
 
 type builderFlags struct {
@@ -657,8 +671,13 @@
 		deps = append(deps, crtBegin.Path(), crtEnd.Path())
 	}
 
+	rule := ld
+	if ctx.Config().IsEnvTrue("RBE_CXX_LINKS") {
+		rule = ldRE
+	}
+
 	ctx.Build(pctx, android.BuildParams{
-		Rule:            ld,
+		Rule:            rule,
 		Description:     "link " + outputFile.Base(),
 		Output:          outputFile,
 		ImplicitOutputs: implicitOutputs,
@@ -798,16 +817,22 @@
 
 	ldCmd := "${config.ClangBin}/clang++"
 
+	rule := partialLd
+	args := map[string]string{
+		"ldCmd":   ldCmd,
+		"ldFlags": flags.globalLdFlags + " " + flags.localLdFlags,
+	}
+	if ctx.Config().IsEnvTrue("RBE_CXX_LINKS") {
+		rule = partialLdRE
+		args["inCommaList"] = strings.Join(objFiles.Strings(), ",")
+	}
 	ctx.Build(pctx, android.BuildParams{
-		Rule:        partialLd,
+		Rule:        rule,
 		Description: "link " + outputFile.Base(),
 		Output:      outputFile,
 		Inputs:      objFiles,
 		Implicits:   deps,
-		Args: map[string]string{
-			"ldCmd":   ldCmd,
-			"ldFlags": flags.globalLdFlags + " " + flags.localLdFlags,
-		},
+		Args:        args,
 	})
 }
 
diff --git a/cc/cc.go b/cc/cc.go
index 0201404..fdfc221 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -1195,6 +1195,11 @@
 		return false
 	}
 
+	// Coverage builds have extra symbols.
+	if ctx.mod.isCoverageVariant() {
+		return false
+	}
+
 	if ctx.ctx.Fuchsia() {
 		return false
 	}
diff --git a/cc/config/global.go b/cc/config/global.go
index 5611a96..d85ac5f 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -18,6 +18,7 @@
 	"strings"
 
 	"android/soong/android"
+	"android/soong/remoteexec"
 )
 
 var (
@@ -255,6 +256,9 @@
 		}
 		return ""
 	})
+
+	pctx.VariableFunc("RECXXLinksPool", envOverrideFunc("RBE_CXX_LINKS_POOL", remoteexec.DefaultPool))
+	pctx.VariableFunc("RECXXLinksExecStrategy", envOverrideFunc("RBE_CXX_LINKS_EXEC_STRATEGY", remoteexec.LocalExecStrategy))
 }
 
 var HostPrebuiltTag = pctx.VariableConfigMethod("HostPrebuiltTag", android.Config.PrebuiltOS)
@@ -268,3 +272,12 @@
 		"-isystem bionic/libc/kernel/android/uapi",
 	}, " ")
 }
+
+func envOverrideFunc(envVar, defaultVal string) func(ctx android.PackageVarContext) string {
+	return func(ctx android.PackageVarContext) string {
+		if override := ctx.Config().Getenv(envVar); override != "" {
+			return override
+		}
+		return defaultVal
+	}
+}
diff --git a/java/androidmk.go b/java/androidmk.go
index d37eac8..bbf2c33 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -472,34 +472,6 @@
 				if ddoc.Javadoc.stubsSrcJar != nil {
 					entries.SetPath("LOCAL_DROIDDOC_STUBS_SRCJAR", ddoc.Javadoc.stubsSrcJar)
 				}
-				apiFilePrefix := "INTERNAL_PLATFORM_"
-				if String(ddoc.properties.Api_tag_name) != "" {
-					apiFilePrefix += String(ddoc.properties.Api_tag_name) + "_"
-				}
-				if ddoc.apiFile != nil {
-					entries.SetPath(apiFilePrefix+"API_FILE", ddoc.apiFile)
-				}
-				if ddoc.dexApiFile != nil {
-					entries.SetPath(apiFilePrefix+"DEX_API_FILE", ddoc.dexApiFile)
-				}
-				if ddoc.privateApiFile != nil {
-					entries.SetPath(apiFilePrefix+"PRIVATE_API_FILE", ddoc.privateApiFile)
-				}
-				if ddoc.privateDexApiFile != nil {
-					entries.SetPath(apiFilePrefix+"PRIVATE_DEX_API_FILE", ddoc.privateDexApiFile)
-				}
-				if ddoc.removedApiFile != nil {
-					entries.SetPath(apiFilePrefix+"REMOVED_API_FILE", ddoc.removedApiFile)
-				}
-				if ddoc.removedDexApiFile != nil {
-					entries.SetPath(apiFilePrefix+"REMOVED_DEX_API_FILE", ddoc.removedDexApiFile)
-				}
-				if ddoc.exactApiFile != nil {
-					entries.SetPath(apiFilePrefix+"EXACT_API_FILE", ddoc.exactApiFile)
-				}
-				if ddoc.proguardFile != nil {
-					entries.SetPath(apiFilePrefix+"PROGUARD_FILE", ddoc.proguardFile)
-				}
 			},
 		},
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
@@ -578,35 +550,18 @@
 				if dstubs.metadataZip != nil {
 					entries.SetPath("LOCAL_DROIDDOC_METADATA_ZIP", dstubs.metadataZip)
 				}
-				apiFilePrefix := "INTERNAL_PLATFORM_"
-				if String(dstubs.properties.Api_tag_name) != "" {
-					apiFilePrefix += String(dstubs.properties.Api_tag_name) + "_"
-				}
-				if dstubs.apiFile != nil {
-					entries.SetPath(apiFilePrefix+"API_FILE", dstubs.apiFile)
-				}
-				if dstubs.dexApiFile != nil {
-					entries.SetPath(apiFilePrefix+"DEX_API_FILE", dstubs.dexApiFile)
-				}
-				if dstubs.privateApiFile != nil {
-					entries.SetPath(apiFilePrefix+"PRIVATE_API_FILE", dstubs.privateApiFile)
-				}
-				if dstubs.privateDexApiFile != nil {
-					entries.SetPath(apiFilePrefix+"PRIVATE_DEX_API_FILE", dstubs.privateDexApiFile)
-				}
-				if dstubs.removedApiFile != nil {
-					entries.SetPath(apiFilePrefix+"REMOVED_API_FILE", dstubs.removedApiFile)
-				}
-				if dstubs.removedDexApiFile != nil {
-					entries.SetPath(apiFilePrefix+"REMOVED_DEX_API_FILE", dstubs.removedDexApiFile)
-				}
-				if dstubs.exactApiFile != nil {
-					entries.SetPath(apiFilePrefix+"EXACT_API_FILE", dstubs.exactApiFile)
-				}
 			},
 		},
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
 			func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) {
+				if dstubs.apiFile != nil {
+					fmt.Fprintf(w, ".PHONY: %s %s.txt\n", dstubs.Name(), dstubs.Name())
+					fmt.Fprintf(w, "%s %s.txt: %s\n", dstubs.Name(), dstubs.Name(), dstubs.apiFile)
+				}
+				if dstubs.removedApiFile != nil {
+					fmt.Fprintf(w, ".PHONY: %s %s.txt\n", dstubs.Name(), dstubs.Name())
+					fmt.Fprintf(w, "%s %s.txt: %s\n", dstubs.Name(), dstubs.Name(), dstubs.removedApiFile)
+				}
 				if dstubs.checkCurrentApiTimestamp != nil {
 					fmt.Fprintln(w, ".PHONY:", dstubs.Name()+"-check-current-api")
 					fmt.Fprintln(w, dstubs.Name()+"-check-current-api:",
diff --git a/java/app.go b/java/app.go
index ac5121a..7d509ee 100755
--- a/java/app.go
+++ b/java/app.go
@@ -511,6 +511,10 @@
 	return certificates
 }
 
+func (a *AndroidApp) InstallApkName() string {
+	return a.installApkName
+}
+
 func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) {
 	var apkDeps android.Paths
 
@@ -1134,6 +1138,10 @@
 	a.generateAndroidBuildActions(ctx)
 }
 
+func (a *AndroidAppImport) InstallApkName() string {
+	return a.BaseModuleName()
+}
+
 func (a *AndroidAppImport) generateAndroidBuildActions(ctx android.ModuleContext) {
 	numCertPropsSet := 0
 	if String(a.properties.Certificate) != "" {
@@ -1191,6 +1199,8 @@
 		dexOutput = dexUncompressed
 	}
 
+	apkFilename := proptools.StringDefault(a.properties.Filename, a.BaseModuleName()+".apk")
+
 	// Sign or align the package
 	// TODO: Handle EXTERNAL
 	if !Bool(a.properties.Presigned) {
@@ -1201,11 +1211,11 @@
 			ctx.ModuleErrorf("Unexpected number of certificates were extracted: %q", certificates)
 		}
 		a.certificate = certificates[0]
-		signed := android.PathForModuleOut(ctx, "signed", ctx.ModuleName()+".apk")
+		signed := android.PathForModuleOut(ctx, "signed", apkFilename)
 		SignAppPackage(ctx, signed, dexOutput, certificates, nil)
 		a.outputFile = signed
 	} else {
-		alignedApk := android.PathForModuleOut(ctx, "zip-aligned", ctx.ModuleName()+".apk")
+		alignedApk := android.PathForModuleOut(ctx, "zip-aligned", apkFilename)
 		TransformZipAlign(ctx, alignedApk, dexOutput)
 		a.outputFile = alignedApk
 		a.certificate = presignedCertificate
@@ -1213,8 +1223,7 @@
 
 	// TODO: Optionally compress the output apk.
 
-	a.installPath = ctx.InstallFile(installDir,
-		proptools.StringDefault(a.properties.Filename, a.BaseModuleName()+".apk"), a.outputFile)
+	a.installPath = ctx.InstallFile(installDir, apkFilename, a.outputFile)
 
 	// TODO: androidmk converter jni libs
 }
diff --git a/java/sdk_library.go b/java/sdk_library.go
index ff80d63..51e680b 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -394,13 +394,6 @@
 	}
 }
 
-// $(INTERNAL_PLATFORM_<apiTagName>_API_FILE) points to the generated
-// api file for the current source
-// TODO: remove this when apicheck is done in soong
-func (module *SdkLibrary) apiTagName(apiScope *apiScope) string {
-	return strings.Replace(strings.ToUpper(module.BaseModuleName()), ".", "_", -1) + apiScope.apiFileMakeVariableSuffix
-}
-
 func (module *SdkLibrary) latestApiFilegroupName(apiScope *apiScope) string {
 	return ":" + module.BaseModuleName() + ".api." + apiScope.name + ".latest"
 }
@@ -491,9 +484,6 @@
 		Libs                             []string
 		Arg_files                        []string
 		Args                             *string
-		Api_tag_name                     *string
-		Api_filename                     *string
-		Removed_api_filename             *string
 		Java_version                     *string
 		Merge_annotations_dirs           []string
 		Merge_inclusion_annotations_dirs []string
@@ -572,10 +562,6 @@
 	apiDir := module.getApiDir()
 	currentApiFileName = path.Join(apiDir, currentApiFileName)
 	removedApiFileName = path.Join(apiDir, removedApiFileName)
-	// TODO(jiyong): remove these three props
-	props.Api_tag_name = proptools.StringPtr(module.apiTagName(apiScope))
-	props.Api_filename = proptools.StringPtr(currentApiFileName)
-	props.Removed_api_filename = proptools.StringPtr(removedApiFileName)
 
 	// check against the not-yet-release API
 	props.Check_api.Current.Api_file = proptools.StringPtr(currentApiFileName)
diff --git a/remoteexec/remoteexec.go b/remoteexec/remoteexec.go
new file mode 100644
index 0000000..d43dc6c
--- /dev/null
+++ b/remoteexec/remoteexec.go
@@ -0,0 +1,155 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package remoteexec
+
+import (
+	"sort"
+	"strings"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+const (
+	// ContainerImageKey is the key identifying the container image in the platform spec.
+	ContainerImageKey = "container-image"
+
+	// PoolKey is the key identifying the pool to use for remote execution.
+	PoolKey = "Pool"
+
+	// DefaultImage is the default container image used for Android remote execution. The
+	// image was built with the Dockerfile at
+	// https://android.googlesource.com/platform/prebuilts/remoteexecution-client/+/refs/heads/master/docker/Dockerfile
+	DefaultImage = "docker://gcr.io/androidbuild-re-dockerimage/android-build-remoteexec-image@sha256:582efb38f0c229ea39952fff9e132ccbe183e14869b39888010dacf56b360d62"
+
+	// DefaultWrapperPath is the default path to the remote execution wrapper.
+	DefaultWrapperPath = "prebuilts/remoteexecution-client/live/rewrapper"
+
+	// DefaultPool is the name of the pool to use for remote execution when none is specified.
+	DefaultPool = "default"
+
+	// LocalExecStrategy is the exec strategy to indicate that the action should be run locally.
+	LocalExecStrategy = "local"
+
+	// RemoteExecStrategy is the exec strategy to indicate that the action should be run
+	// remotely.
+	RemoteExecStrategy = "remote"
+
+	// RemoteLocalFallbackExecStrategy is the exec strategy to indicate that the action should
+	// be run remotely and fallback to local execution if remote fails.
+	RemoteLocalFallbackExecStrategy = "remote_local_fallback"
+)
+
+var (
+	defaultLabels       = map[string]string{"type": "tool"}
+	defaultExecStrategy = LocalExecStrategy
+	pctx                = android.NewPackageContext("android/soong/remoteexec")
+)
+
+// REParams holds information pertinent to the remote execution of a rule.
+type REParams struct {
+	// Platform is the key value pair used for remotely executing the action.
+	Platform map[string]string
+	// Labels is a map of labels that identify the rule.
+	Labels map[string]string
+	// ExecStrategy is the remote execution strategy: remote, local, or remote_local_fallback.
+	ExecStrategy string
+	// Inputs is a list of input paths or ninja variables.
+	Inputs []string
+	// RSPFile is the name of the ninja variable used by the rule as a placeholder for an rsp
+	// input.
+	RSPFile string
+	// OutputFiles is a list of output file paths or ninja variables as placeholders for rule
+	// outputs.
+	OutputFiles []string
+	// ToolchainInputs is a list of paths or ninja variables pointing to the location of
+	// toolchain binaries used by the rule.
+	ToolchainInputs []string
+}
+
+func init() {
+	pctx.VariableFunc("Wrapper", func(ctx android.PackageVarContext) string {
+		if override := ctx.Config().Getenv("RBE_WRAPPER"); override != "" {
+			return override
+		}
+		return DefaultWrapperPath
+	})
+}
+
+// Generate the remote execution wrapper template to be added as a prefix to the rule's command.
+func (r *REParams) Template() string {
+	template := "${remoteexec.Wrapper}"
+
+	var kvs []string
+	labels := r.Labels
+	if len(labels) == 0 {
+		labels = defaultLabels
+	}
+	for k, v := range labels {
+		kvs = append(kvs, k+"="+v)
+	}
+	sort.Strings(kvs)
+	template += " --labels=" + strings.Join(kvs, ",")
+
+	var platform []string
+	for k, v := range r.Platform {
+		if v == "" {
+			continue
+		}
+		platform = append(platform, k+"="+v)
+	}
+	if _, ok := r.Platform[ContainerImageKey]; !ok {
+		platform = append(platform, ContainerImageKey+"="+DefaultImage)
+	}
+	if platform != nil {
+		sort.Strings(platform)
+		template += " --platform=\"" + strings.Join(platform, ",") + "\""
+	}
+
+	strategy := r.ExecStrategy
+	if strategy == "" {
+		strategy = defaultExecStrategy
+	}
+	template += " --exec_strategy=" + strategy
+
+	if len(r.Inputs) > 0 {
+		template += " --inputs=" + strings.Join(r.Inputs, ",")
+	}
+
+	if r.RSPFile != "" {
+		template += " --input_list_paths=" + r.RSPFile
+	}
+
+	if len(r.OutputFiles) > 0 {
+		template += " --output_files=" + strings.Join(r.OutputFiles, ",")
+	}
+
+	if len(r.ToolchainInputs) > 0 {
+		template += " --toolchain_inputs=" + strings.Join(r.ToolchainInputs, ",")
+	}
+
+	return template + " -- "
+}
+
+// StaticRules returns a pair of rules based on the given RuleParams, where the first rule is a
+// locally executable rule and the second rule is a remotely executable rule.
+func StaticRules(ctx android.PackageContext, name string, ruleParams blueprint.RuleParams, reParams *REParams, commonArgs []string, reArgs []string) (blueprint.Rule, blueprint.Rule) {
+	ruleParamsRE := ruleParams
+	ruleParamsRE.Command = reParams.Template() + ruleParamsRE.Command
+
+	return ctx.AndroidStaticRule(name, ruleParams, commonArgs...),
+		ctx.AndroidRemoteStaticRule(name+"RE", android.RemoteRuleSupports{RBE: true}, ruleParamsRE, append(commonArgs, reArgs...)...)
+}
diff --git a/remoteexec/remoteexec_test.go b/remoteexec/remoteexec_test.go
new file mode 100644
index 0000000..30e891c
--- /dev/null
+++ b/remoteexec/remoteexec_test.go
@@ -0,0 +1,83 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package remoteexec
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestTemplate(t *testing.T) {
+	tests := []struct {
+		name   string
+		params *REParams
+		want   string
+	}{
+		{
+			name: "basic",
+			params: &REParams{
+				Labels:      map[string]string{"type": "compile", "lang": "cpp", "compiler": "clang"},
+				Inputs:      []string{"$in"},
+				OutputFiles: []string{"$out"},
+				Platform: map[string]string{
+					ContainerImageKey: DefaultImage,
+					PoolKey:           "default",
+				},
+			},
+			want: fmt.Sprintf("${remoteexec.Wrapper} --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=local --inputs=$in --output_files=$out -- ", DefaultImage),
+		},
+		{
+			name: "all params",
+			params: &REParams{
+				Labels:          map[string]string{"type": "compile", "lang": "cpp", "compiler": "clang"},
+				Inputs:          []string{"$in"},
+				OutputFiles:     []string{"$out"},
+				ExecStrategy:    "remote",
+				RSPFile:         "$out.rsp",
+				ToolchainInputs: []string{"clang++"},
+				Platform: map[string]string{
+					ContainerImageKey: DefaultImage,
+					PoolKey:           "default",
+				},
+			},
+			want: fmt.Sprintf("${remoteexec.Wrapper} --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=remote --inputs=$in --input_list_paths=$out.rsp --output_files=$out --toolchain_inputs=clang++ -- ", DefaultImage),
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if got := test.params.Template(); got != test.want {
+				t.Errorf("Template() returned\n%s\nwant\n%s", got, test.want)
+			}
+		})
+	}
+}
+
+func TestTemplateDeterminism(t *testing.T) {
+	r := &REParams{
+		Labels:      map[string]string{"type": "compile", "lang": "cpp", "compiler": "clang"},
+		Inputs:      []string{"$in"},
+		OutputFiles: []string{"$out"},
+		Platform: map[string]string{
+			ContainerImageKey: DefaultImage,
+			PoolKey:           "default",
+		},
+	}
+	want := fmt.Sprintf("${remoteexec.Wrapper} --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=local --inputs=$in --output_files=$out -- ", DefaultImage)
+	for i := 0; i < 1000; i++ {
+		if got := r.Template(); got != want {
+			t.Fatalf("Template() returned\n%s\nwant\n%s", got, want)
+		}
+	}
+}
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index ce8f968..a3234e3 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -190,6 +190,7 @@
 		// compiler wrappers set up by make
 		"CC_WRAPPER",
 		"CXX_WRAPPER",
+		"RBE_WRAPPER",
 		"JAVAC_WRAPPER",
 		"R8_WRAPPER",
 		"D8_WRAPPER",
diff --git a/ui/build/exec.go b/ui/build/exec.go
index e435c53..053bbae 100644
--- a/ui/build/exec.go
+++ b/ui/build/exec.go
@@ -19,6 +19,8 @@
 	"io"
 	"os/exec"
 	"strings"
+	"syscall"
+	"time"
 )
 
 // Cmd is a wrapper of os/exec.Cmd that integrates with the build context for
@@ -33,6 +35,8 @@
 	ctx    Context
 	config Config
 	name   string
+
+	started time.Time
 }
 
 func Command(ctx Context, config Config, name string, executable string, args ...string) *Cmd {
@@ -57,7 +61,20 @@
 		c.wrapSandbox()
 	}
 
-	c.ctx.Verboseln(c.Path, c.Args)
+	c.ctx.Verbosef("%q executing %q %v\n", c.name, c.Path, c.Args)
+	c.started = time.Now()
+}
+
+func (c *Cmd) report() {
+	if c.Cmd.ProcessState != nil {
+		rusage := c.Cmd.ProcessState.SysUsage().(*syscall.Rusage)
+		c.ctx.Verbosef("%q finished with exit code %d (%s real, %s user, %s system, %dMB maxrss)",
+			c.name, c.Cmd.ProcessState.ExitCode(),
+			time.Since(c.started).Round(time.Millisecond),
+			c.Cmd.ProcessState.UserTime().Round(time.Millisecond),
+			c.Cmd.ProcessState.SystemTime().Round(time.Millisecond),
+			rusage.Maxrss/1024)
+	}
 }
 
 func (c *Cmd) Start() error {
@@ -68,21 +85,30 @@
 func (c *Cmd) Run() error {
 	c.prepare()
 	err := c.Cmd.Run()
+	c.report()
 	return err
 }
 
 func (c *Cmd) Output() ([]byte, error) {
 	c.prepare()
 	bytes, err := c.Cmd.Output()
+	c.report()
 	return bytes, err
 }
 
 func (c *Cmd) CombinedOutput() ([]byte, error) {
 	c.prepare()
 	bytes, err := c.Cmd.CombinedOutput()
+	c.report()
 	return bytes, err
 }
 
+func (c *Cmd) Wait() error {
+	err := c.Cmd.Wait()
+	c.report()
+	return err
+}
+
 // StartOrFatal is equivalent to Start, but handles the error with a call to ctx.Fatal
 func (c *Cmd) StartOrFatal() {
 	if err := c.Start(); err != nil {