Merge "Name the resulting file $BUILD_NUMBER.kzip, drop revision.txt"
diff --git a/android/hooks.go b/android/hooks.go
index 5810996..64ffd52 100644
--- a/android/hooks.go
+++ b/android/hooks.go
@@ -30,7 +30,7 @@
 	BaseModuleContext
 	AppendProperties(...interface{})
 	PrependProperties(...interface{})
-	CreateModule(blueprint.ModuleFactory, ...interface{})
+	CreateModule(ModuleFactory, ...interface{})
 }
 
 // Arch hooks are run after the module has been split into architecture variants, and can be used
diff --git a/android/mutator.go b/android/mutator.go
index e76f847..7b7859c 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -121,7 +121,7 @@
 
 	Rename(name string)
 
-	CreateModule(blueprint.ModuleFactory, ...interface{})
+	CreateModule(ModuleFactory, ...interface{})
 }
 
 type topDownMutatorContext struct {
@@ -243,9 +243,9 @@
 	t.Module().base().commonProperties.DebugName = name
 }
 
-func (t *topDownMutatorContext) CreateModule(factory blueprint.ModuleFactory, props ...interface{}) {
+func (t *topDownMutatorContext) CreateModule(factory ModuleFactory, props ...interface{}) {
 	inherited := []interface{}{&t.Module().base().commonProperties, &t.Module().base().variableProperties}
-	t.bp.CreateModule(factory, append(inherited, props...)...)
+	t.bp.CreateModule(ModuleFactoryAdaptor(factory), append(inherited, props...)...)
 }
 
 func (b *bottomUpMutatorContext) MutatorName() string {
diff --git a/cc/test.go b/cc/test.go
index ba0b7e4..0e66e28 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -80,6 +80,11 @@
 	// Add MinApiLevelModuleController to auto generated test config. If the device property of
 	// "ro.build.version.sdk" < Test_min_sdk_version, then skip this module.
 	Test_min_sdk_version *int64
+
+	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
+	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
+	// explicitly.
+	Auto_gen_config *bool
 }
 
 func init() {
@@ -362,7 +367,7 @@
 	}
 
 	test.testConfig = tradefed.AutoGenNativeTestConfig(ctx, test.Properties.Test_config,
-		test.Properties.Test_config_template, test.Properties.Test_suites, configs)
+		test.Properties.Test_config_template, test.Properties.Test_suites, configs, test.Properties.Auto_gen_config)
 
 	test.binaryDecorator.baseInstaller.dir = "nativetest"
 	test.binaryDecorator.baseInstaller.dir64 = "nativetest64"
@@ -453,6 +458,11 @@
 	// Add RootTargetPreparer to auto generated test config. This guarantees the test to run
 	// with root permission.
 	Require_root *bool
+
+	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
+	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
+	// explicitly.
+	Auto_gen_config *bool
 }
 
 type benchmarkDecorator struct {
@@ -490,7 +500,7 @@
 		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", nil})
 	}
 	benchmark.testConfig = tradefed.AutoGenNativeBenchmarkTestConfig(ctx, benchmark.Properties.Test_config,
-		benchmark.Properties.Test_config_template, benchmark.Properties.Test_suites, configs)
+		benchmark.Properties.Test_config_template, benchmark.Properties.Test_suites, configs, benchmark.Properties.Auto_gen_config)
 
 	benchmark.binaryDecorator.baseInstaller.dir = filepath.Join("benchmarktest", ctx.ModuleName())
 	benchmark.binaryDecorator.baseInstaller.dir64 = filepath.Join("benchmarktest64", ctx.ModuleName())
diff --git a/cmd/multiproduct_kati/main.go b/cmd/multiproduct_kati/main.go
index 1171a65..2800ade 100644
--- a/cmd/multiproduct_kati/main.go
+++ b/cmd/multiproduct_kati/main.go
@@ -158,7 +158,7 @@
 func main() {
 	stdio := terminal.StdioImpl{}
 
-	output := terminal.NewStatusOutput(stdio.Stdout(), "",
+	output := terminal.NewStatusOutput(stdio.Stdout(), "", false,
 		build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))
 
 	log := logger.New(output)
@@ -391,7 +391,7 @@
 		Thread:  mpctx.Tracer.NewThread(product),
 		Status:  &status.Status{},
 	}}
-	ctx.Status.AddOutput(terminal.NewStatusOutput(ctx.Writer, "",
+	ctx.Status.AddOutput(terminal.NewStatusOutput(ctx.Writer, "", false,
 		build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")))
 
 	config := build.NewConfig(ctx, flag.Args()...)
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index ec4f90e..974c644 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -41,6 +41,12 @@
 	// description for the flag (to display when running help)
 	description string
 
+	// Forces the status output into dumb terminal mode.
+	forceDumbOutput bool
+
+	// Sets a prefix string to use for filenames of log files.
+	logsPrefix string
+
 	// Creates the build configuration based on the args and build context.
 	config func(ctx build.Context, args ...string) build.Config
 
@@ -64,17 +70,21 @@
 		stdio: stdio,
 		run:   make,
 	}, {
-		flag:        "--dumpvar-mode",
-		description: "print the value of the legacy make variable VAR to stdout",
-		config:      dumpVarConfig,
-		stdio:       customStdio,
-		run:         dumpVar,
+		flag:            "--dumpvar-mode",
+		description:     "print the value of the legacy make variable VAR to stdout",
+		forceDumbOutput: true,
+		logsPrefix:      "dumpvars-",
+		config:          dumpVarConfig,
+		stdio:           customStdio,
+		run:             dumpVar,
 	}, {
-		flag:        "--dumpvars-mode",
-		description: "dump the values of one or more legacy make variables, in shell syntax",
-		config:      dumpVarConfig,
-		stdio:       customStdio,
-		run:         dumpVars,
+		flag:            "--dumpvars-mode",
+		description:     "dump the values of one or more legacy make variables, in shell syntax",
+		forceDumbOutput: true,
+		logsPrefix:      "dumpvars-",
+		config:          dumpVarConfig,
+		stdio:           customStdio,
+		run:             dumpVars,
 	}, {
 		flag:        "--build-mode",
 		description: "build modules based on the specified build action",
@@ -113,7 +123,7 @@
 		os.Exit(1)
 	}
 
-	output := terminal.NewStatusOutput(c.stdio().Stdout(), os.Getenv("NINJA_STATUS"),
+	output := terminal.NewStatusOutput(c.stdio().Stdout(), os.Getenv("NINJA_STATUS"), c.forceDumbOutput,
 		build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))
 
 	log := logger.New(output)
@@ -157,14 +167,14 @@
 	}
 
 	os.MkdirAll(logsDir, 0777)
-	log.SetOutput(filepath.Join(logsDir, "soong.log"))
-	trace.SetOutput(filepath.Join(logsDir, "build.trace"))
-	stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, "verbose.log")))
-	stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, "error.log")))
-	stat.AddOutput(status.NewProtoErrorLog(log, filepath.Join(logsDir, "build_error")))
+	log.SetOutput(filepath.Join(logsDir, c.logsPrefix+"soong.log"))
+	trace.SetOutput(filepath.Join(logsDir, c.logsPrefix+"build.trace"))
+	stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, c.logsPrefix+"verbose.log")))
+	stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, c.logsPrefix+"error.log")))
+	stat.AddOutput(status.NewProtoErrorLog(log, filepath.Join(logsDir, c.logsPrefix+"build_error")))
 	stat.AddOutput(status.NewCriticalPath(log))
 
-	defer met.Dump(filepath.Join(logsDir, "soong_metrics"))
+	defer met.Dump(filepath.Join(logsDir, c.logsPrefix+"soong_metrics"))
 
 	if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
 		if !strings.HasSuffix(start, "N") {
diff --git a/java/app.go b/java/app.go
index d00c4c0..afddb6a 100644
--- a/java/app.go
+++ b/java/app.go
@@ -608,7 +608,8 @@
 	}
 	a.generateAndroidBuildActions(ctx)
 
-	a.testConfig = tradefed.AutoGenInstrumentationTestConfig(ctx, a.testProperties.Test_config, a.testProperties.Test_config_template, a.manifestPath, a.testProperties.Test_suites)
+	a.testConfig = tradefed.AutoGenInstrumentationTestConfig(ctx, a.testProperties.Test_config,
+		a.testProperties.Test_config_template, a.manifestPath, a.testProperties.Test_suites, a.testProperties.Auto_gen_config)
 	a.data = android.PathsForModuleSrc(ctx, a.testProperties.Data)
 }
 
@@ -656,6 +657,11 @@
 	// list of compatibility suites (for example "cts", "vts") that the module should be
 	// installed into.
 	Test_suites []string `android:"arch_variant"`
+
+	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
+	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
+	// explicitly.
+	Auto_gen_config *bool
 }
 
 type AndroidTestHelperApp struct {
diff --git a/java/java.go b/java/java.go
index 4dd6a49..ce72c27 100644
--- a/java/java.go
+++ b/java/java.go
@@ -1685,6 +1685,11 @@
 	// list of files or filegroup modules that provide data that should be installed alongside
 	// the test
 	Data []string `android:"path"`
+
+	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
+	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
+	// explicitly.
+	Auto_gen_config *bool
 }
 
 type testHelperLibraryProperties struct {
@@ -1709,7 +1714,8 @@
 }
 
 func (j *Test) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.testProperties.Test_config, j.testProperties.Test_config_template, j.testProperties.Test_suites)
+	j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.testProperties.Test_config, j.testProperties.Test_config_template,
+		j.testProperties.Test_suites, j.testProperties.Auto_gen_config)
 	j.data = android.PathsForModuleSrc(ctx, j.testProperties.Data)
 
 	j.Library.GenerateAndroidBuildActions(ctx)
diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go
index c370811..0d5e31f 100644
--- a/java/prebuilt_apis.go
+++ b/java/prebuilt_apis.go
@@ -82,7 +82,7 @@
 	props.Sdk_version = proptools.StringPtr("current")
 	props.Installable = proptools.BoolPtr(false)
 
-	mctx.CreateModule(android.ModuleFactoryAdaptor(ImportFactory), &props)
+	mctx.CreateModule(ImportFactory, &props)
 }
 
 func createFilegroup(mctx android.TopDownMutatorContext, module string, scope string, apiver string, path string) {
@@ -93,7 +93,7 @@
 	}{}
 	filegroupProps.Name = proptools.StringPtr(fgName)
 	filegroupProps.Srcs = []string{path}
-	mctx.CreateModule(android.ModuleFactoryAdaptor(android.FileGroupFactory), &filegroupProps)
+	mctx.CreateModule(android.FileGroupFactory, &filegroupProps)
 }
 
 func getPrebuiltFiles(mctx android.TopDownMutatorContext, name string) []string {
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 30fd1c4..cc7256b 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -422,7 +422,7 @@
 		props.Product_specific = proptools.BoolPtr(true)
 	}
 
-	mctx.CreateModule(android.ModuleFactoryAdaptor(LibraryFactory), &props)
+	mctx.CreateModule(LibraryFactory, &props)
 }
 
 // Creates a droiddoc module that creates stubs source files from the given full source
@@ -522,7 +522,7 @@
 		module.latestRemovedApiFilegroupName(apiScope))
 	props.Check_api.Ignore_missing_latest_api = proptools.BoolPtr(true)
 
-	mctx.CreateModule(android.ModuleFactoryAdaptor(DroidstubsFactory), &props)
+	mctx.CreateModule(DroidstubsFactory, &props)
 }
 
 // Creates the xml file that publicizes the runtime library
@@ -560,7 +560,7 @@
 	genruleProps.Name = proptools.StringPtr(module.xmlFileName() + "-gen")
 	genruleProps.Cmd = proptools.StringPtr("echo '" + xmlContent + "' > $(out)")
 	genruleProps.Out = []string{module.xmlFileName()}
-	mctx.CreateModule(android.ModuleFactoryAdaptor(genrule.GenRuleFactory), &genruleProps)
+	mctx.CreateModule(genrule.GenRuleFactory, &genruleProps)
 
 	// creates a prebuilt_etc module to actually place the xml file under
 	// <partition>/etc/permissions
@@ -582,7 +582,7 @@
 	} else if module.ProductSpecific() {
 		etcProps.Product_specific = proptools.BoolPtr(true)
 	}
-	mctx.CreateModule(android.ModuleFactoryAdaptor(android.PrebuiltEtcFactory), &etcProps)
+	mctx.CreateModule(android.PrebuiltEtcFactory, &etcProps)
 }
 
 func (module *SdkLibrary) PrebuiltJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths {
@@ -815,7 +815,7 @@
 		props.Product_specific = proptools.BoolPtr(true)
 	}
 
-	mctx.CreateModule(android.ModuleFactoryAdaptor(ImportFactory), &props, &module.properties)
+	mctx.CreateModule(ImportFactory, &props, &module.properties)
 
 	javaSdkLibraries := javaSdkLibraries(mctx.Config())
 	javaSdkLibrariesLock.Lock()
diff --git a/python/binary.go b/python/binary.go
index 140f07a..695fa12 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -47,6 +47,11 @@
 	// false it will act much like the normal `python` executable, but with the sources and
 	// libraries automatically included in the PYTHONPATH.
 	Autorun *bool `android:"arch_variant"`
+
+	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
+	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
+	// explicitly.
+	Auto_gen_config *bool
 }
 
 type binaryDecorator struct {
diff --git a/python/test.go b/python/test.go
index 55b0ab5..f684fd5 100644
--- a/python/test.go
+++ b/python/test.go
@@ -50,7 +50,8 @@
 
 func (test *testDecorator) install(ctx android.ModuleContext, file android.Path) {
 	test.testConfig = tradefed.AutoGenPythonBinaryHostTestConfig(ctx, test.testProperties.Test_config,
-		test.testProperties.Test_config_template, test.binaryDecorator.binaryProperties.Test_suites)
+		test.testProperties.Test_config_template, test.binaryDecorator.binaryProperties.Test_suites,
+		test.binaryDecorator.binaryProperties.Auto_gen_config)
 
 	test.binaryDecorator.pythonInstaller.dir = "nativetest"
 	test.binaryDecorator.pythonInstaller.dir64 = "nativetest64"
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index 5eb7fa9..a876341 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -362,7 +362,7 @@
 	ccProps.Recovery_available = m.properties.Recovery_available
 	ccProps.Vendor_available = m.properties.Vendor_available
 
-	ctx.CreateModule(android.ModuleFactoryAdaptor(cc.LibraryFactory), &ccProps)
+	ctx.CreateModule(cc.LibraryFactory, &ccProps)
 
 	// internal scope contains all properties
 	// public scope only contains public properties
@@ -390,7 +390,7 @@
 		Name:  proptools.StringPtr(m.javaGenModuleName()),
 	}
 
-	ctx.CreateModule(android.ModuleFactoryAdaptor(syspropJavaGenFactory), &javaGenProps)
+	ctx.CreateModule(syspropJavaGenFactory, &javaGenProps)
 
 	javaProps := struct {
 		Name             *string
@@ -413,7 +413,7 @@
 	javaProps.Sdk_version = proptools.StringPtr("core_current")
 	javaProps.Libs = []string{stub}
 
-	ctx.CreateModule(android.ModuleFactoryAdaptor(java.LibraryFactory), &javaProps)
+	ctx.CreateModule(java.LibraryFactory, &javaProps)
 }
 
 func syspropDepsMutator(ctx android.BottomUpMutatorContext) {
diff --git a/tradefed/autogen.go b/tradefed/autogen.go
index 1400345..3d30cfa 100644
--- a/tradefed/autogen.go
+++ b/tradefed/autogen.go
@@ -44,10 +44,11 @@
 	CommandDeps: []string{"$template"},
 }, "name", "template", "extraConfigs")
 
-func testConfigPath(ctx android.ModuleContext, prop *string, testSuites []string) (path android.Path, autogenPath android.WritablePath) {
-	if p := getTestConfig(ctx, prop); p != nil {
+func testConfigPath(ctx android.ModuleContext, prop *string, testSuites []string, autoGenConfig *bool) (path android.Path, autogenPath android.WritablePath) {
+	p := getTestConfig(ctx, prop)
+	if !Bool(autoGenConfig) && p != nil {
 		return p, nil
-	} else if !android.InList("cts", testSuites) {
+	} else if !android.InList("cts", testSuites) && BoolDefault(autoGenConfig, true) {
 		outputFile := android.PathForModuleOut(ctx, ctx.ModuleName()+".config")
 		return nil, outputFile
 	} else {
@@ -124,8 +125,8 @@
 }
 
 func AutoGenNativeTestConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string, config []Config) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites)
+	testConfigTemplateProp *string, testSuites []string, config []Config, autoGenConfig *bool) android.Path {
+	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig)
 	if autogenPath != nil {
 		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
 		if templatePath.Valid() {
@@ -143,8 +144,8 @@
 }
 
 func AutoGenNativeBenchmarkTestConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string, configs []Config) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites)
+	testConfigTemplateProp *string, testSuites []string, configs []Config, autoGenConfig *bool) android.Path {
+	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig)
 	if autogenPath != nil {
 		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
 		if templatePath.Valid() {
@@ -157,8 +158,9 @@
 	return path
 }
 
-func AutoGenJavaTestConfig(ctx android.ModuleContext, testConfigProp *string, testConfigTemplateProp *string, testSuites []string) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites)
+func AutoGenJavaTestConfig(ctx android.ModuleContext, testConfigProp *string, testConfigTemplateProp *string,
+	testSuites []string, autoGenConfig *bool) android.Path {
+	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig)
 	if autogenPath != nil {
 		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
 		if templatePath.Valid() {
@@ -176,9 +178,9 @@
 }
 
 func AutoGenPythonBinaryHostTestConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string) android.Path {
+	testConfigTemplateProp *string, testSuites []string, autoGenConfig *bool) android.Path {
 
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites)
+	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig)
 	if autogenPath != nil {
 		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
 		if templatePath.Valid() {
@@ -200,8 +202,9 @@
 	},
 }, "name", "template")
 
-func AutoGenInstrumentationTestConfig(ctx android.ModuleContext, testConfigProp *string, testConfigTemplateProp *string, manifest android.Path, testSuites []string) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites)
+func AutoGenInstrumentationTestConfig(ctx android.ModuleContext, testConfigProp *string,
+	testConfigTemplateProp *string, manifest android.Path, testSuites []string, autoGenConfig *bool) android.Path {
+	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig)
 	if autogenPath != nil {
 		template := "${InstrumentationTestConfigTemplate}"
 		moduleTemplate := getTestConfigTemplate(ctx, testConfigTemplateProp)
@@ -222,3 +225,6 @@
 	}
 	return path
 }
+
+var Bool = proptools.Bool
+var BoolDefault = proptools.BoolDefault
diff --git a/ui/build/config.go b/ui/build/config.go
index 665d2f0..def3345 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -16,7 +16,6 @@
 
 import (
 	"io/ioutil"
-	"log"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -189,27 +188,27 @@
 	checkTopDir(ctx)
 
 	if srcDir := absPath(ctx, "."); strings.ContainsRune(srcDir, ' ') {
-		log.Println("You are building in a directory whose absolute path contains a space character:")
-		log.Println()
-		log.Printf("%q\n", srcDir)
-		log.Println()
-		log.Fatalln("Directory names containing spaces are not supported")
+		ctx.Println("You are building in a directory whose absolute path contains a space character:")
+		ctx.Println()
+		ctx.Printf("%q\n", srcDir)
+		ctx.Println()
+		ctx.Fatalln("Directory names containing spaces are not supported")
 	}
 
 	if outDir := ret.OutDir(); strings.ContainsRune(outDir, ' ') {
-		log.Println("The absolute path of your output directory ($OUT_DIR) contains a space character:")
-		log.Println()
-		log.Printf("%q\n", outDir)
-		log.Println()
-		log.Fatalln("Directory names containing spaces are not supported")
+		ctx.Println("The absolute path of your output directory ($OUT_DIR) contains a space character:")
+		ctx.Println()
+		ctx.Printf("%q\n", outDir)
+		ctx.Println()
+		ctx.Fatalln("Directory names containing spaces are not supported")
 	}
 
 	if distDir := ret.DistDir(); strings.ContainsRune(distDir, ' ') {
-		log.Println("The absolute path of your dist directory ($DIST_DIR) contains a space character:")
-		log.Println()
-		log.Printf("%q\n", distDir)
-		log.Println()
-		log.Fatalln("Directory names containing spaces are not supported")
+		ctx.Println("The absolute path of your dist directory ($DIST_DIR) contains a space character:")
+		ctx.Println()
+		ctx.Printf("%q\n", distDir)
+		ctx.Println()
+		ctx.Fatalln("Directory names containing spaces are not supported")
 	}
 
 	// Configure Java-related variables, including adding it to $PATH
diff --git a/ui/terminal/smart_status.go b/ui/terminal/smart_status.go
index 57f71ab..efcfd43 100644
--- a/ui/terminal/smart_status.go
+++ b/ui/terminal/smart_status.go
@@ -189,7 +189,7 @@
 		fmt.Fprintf(s.writer, ansi.resetScrollingMargins())
 		_, height, _ := termSize(s.writer)
 		// Move the cursor to the top of the now-blank, previously non-scrolling region
-		fmt.Fprintf(s.writer, ansi.setCursor(height-s.tableHeight, 0))
+		fmt.Fprintf(s.writer, ansi.setCursor(height-s.tableHeight, 1))
 		// Turn the cursor back on
 		fmt.Fprintf(s.writer, ansi.showCursor())
 	}
@@ -334,52 +334,44 @@
 	scrollingHeight := s.termHeight - s.tableHeight
 
 	// Update the scrolling region in case the height of the terminal changed
-	fmt.Fprint(s.writer, ansi.setScrollingMargins(0, scrollingHeight))
-	// Move the cursor to the first line of the non-scrolling region
-	fmt.Fprint(s.writer, ansi.setCursor(scrollingHeight+1, 0))
+
+	fmt.Fprint(s.writer, ansi.setScrollingMargins(1, scrollingHeight))
 
 	// Write as many status lines as fit in the table
-	var tableLine int
-	var runningAction actionTableEntry
-	for tableLine, runningAction = range s.runningActions {
+	for tableLine := 0; tableLine < s.tableHeight; tableLine++ {
 		if tableLine >= s.tableHeight {
 			break
 		}
+		// Move the cursor to the correct line of the non-scrolling region
+		fmt.Fprint(s.writer, ansi.setCursor(scrollingHeight+1+tableLine, 1))
 
-		seconds := int(time.Since(runningAction.startTime).Round(time.Second).Seconds())
+		if tableLine < len(s.runningActions) {
+			runningAction := s.runningActions[tableLine]
 
-		desc := runningAction.action.Description
-		if desc == "" {
-			desc = runningAction.action.Command
+			seconds := int(time.Since(runningAction.startTime).Round(time.Second).Seconds())
+
+			desc := runningAction.action.Description
+			if desc == "" {
+				desc = runningAction.action.Command
+			}
+
+			color := ""
+			if seconds >= 60 {
+				color = ansi.red() + ansi.bold()
+			} else if seconds >= 30 {
+				color = ansi.yellow() + ansi.bold()
+			}
+
+			durationStr := fmt.Sprintf("   %2d:%02d ", seconds/60, seconds%60)
+			desc = elide(desc, s.termWidth-len(durationStr))
+			durationStr = color + durationStr + ansi.regular()
+			fmt.Fprint(s.writer, durationStr, desc)
 		}
-
-		color := ""
-		if seconds >= 60 {
-			color = ansi.red() + ansi.bold()
-		} else if seconds >= 30 {
-			color = ansi.yellow() + ansi.bold()
-		}
-
-		durationStr := fmt.Sprintf("   %2d:%02d ", seconds/60, seconds%60)
-		desc = elide(desc, s.termWidth-len(durationStr))
-		durationStr = color + durationStr + ansi.regular()
-
-		fmt.Fprint(s.writer, durationStr, desc, ansi.clearToEndOfLine())
-		if tableLine < s.tableHeight-1 {
-			fmt.Fprint(s.writer, "\n")
-		}
-	}
-
-	// Clear any remaining lines in the table
-	for ; tableLine < s.tableHeight; tableLine++ {
 		fmt.Fprint(s.writer, ansi.clearToEndOfLine())
-		if tableLine < s.tableHeight-1 {
-			fmt.Fprint(s.writer, "\n")
-		}
 	}
 
 	// Move the cursor back to the last line of the scrolling region
-	fmt.Fprint(s.writer, ansi.setCursor(scrollingHeight, 0))
+	fmt.Fprint(s.writer, ansi.setCursor(scrollingHeight, 1))
 }
 
 var ansi = ansiImpl{}
diff --git a/ui/terminal/status.go b/ui/terminal/status.go
index 69a2a09..60dfc70 100644
--- a/ui/terminal/status.go
+++ b/ui/terminal/status.go
@@ -26,10 +26,10 @@
 //
 // statusFormat takes nearly all the same options as NINJA_STATUS.
 // %c is currently unsupported.
-func NewStatusOutput(w io.Writer, statusFormat string, quietBuild bool) status.StatusOutput {
+func NewStatusOutput(w io.Writer, statusFormat string, forceDumbOutput, quietBuild bool) status.StatusOutput {
 	formatter := newFormatter(statusFormat, quietBuild)
 
-	if isSmartTerminal(w) {
+	if !forceDumbOutput && isSmartTerminal(w) {
 		return NewSmartStatusOutput(w, formatter)
 	} else {
 		return NewDumbStatusOutput(w, formatter)
diff --git a/ui/terminal/status_test.go b/ui/terminal/status_test.go
index 81aa238..9f60829 100644
--- a/ui/terminal/status_test.go
+++ b/ui/terminal/status_test.go
@@ -94,7 +94,7 @@
 
 			t.Run("smart", func(t *testing.T) {
 				smart := &fakeSmartTerminal{termWidth: 40}
-				stat := NewStatusOutput(smart, "", false)
+				stat := NewStatusOutput(smart, "", false, false)
 				tt.calls(stat)
 				stat.Flush()
 
@@ -105,7 +105,7 @@
 
 			t.Run("dumb", func(t *testing.T) {
 				dumb := &bytes.Buffer{}
-				stat := NewStatusOutput(dumb, "", false)
+				stat := NewStatusOutput(dumb, "", false, false)
 				tt.calls(stat)
 				stat.Flush()
 
@@ -113,6 +113,17 @@
 					t.Errorf("want:\n%q\ngot:\n%q", w, g)
 				}
 			})
+
+			t.Run("force dumb", func(t *testing.T) {
+				smart := &fakeSmartTerminal{termWidth: 40}
+				stat := NewStatusOutput(smart, "", true, false)
+				tt.calls(stat)
+				stat.Flush()
+
+				if g, w := smart.String(), tt.dumb; g != w {
+					t.Errorf("want:\n%q\ngot:\n%q", w, g)
+				}
+			})
 		})
 	}
 }
@@ -258,7 +269,7 @@
 	os.Setenv(tableHeightEnVar, "")
 
 	smart := &fakeSmartTerminal{termWidth: 40}
-	stat := NewStatusOutput(smart, "", false)
+	stat := NewStatusOutput(smart, "", false, false)
 	smartStat := stat.(*smartStatusOutput)
 	smartStat.sigwinchHandled = make(chan bool)