Merge "OverridableModuleBase shouldn't embed ModuleBase."
diff --git a/Android.bp b/Android.bp
index 614e71f..fff17ef 100644
--- a/Android.bp
+++ b/Android.bp
@@ -296,6 +296,7 @@
"java/jdeps_test.go",
"java/kotlin_test.go",
"java/plugin_test.go",
+ "java/robolectric_test.go",
"java/sdk_test.go",
],
pluginFor: ["soong_build"],
diff --git a/OWNERS b/OWNERS
index 85c70df..4ae045d 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,5 @@
-per-file * = asmundak@google.com,ccross@android.com,dwillemsen@google.com,jungjw@google.com
+per-file * = asmundak@google.com,ccross@android.com,dwillemsen@google.com,jungjw@google.com,paulduffin@google.com
+
per-file ndk_*.go, *gen_stub_libs.py = danalbert@google.com
per-file clang.go,global.go = srhines@google.com, chh@google.com, pirama@google.com, yikong@google.com
per-file tidy.go = srhines@google.com, chh@google.com
diff --git a/android/arch.go b/android/arch.go
index f4a3c06..46e582c 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -713,7 +713,7 @@
// If host is supported for the module, the Host and HostCross OsClasses are selected. If device is supported
// for the module, the Device OsClass is selected.
// Within each selected OsClass, the multilib selection is determined by:
-// - The compile_multilib property if it set (which may be overriden by target.android.compile_multlib or
+// - The compile_multilib property if it set (which may be overridden by target.android.compile_multilib or
// target.host.compile_multilib).
// - The default multilib passed to InitAndroidArchModule if compile_multilib was not set.
// Valid multilib values include:
diff --git a/android/module.go b/android/module.go
index 03993e5..87e2ca7 100644
--- a/android/module.go
+++ b/android/module.go
@@ -966,10 +966,12 @@
func (m *moduleContext) ninjaError(params BuildParams, err error) (PackageContext, BuildParams) {
return pctx, BuildParams{
- Rule: ErrorRule,
- Description: params.Description,
- Output: params.Output,
- Outputs: params.Outputs,
+ Rule: ErrorRule,
+ Description: params.Description,
+ Output: params.Output,
+ Outputs: params.Outputs,
+ ImplicitOutput: params.ImplicitOutput,
+ ImplicitOutputs: params.ImplicitOutputs,
Args: map[string]string{
"error": err.Error(),
},
diff --git a/android/namespace.go b/android/namespace.go
index 78d7f3c..27ec163 100644
--- a/android/namespace.go
+++ b/android/namespace.go
@@ -222,6 +222,11 @@
}
func (r *NameResolver) getNamespacesToSearchForModule(sourceNamespace *Namespace) (searchOrder []*Namespace) {
+ if sourceNamespace.visibleNamespaces == nil {
+ // When handling dependencies before namespaceMutator, assume they are non-Soong Blueprint modules and give
+ // access to all namespaces.
+ return r.sortedNamespaces.sortedItems()
+ }
return sourceNamespace.visibleNamespaces
}
diff --git a/android/namespace_test.go b/android/namespace_test.go
index 51a0af2..20241fe 100644
--- a/android/namespace_test.go
+++ b/android/namespace_test.go
@@ -91,6 +91,28 @@
// setupTest will report any errors
}
+func TestDependingOnBlueprintModuleInRootNamespace(t *testing.T) {
+ _ = setupTest(t,
+ map[string]string{
+ ".": `
+ blueprint_test_module {
+ name: "a",
+ }
+ `,
+ "dir1": `
+ soong_namespace {
+ }
+ blueprint_test_module {
+ name: "b",
+ deps: ["a"],
+ }
+ `,
+ },
+ )
+
+ // setupTest will report any errors
+}
+
func TestDependingOnModuleInImportedNamespace(t *testing.T) {
ctx := setupTest(t,
map[string]string{
@@ -617,6 +639,7 @@
ctx.MockFileSystem(bps)
ctx.RegisterModuleType("test_module", ModuleFactoryAdaptor(newTestModule))
ctx.RegisterModuleType("soong_namespace", ModuleFactoryAdaptor(NamespaceFactory))
+ ctx.RegisterModuleType("blueprint_test_module", newBlueprintTestModule)
ctx.PreArchMutators(RegisterNamespaceMutator)
ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
ctx.BottomUp("rename", renameMutator)
@@ -641,6 +664,7 @@
}
func setupTest(t *testing.T, bps map[string]string) (ctx *TestContext) {
+ t.Helper()
ctx, errs := setupTestExpectErrs(bps)
FailIfErrored(t, errs)
return ctx
@@ -718,3 +742,22 @@
InitAndroidModule(m)
return m
}
+
+type blueprintTestModule struct {
+ blueprint.SimpleName
+ properties struct {
+ Deps []string
+ }
+}
+
+func (b *blueprintTestModule) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
+ return b.properties.Deps
+}
+
+func (b *blueprintTestModule) GenerateBuildActions(blueprint.ModuleContext) {
+}
+
+func newBlueprintTestModule() (blueprint.Module, []interface{}) {
+ m := &blueprintTestModule{}
+ return m, []interface{}{&m.properties, &m.SimpleName.Properties}
+}
diff --git a/android/neverallow.go b/android/neverallow.go
index ee3bf4a..f35d1fe 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -100,13 +100,12 @@
"development",
}
- // Core library constraints. The no_standard_libs can only be used in core
- // library projects. Access to core library targets is restricted using
- // visibility rules.
+ // Core library constraints. The sdk_version: "none" can only be used in core library projects.
+ // Access to core library targets is restricted using visibility rules.
rules := []*rule{
neverallow().
notIn(coreLibraryProjects...).
- with("no_standard_libs", "true"),
+ with("sdk_version", "none"),
}
return rules
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index 40ccf14..ee3c94f 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -148,33 +148,33 @@
},
// Libcore rule tests
{
- name: "no_standard_libs: true inside core libraries",
+ name: "sdk_version: \"none\" inside core libraries",
fs: map[string][]byte{
"libcore/Blueprints": []byte(`
java_library {
name: "inside_core_libraries",
- no_standard_libs: true,
+ sdk_version: "none",
}`),
},
},
{
- name: "no_standard_libs: true outside core libraries",
+ name: "sdk_version: \"none\" outside core libraries",
fs: map[string][]byte{
"Blueprints": []byte(`
java_library {
name: "outside_core_libraries",
- no_standard_libs: true,
+ sdk_version: "none",
}`),
},
expectedError: "module \"outside_core_libraries\": violates neverallow",
},
{
- name: "no_standard_libs: false",
+ name: "sdk_version: \"current\"",
fs: map[string][]byte{
"Blueprints": []byte(`
java_library {
name: "outside_core_libraries",
- no_standard_libs: false,
+ sdk_version: "current",
}`),
},
},
@@ -200,6 +200,7 @@
ctx := NewTestContext()
ctx.RegisterModuleType("cc_library", ModuleFactoryAdaptor(newMockCcLibraryModule))
ctx.RegisterModuleType("java_library", ModuleFactoryAdaptor(newMockJavaLibraryModule))
+ ctx.RegisterModuleType("java_library_host", ModuleFactoryAdaptor(newMockJavaLibraryModule))
ctx.RegisterModuleType("java_device_for_host", ModuleFactoryAdaptor(newMockJavaLibraryModule))
ctx.PostDepsMutators(registerNeverallowMutator)
ctx.Register()
@@ -251,8 +252,8 @@
}
type mockJavaLibraryProperties struct {
- Libs []string
- No_standard_libs *bool
+ Libs []string
+ Sdk_version *string
}
type mockJavaLibraryModule struct {
diff --git a/androidmk/cmd/androidmk/android.go b/androidmk/cmd/androidmk/android.go
index 62aa3dc..af81e43 100644
--- a/androidmk/cmd/androidmk/android.go
+++ b/androidmk/cmd/androidmk/android.go
@@ -185,7 +185,6 @@
"LOCAL_NO_CRT": "nocrt",
"LOCAL_ALLOW_UNDEFINED_SYMBOLS": "allow_undefined_symbols",
"LOCAL_RTTI_FLAG": "rtti",
- "LOCAL_NO_STANDARD_LIBRARIES": "no_standard_libs",
"LOCAL_PACK_MODULE_RELOCATIONS": "pack_relocations",
"LOCAL_TIDY": "tidy",
"LOCAL_USE_CLANG_LLD": "use_clang_lld",
diff --git a/apex/apex.go b/apex/apex.go
index 11b433c..84e5497 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -61,7 +61,7 @@
`--key ${key} ${opt_flags} ${image_dir} ${out} `,
CommandDeps: []string{"${apexer}", "${avbtool}", "${e2fsdroid}", "${merge_zips}",
"${mke2fs}", "${resize2fs}", "${sefcontext_compile}",
- "${soong_zip}", "${zipalign}", "${aapt2}"},
+ "${soong_zip}", "${zipalign}", "${aapt2}", "prebuilts/sdk/current/public/android.jar"},
Description: "APEX ${image_dir} => ${out}",
}, "tool_path", "image_dir", "copy_commands", "manifest", "file_contexts", "canned_fs_config", "key", "opt_flags")
@@ -1062,6 +1062,10 @@
Description: "signapk",
Output: a.outputFiles[apexType],
Input: unsignedOutputFile,
+ Implicits: []android.Path{
+ a.container_certificate_file,
+ a.container_private_key_file,
+ },
Args: map[string]string{
"certificates": a.container_certificate_file.String() + " " + a.container_private_key_file.String(),
"flags": "-a 4096", //alignment
diff --git a/cc/builder.go b/cc/builder.go
index 3a8afc0..1e12361 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -22,7 +22,6 @@
"fmt"
"path/filepath"
"runtime"
- "strconv"
"strings"
"github.com/google/blueprint"
@@ -93,20 +92,6 @@
},
"arCmd", "arFlags")
- darwinAr = pctx.AndroidStaticRule("darwinAr",
- blueprint.RuleParams{
- Command: "rm -f ${out} && ${config.MacArPath} $arFlags $out $in",
- CommandDeps: []string{"${config.MacArPath}"},
- },
- "arFlags")
-
- darwinAppendAr = pctx.AndroidStaticRule("darwinAppendAr",
- blueprint.RuleParams{
- Command: "cp -f ${inAr} ${out}.tmp && ${config.MacArPath} $arFlags ${out}.tmp $in && mv ${out}.tmp ${out}",
- CommandDeps: []string{"${config.MacArPath}", "${inAr}"},
- },
- "arFlags", "inAr")
-
darwinStrip = pctx.AndroidStaticRule("darwinStrip",
blueprint.RuleParams{
Command: "${config.MacStripPath} -u -r -o $out $in",
@@ -515,11 +500,6 @@
func TransformObjToStaticLib(ctx android.ModuleContext, objFiles android.Paths,
flags builderFlags, outputFile android.ModuleOutPath, deps android.Paths) {
- if ctx.Darwin() {
- transformDarwinObjToStaticLib(ctx, objFiles, flags, outputFile, deps)
- return
- }
-
arCmd := "${config.ClangBin}/llvm-ar"
arFlags := "crsD"
if !ctx.Darwin() {
@@ -542,82 +522,6 @@
})
}
-// Generate a rule for compiling multiple .o files to a static library (.a) on
-// darwin. The darwin ar tool doesn't support @file for list files, and has a
-// very small command line length limit, so we have to split the ar into multiple
-// steps, each appending to the previous one.
-func transformDarwinObjToStaticLib(ctx android.ModuleContext, objFiles android.Paths,
- flags builderFlags, outputFile android.ModuleOutPath, deps android.Paths) {
-
- arFlags := "cqs"
-
- if len(objFiles) == 0 {
- dummy := android.PathForModuleOut(ctx, "dummy"+objectExtension)
- dummyAr := android.PathForModuleOut(ctx, "dummy"+staticLibraryExtension)
-
- ctx.Build(pctx, android.BuildParams{
- Rule: emptyFile,
- Description: "empty object file",
- Output: dummy,
- Implicits: deps,
- })
-
- ctx.Build(pctx, android.BuildParams{
- Rule: darwinAr,
- Description: "empty static archive",
- Output: dummyAr,
- Input: dummy,
- Args: map[string]string{
- "arFlags": arFlags,
- },
- })
-
- ctx.Build(pctx, android.BuildParams{
- Rule: darwinAppendAr,
- Description: "static link " + outputFile.Base(),
- Output: outputFile,
- Input: dummy,
- Args: map[string]string{
- "arFlags": "d",
- "inAr": dummyAr.String(),
- },
- })
-
- return
- }
-
- // ARG_MAX on darwin is 262144, use half that to be safe
- objFilesLists, err := splitListForSize(objFiles, 131072)
- if err != nil {
- ctx.ModuleErrorf("%s", err.Error())
- }
-
- var in, out android.WritablePath
- for i, l := range objFilesLists {
- in = out
- out = outputFile
- if i != len(objFilesLists)-1 {
- out = android.PathForModuleOut(ctx, outputFile.Base()+strconv.Itoa(i))
- }
-
- build := android.BuildParams{
- Rule: darwinAr,
- Description: "static link " + out.Base(),
- Output: out,
- Inputs: l,
- Implicits: deps,
- Args: map[string]string{
- "arFlags": arFlags,
- },
- }
- if i != 0 {
- build.Rule = darwinAppendAr
- build.Args["inAr"] = in.String()
- }
- ctx.Build(pctx, build)
- }
-}
-
// Generate a rule for compiling multiple .o files, plus static libraries, whole static libraries,
// and shared libraries, to a shared library (.so) or dynamic executable
func TransformObjToDynamicBinary(ctx android.ModuleContext,
diff --git a/cc/config/global.go b/cc/config/global.go
index 24075d9..a27246e 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -122,8 +122,8 @@
// prebuilts/clang default settings.
ClangDefaultBase = "prebuilts/clang/host"
- ClangDefaultVersion = "clang-r353983c"
- ClangDefaultShortVersion = "9.0.3"
+ ClangDefaultVersion = "clang-r353983d"
+ ClangDefaultShortVersion = "9.0.4"
// Directories with warnings from Android.bp files.
WarningAllowedProjects = []string{
diff --git a/cc/gen.go b/cc/gen.go
index 82669ac..1d30dab 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -39,11 +39,11 @@
sysprop = pctx.AndroidStaticRule("sysprop",
blueprint.RuleParams{
- Command: "$syspropCmd --header-dir=$headerOutDir --system-header-dir=$systemOutDir " +
+ Command: "$syspropCmd --header-dir=$headerOutDir --public-header-dir=$publicOutDir " +
"--source-dir=$srcOutDir --include-name=$includeName $in",
CommandDeps: []string{"$syspropCmd"},
},
- "headerOutDir", "systemOutDir", "srcOutDir", "includeName")
+ "headerOutDir", "publicOutDir", "srcOutDir", "includeName")
windmc = pctx.AndroidStaticRule("windmc",
blueprint.RuleParams{
@@ -150,7 +150,7 @@
func genSysprop(ctx android.ModuleContext, syspropFile android.Path) (android.Path, android.Path) {
headerFile := android.PathForModuleGen(ctx, "sysprop", "include", syspropFile.Rel()+".h")
- systemHeaderFile := android.PathForModuleGen(ctx, "sysprop/system", "include", syspropFile.Rel()+".h")
+ publicHeaderFile := android.PathForModuleGen(ctx, "sysprop/public", "include", syspropFile.Rel()+".h")
cppFile := android.PathForModuleGen(ctx, "sysprop", syspropFile.Rel()+".cpp")
ctx.Build(pctx, android.BuildParams{
@@ -161,7 +161,7 @@
Input: syspropFile,
Args: map[string]string{
"headerOutDir": filepath.Dir(headerFile.String()),
- "systemOutDir": filepath.Dir(systemHeaderFile.String()),
+ "publicOutDir": filepath.Dir(publicHeaderFile.String()),
"srcOutDir": filepath.Dir(cppFile.String()),
"includeName": syspropFile.Rel() + ".h",
},
diff --git a/cc/library.go b/cc/library.go
index fab5837..f98cd36 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -882,10 +882,10 @@
isVendor := ctx.useVndk()
isOwnerPlatform := Bool(library.Properties.Sysprop.Platform)
- useSystem := isProduct || (isOwnerPlatform == isVendor)
+ usePublic := isProduct || (isOwnerPlatform == isVendor)
- if useSystem {
- dir = android.PathForModuleGen(ctx, "sysprop/system", "include").String()
+ if usePublic {
+ dir = android.PathForModuleGen(ctx, "sysprop/public", "include").String()
}
}
diff --git a/cc/pgo.go b/cc/pgo.go
index 7334ea2..4e915ff 100644
--- a/cc/pgo.go
+++ b/cc/pgo.go
@@ -27,10 +27,9 @@
var (
// Add flags to ignore warnings that profiles are old or missing for
- // some functions, and turn on the experimental new pass manager.
+ // some functions.
profileUseOtherFlags = []string{
"-Wno-backend-plugin",
- "-fexperimental-new-pass-manager",
}
globalPgoProfileProjects = []string{
diff --git a/cc/rs.go b/cc/rs.go
index 5421b92..fbc6bfb 100644
--- a/cc/rs.go
+++ b/cc/rs.go
@@ -72,11 +72,12 @@
stampFile := android.PathForModuleGen(ctx, "rs", "rs.stamp")
depFiles := make(android.WritablePaths, 0, len(rsFiles))
genFiles := make(android.WritablePaths, 0, 2*len(rsFiles))
+ headers := make(android.Paths, 0, len(rsFiles))
for _, rsFile := range rsFiles {
depFiles = append(depFiles, rsGeneratedDepFile(ctx, rsFile))
- genFiles = append(genFiles,
- rsGeneratedCppFile(ctx, rsFile),
- rsGeneratedHFile(ctx, rsFile))
+ headerFile := rsGeneratedHFile(ctx, rsFile)
+ genFiles = append(genFiles, rsGeneratedCppFile(ctx, rsFile), headerFile)
+ headers = append(headers, headerFile)
}
ctx.Build(pctx, android.BuildParams{
@@ -92,7 +93,7 @@
},
})
- return android.Paths{stampFile}
+ return headers
}
func rsFlags(ctx ModuleContext, flags Flags, properties *BaseCompilerProperties) Flags {
diff --git a/cc/sanitize.go b/cc/sanitize.go
index fdda7be..0af0659 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -40,7 +40,8 @@
hwasanCflags = []string{"-fno-omit-frame-pointer", "-Wno-frame-larger-than=",
"-mllvm", "-hwasan-create-frame-descriptions=0",
"-mllvm", "-hwasan-allow-ifunc",
- "-fsanitize-hwaddress-abi=platform"}
+ "-fsanitize-hwaddress-abi=platform",
+ "-fno-experimental-new-pass-manager"}
cfiCflags = []string{"-flto", "-fsanitize-cfi-cross-dso",
"-fsanitize-blacklist=external/compiler-rt/lib/cfi/cfi_blacklist.txt"}
diff --git a/cc/test.go b/cc/test.go
index dae2a37..c735fd9 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -64,6 +64,10 @@
// Test options.
Test_options TestOptions
+
+ // Add RootTargetPreparer to auto generated test config. This guarantees the test to run
+ // with root permission.
+ Require_root *bool
}
func init() {
@@ -273,18 +277,19 @@
func (test *testBinary) install(ctx ModuleContext, file android.Path) {
test.data = android.PathsForModuleSrc(ctx, test.Properties.Data)
- optionsMap := map[string]string{}
- if Bool(test.testDecorator.Properties.Isolated) {
- optionsMap["not-shardable"] = "true"
+ var configs []tradefed.Config
+ if Bool(test.Properties.Require_root) {
+ configs = append(configs, tradefed.Preparer{"com.android.tradefed.targetprep.RootTargetPreparer"})
}
-
+ if Bool(test.testDecorator.Properties.Isolated) {
+ configs = append(configs, tradefed.Option{"not-shardable", "true"})
+ }
if test.Properties.Test_options.Run_test_as != nil {
- optionsMap["run-test-as"] = String(test.Properties.Test_options.Run_test_as)
+ configs = append(configs, tradefed.Option{"run-test-as", String(test.Properties.Test_options.Run_test_as)})
}
test.testConfig = tradefed.AutoGenNativeTestConfig(ctx, test.Properties.Test_config,
- test.Properties.Test_config_template,
- test.Properties.Test_suites, optionsMap)
+ test.Properties.Test_config_template, test.Properties.Test_suites, configs)
test.binaryDecorator.baseInstaller.dir = "nativetest"
test.binaryDecorator.baseInstaller.dir64 = "nativetest64"
@@ -371,6 +376,10 @@
// the name of the test configuration template (for example "AndroidTestTemplate.xml") that
// should be installed with the module.
Test_config_template *string `android:"path,arch_variant"`
+
+ // Add RootTargetPreparer to auto generated test config. This guarantees the test to run
+ // with root permission.
+ Require_root *bool
}
type benchmarkDecorator struct {
@@ -403,8 +412,12 @@
func (benchmark *benchmarkDecorator) install(ctx ModuleContext, file android.Path) {
benchmark.data = android.PathsForModuleSrc(ctx, benchmark.Properties.Data)
+ var configs []tradefed.Config
+ if Bool(benchmark.Properties.Require_root) {
+ configs = append(configs, tradefed.Preparer{"com.android.tradefed.targetprep.RootTargetPreparer"})
+ }
benchmark.testConfig = tradefed.AutoGenNativeBenchmarkTestConfig(ctx, benchmark.Properties.Test_config,
- benchmark.Properties.Test_config_template, benchmark.Properties.Test_suites)
+ benchmark.Properties.Test_config_template, benchmark.Properties.Test_suites, configs)
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 330c5dd..1171a65 100644
--- a/cmd/multiproduct_kati/main.go
+++ b/cmd/multiproduct_kati/main.go
@@ -156,10 +156,12 @@
}
func main() {
- writer := terminal.NewWriter(terminal.StdioImpl{})
- defer writer.Finish()
+ stdio := terminal.StdioImpl{}
- log := logger.New(writer)
+ output := terminal.NewStatusOutput(stdio.Stdout(), "",
+ build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))
+
+ log := logger.New(output)
defer log.Cleanup()
flag.Parse()
@@ -172,8 +174,7 @@
stat := &status.Status{}
defer stat.Finish()
- stat.AddOutput(terminal.NewStatusOutput(writer, "",
- build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")))
+ stat.AddOutput(output)
var failures failureCount
stat.AddOutput(&failures)
@@ -188,7 +189,7 @@
Context: ctx,
Logger: log,
Tracer: trace,
- Writer: writer,
+ Writer: output,
Status: stat,
}}
@@ -341,7 +342,7 @@
} else if failures > 1 {
log.Fatalf("%d failures", failures)
} else {
- writer.Print("Success")
+ fmt.Fprintln(output, "Success")
}
}
@@ -386,7 +387,7 @@
Context: mpctx.Context,
Logger: log,
Tracer: mpctx.Tracer,
- Writer: terminal.NewWriter(terminal.NewCustomStdio(nil, f, f)),
+ Writer: f,
Thread: mpctx.Tracer.NewThread(product),
Status: &status.Status{},
}}
@@ -466,3 +467,8 @@
}
func (f *failureCount) Flush() {}
+
+func (f *failureCount) Write(p []byte) (int, error) {
+ // discard writes
+ return len(p), nil
+}
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index 5f9bd01..b909779 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -61,10 +61,8 @@
config: func(ctx build.Context, args ...string) build.Config {
return build.NewConfig(ctx, args...)
},
- stdio: func() terminal.StdioInterface {
- return terminal.StdioImpl{}
- },
- run: make,
+ stdio: stdio,
+ run: make,
}, {
flag: "--dumpvar-mode",
description: "print the value of the legacy make variable VAR to stdout",
@@ -77,6 +75,12 @@
config: dumpVarConfig,
stdio: customStdio,
run: dumpVars,
+ }, {
+ flag: "--build-mode",
+ description: "build modules based on the specified build action",
+ config: buildActionConfig,
+ stdio: stdio,
+ run: make,
},
}
@@ -109,10 +113,10 @@
os.Exit(1)
}
- writer := terminal.NewWriter(c.stdio())
- defer writer.Finish()
+ output := terminal.NewStatusOutput(c.stdio().Stdout(), os.Getenv("NINJA_STATUS"),
+ build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))
- log := logger.New(writer)
+ log := logger.New(output)
defer log.Cleanup()
ctx, cancel := context.WithCancel(context.Background())
@@ -125,8 +129,7 @@
stat := &status.Status{}
defer stat.Finish()
- stat.AddOutput(terminal.NewStatusOutput(writer, os.Getenv("NINJA_STATUS"),
- build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")))
+ stat.AddOutput(output)
stat.AddOutput(trace.StatusTracer())
build.SetupSignals(log, cancel, func() {
@@ -140,7 +143,7 @@
Logger: log,
Metrics: met,
Tracer: trace,
- Writer: writer,
+ Writer: output,
Status: stat,
}}
@@ -159,7 +162,7 @@
stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, "verbose.log")))
stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, "error.log")))
- defer met.Dump(filepath.Join(logsDir, "build_metrics"))
+ defer met.Dump(filepath.Join(logsDir, "soong_metrics"))
if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
if !strings.HasSuffix(start, "N") {
@@ -185,13 +188,13 @@
func dumpVar(ctx build.Context, config build.Config, args []string, _ string) {
flags := flag.NewFlagSet("dumpvar", flag.ExitOnError)
flags.Usage = func() {
- fmt.Fprintf(os.Stderr, "usage: %s --dumpvar-mode [--abs] <VAR>\n\n", os.Args[0])
- fmt.Fprintln(os.Stderr, "In dumpvar mode, print the value of the legacy make variable VAR to stdout")
- fmt.Fprintln(os.Stderr, "")
+ fmt.Fprintf(ctx.Writer, "usage: %s --dumpvar-mode [--abs] <VAR>\n\n", os.Args[0])
+ fmt.Fprintln(ctx.Writer, "In dumpvar mode, print the value of the legacy make variable VAR to stdout")
+ fmt.Fprintln(ctx.Writer, "")
- fmt.Fprintln(os.Stderr, "'report_config' is a special case that prints the human-readable config banner")
- fmt.Fprintln(os.Stderr, "from the beginning of the build.")
- fmt.Fprintln(os.Stderr, "")
+ fmt.Fprintln(ctx.Writer, "'report_config' is a special case that prints the human-readable config banner")
+ fmt.Fprintln(ctx.Writer, "from the beginning of the build.")
+ fmt.Fprintln(ctx.Writer, "")
flags.PrintDefaults()
}
abs := flags.Bool("abs", false, "Print the absolute path of the value")
@@ -235,15 +238,15 @@
func dumpVars(ctx build.Context, config build.Config, args []string, _ string) {
flags := flag.NewFlagSet("dumpvars", flag.ExitOnError)
flags.Usage = func() {
- fmt.Fprintf(os.Stderr, "usage: %s --dumpvars-mode [--vars=\"VAR VAR ...\"]\n\n", os.Args[0])
- fmt.Fprintln(os.Stderr, "In dumpvars mode, dump the values of one or more legacy make variables, in")
- fmt.Fprintln(os.Stderr, "shell syntax. The resulting output may be sourced directly into a shell to")
- fmt.Fprintln(os.Stderr, "set corresponding shell variables.")
- fmt.Fprintln(os.Stderr, "")
+ fmt.Fprintf(ctx.Writer, "usage: %s --dumpvars-mode [--vars=\"VAR VAR ...\"]\n\n", os.Args[0])
+ fmt.Fprintln(ctx.Writer, "In dumpvars mode, dump the values of one or more legacy make variables, in")
+ fmt.Fprintln(ctx.Writer, "shell syntax. The resulting output may be sourced directly into a shell to")
+ fmt.Fprintln(ctx.Writer, "set corresponding shell variables.")
+ fmt.Fprintln(ctx.Writer, "")
- fmt.Fprintln(os.Stderr, "'report_config' is a special case that dumps a variable containing the")
- fmt.Fprintln(os.Stderr, "human-readable config banner from the beginning of the build.")
- fmt.Fprintln(os.Stderr, "")
+ fmt.Fprintln(ctx.Writer, "'report_config' is a special case that dumps a variable containing the")
+ fmt.Fprintln(ctx.Writer, "human-readable config banner from the beginning of the build.")
+ fmt.Fprintln(ctx.Writer, "")
flags.PrintDefaults()
}
@@ -300,6 +303,10 @@
}
}
+func stdio() terminal.StdioInterface {
+ return terminal.StdioImpl{}
+}
+
func customStdio() terminal.StdioInterface {
return terminal.NewCustomStdio(os.Stdin, os.Stderr, os.Stderr)
}
@@ -309,16 +316,98 @@
return build.NewConfig(ctx)
}
+func buildActionConfig(ctx build.Context, args ...string) build.Config {
+ flags := flag.NewFlagSet("build-mode", flag.ContinueOnError)
+ flags.Usage = func() {
+ fmt.Fprintf(ctx.Writer, "usage: %s --build-mode --dir=<path> <build action> [<build arg 1> <build arg 2> ...]\n\n", os.Args[0])
+ fmt.Fprintln(ctx.Writer, "In build mode, build the set of modules based on the specified build")
+ fmt.Fprintln(ctx.Writer, "action. The --dir flag is required to determine what is needed to")
+ fmt.Fprintln(ctx.Writer, "build in the source tree based on the build action. See below for")
+ fmt.Fprintln(ctx.Writer, "the list of acceptable build action flags.")
+ fmt.Fprintln(ctx.Writer, "")
+ flags.PrintDefaults()
+ }
+
+ buildActionFlags := []struct {
+ name string
+ description string
+ action build.BuildAction
+ buildDependencies bool
+ set bool
+ }{{
+ name: "all-modules",
+ description: "Build action: build from the top of the source tree.",
+ action: build.BUILD_MODULES_IN_A_DIRECTORY,
+ buildDependencies: true,
+ }, {
+ name: "modules-in-a-dir-no-deps",
+ description: "Build action: builds all of the modules in the current directory without their dependencies.",
+ action: build.BUILD_MODULES_IN_A_DIRECTORY,
+ buildDependencies: false,
+ }, {
+ name: "modules-in-dirs-no-deps",
+ description: "Build action: builds all of the modules in the supplied directories without their dependencies.",
+ action: build.BUILD_MODULES_IN_DIRECTORIES,
+ buildDependencies: false,
+ }, {
+ name: "modules-in-a-dir",
+ description: "Build action: builds all of the modules in the current directory and their dependencies.",
+ action: build.BUILD_MODULES_IN_A_DIRECTORY,
+ buildDependencies: true,
+ }, {
+ name: "modules-in-dirs",
+ description: "Build action: builds all of the modules in the supplied directories and their dependencies.",
+ action: build.BUILD_MODULES_IN_DIRECTORIES,
+ buildDependencies: true,
+ }}
+ for i, flag := range buildActionFlags {
+ flags.BoolVar(&buildActionFlags[i].set, flag.name, false, flag.description)
+ }
+ dir := flags.String("dir", "", "Directory of the executed build command.")
+
+ // Only interested in the first two args which defines the build action and the directory.
+ // The remaining arguments are passed down to the config.
+ const numBuildActionFlags = 2
+ if len(args) < numBuildActionFlags {
+ flags.Usage()
+ ctx.Fatalln("Improper build action arguments.")
+ }
+ flags.Parse(args[0:numBuildActionFlags])
+
+ // The next block of code is to validate that exactly one build action is set and the dir flag
+ // is specified.
+ buildActionCount := 0
+ var buildAction build.BuildAction
+ buildDependency := false
+ for _, flag := range buildActionFlags {
+ if flag.set {
+ buildActionCount++
+ buildAction = flag.action
+ buildDependency = flag.buildDependencies
+ }
+ }
+ if buildActionCount != 1 {
+ ctx.Fatalln("Build action not defined.")
+ }
+ if *dir == "" {
+ ctx.Fatalln("-dir not specified.")
+ }
+
+ // Remove the build action flags from the args as they are not recognized by the config.
+ args = args[numBuildActionFlags:]
+ return build.NewBuildActionConfig(buildAction, *dir, buildDependency, ctx, args...)
+}
+
func make(ctx build.Context, config build.Config, _ []string, logsDir string) {
if config.IsVerbose() {
writer := ctx.Writer
- writer.Print("! The argument `showcommands` is no longer supported.")
- writer.Print("! Instead, the verbose log is always written to a compressed file in the output dir:")
- writer.Print("!")
- writer.Print(fmt.Sprintf("! gzip -cd %s/verbose.log.gz | less -R", logsDir))
- writer.Print("!")
- writer.Print("! Older versions are saved in verbose.log.#.gz files")
- writer.Print("")
+ fmt.Fprintln(writer, "! The argument `showcommands` is no longer supported.")
+ fmt.Fprintln(writer, "! Instead, the verbose log is always written to a compressed file in the output dir:")
+ fmt.Fprintln(writer, "!")
+ fmt.Fprintf(writer, "! gzip -cd %s/verbose.log.gz | less -R\n", logsDir)
+ fmt.Fprintln(writer, "!")
+ fmt.Fprintln(writer, "! Older versions are saved in verbose.log.#.gz files")
+ fmt.Fprintln(writer, "")
time.Sleep(5 * time.Second)
}
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 5a1bd74..a2f1af4 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -121,8 +121,9 @@
UsesLibraries []string
LibraryPaths map[string]android.Path
- Archs []android.ArchType
- DexPreoptImages []android.Path
+ Archs []android.ArchType
+ DexPreoptImages []android.Path
+ DexPreoptImagesDeps []android.Paths
PreoptBootClassPathDexFiles android.Paths // file paths of boot class path files
PreoptBootClassPathDexLocations []string // virtual locations of boot class path files
@@ -257,6 +258,9 @@
config.ModuleConfig.StripInputPath = constructPath(ctx, config.StripInputPath)
config.ModuleConfig.StripOutputPath = constructWritablePath(ctx, config.StripOutputPath)
+ // This needs to exist, but dependencies are already handled in Make, so we don't need to pass them through JSON.
+ config.ModuleConfig.DexPreoptImagesDeps = make([]android.Paths, len(config.ModuleConfig.DexPreoptImages))
+
return config.ModuleConfig, nil
}
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 0be37d0..e02e60f 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -125,7 +125,8 @@
for i, arch := range module.Archs {
image := module.DexPreoptImages[i]
- dexpreoptCommand(ctx, global, module, rule, arch, profile, image, appImage, generateDM)
+ imageDeps := module.DexPreoptImagesDeps[i]
+ dexpreoptCommand(ctx, global, module, rule, arch, profile, image, imageDeps, appImage, generateDM)
}
}
}
@@ -190,7 +191,7 @@
}
func dexpreoptCommand(ctx android.PathContext, global GlobalConfig, module ModuleConfig, rule *android.RuleBuilder,
- arch android.ArchType, profile, bootImage android.Path, appImage, generateDM bool) {
+ arch android.ArchType, profile, bootImage android.Path, bootImageDeps android.Paths, appImage, generateDM bool) {
// HACK: make soname in Soong-generated .odex files match Make.
base := filepath.Base(module.DexLocation)
@@ -353,7 +354,7 @@
Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", module.PreoptBootClassPathDexLocations, ":").
Flag("${class_loader_context_arg}").
Flag("${stored_class_loader_context_arg}").
- FlagWithArg("--boot-image=", bootImageLocation).Implicit(bootImage).
+ FlagWithArg("--boot-image=", bootImageLocation).Implicits(bootImageDeps).
FlagWithInput("--dex-file=", module.DexPath).
FlagWithArg("--dex-location=", dexLocationArg).
FlagWithOutput("--oat-file=", odexPath).ImplicitOutput(vdexPath).
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index 0402f87..7f1fe42 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -38,6 +38,7 @@
LibraryPaths: nil,
Archs: []android.ArchType{android.Arm},
DexPreoptImages: android.Paths{android.PathForTesting("system/framework/arm/boot.art")},
+ DexPreoptImagesDeps: []android.Paths{android.Paths{}},
PreoptBootClassPathDexFiles: nil,
PreoptBootClassPathDexLocations: nil,
PreoptExtractedApk: false,
diff --git a/jar/Android.bp b/jar/Android.bp
index 6c2e60e..2563474 100644
--- a/jar/Android.bp
+++ b/jar/Android.bp
@@ -18,8 +18,10 @@
srcs: [
"jar.go",
],
+ testSrcs: [
+ "jar_test.go",
+ ],
deps: [
"android-archive-zip",
],
}
-
diff --git a/jar/jar.go b/jar/jar.go
index fa0e693..a8f06a4 100644
--- a/jar/jar.go
+++ b/jar/jar.go
@@ -17,9 +17,12 @@
import (
"bytes"
"fmt"
+ "io"
"os"
"strings"
+ "text/scanner"
"time"
+ "unicode"
"android/soong/third_party/zip"
)
@@ -112,3 +115,111 @@
return finalBytes, nil
}
+
+var javaIgnorableIdentifier = &unicode.RangeTable{
+ R16: []unicode.Range16{
+ {0x00, 0x08, 1},
+ {0x0e, 0x1b, 1},
+ {0x7f, 0x9f, 1},
+ },
+ LatinOffset: 3,
+}
+
+func javaIdentRune(ch rune, i int) bool {
+ if unicode.IsLetter(ch) {
+ return true
+ }
+ if unicode.IsDigit(ch) && i > 0 {
+ return true
+ }
+
+ if unicode.In(ch,
+ unicode.Nl, // letter number
+ unicode.Sc, // currency symbol
+ unicode.Pc, // connecting punctuation
+ ) {
+ return true
+ }
+
+ if unicode.In(ch,
+ unicode.Cf, // format
+ unicode.Mc, // combining mark
+ unicode.Mn, // non-spacing mark
+ javaIgnorableIdentifier,
+ ) && i > 0 {
+ return true
+ }
+
+ return false
+}
+
+// JavaPackage parses the package out of a java source file by looking for the package statement, or the first valid
+// non-package statement, in which case it returns an empty string for the package.
+func JavaPackage(r io.Reader, src string) (string, error) {
+ var s scanner.Scanner
+ var sErr error
+
+ s.Init(r)
+ s.Filename = src
+ s.Error = func(s *scanner.Scanner, msg string) {
+ sErr = fmt.Errorf("error parsing %q: %s", src, msg)
+ }
+ s.IsIdentRune = javaIdentRune
+
+ tok := s.Scan()
+ if sErr != nil {
+ return "", sErr
+ }
+ if tok == scanner.Ident {
+ switch s.TokenText() {
+ case "package":
+ // Nothing
+ case "import":
+ // File has no package statement, first keyword is an import
+ return "", nil
+ case "class", "enum", "interface":
+ // File has no package statement, first keyword is a type declaration
+ return "", nil
+ case "public", "protected", "private", "abstract", "static", "final", "strictfp":
+ // File has no package statement, first keyword is a modifier
+ return "", nil
+ case "module", "open":
+ // File has no package statement, first keyword is a module declaration
+ return "", nil
+ default:
+ return "", fmt.Errorf(`expected first token of java file to be "package", got %q`, s.TokenText())
+ }
+ } else if tok == '@' {
+ // File has no package statement, first token is an annotation
+ return "", nil
+ } else if tok == scanner.EOF {
+ // File no package statement, it has no non-whitespace non-comment tokens
+ return "", nil
+ } else {
+ return "", fmt.Errorf(`expected first token of java file to be "package", got %q`, s.TokenText())
+ }
+
+ var pkg string
+ for {
+ tok = s.Scan()
+ if sErr != nil {
+ return "", sErr
+ }
+ if tok != scanner.Ident {
+ return "", fmt.Errorf(`expected "package <package>;", got "package %s%s"`, pkg, s.TokenText())
+ }
+ pkg += s.TokenText()
+
+ tok = s.Scan()
+ if sErr != nil {
+ return "", sErr
+ }
+ if tok == ';' {
+ return pkg, nil
+ } else if tok == '.' {
+ pkg += "."
+ } else {
+ return "", fmt.Errorf(`expected "package <package>;", got "package %s%s"`, pkg, s.TokenText())
+ }
+ }
+}
diff --git a/jar/jar_test.go b/jar/jar_test.go
new file mode 100644
index 0000000..c92011e
--- /dev/null
+++ b/jar/jar_test.go
@@ -0,0 +1,182 @@
+// Copyright 2017 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 jar
+
+import (
+ "bytes"
+ "io"
+ "testing"
+)
+
+func TestGetJavaPackage(t *testing.T) {
+ type args struct {
+ r io.Reader
+ src string
+ }
+ tests := []struct {
+ name string
+ in string
+ want string
+ wantErr bool
+ }{
+ {
+ name: "simple",
+ in: "package foo.bar;",
+ want: "foo.bar",
+ },
+ {
+ name: "comment",
+ in: "/* test */\npackage foo.bar;",
+ want: "foo.bar",
+ },
+ {
+ name: "no package",
+ in: "import foo.bar;",
+ want: "",
+ },
+ {
+ name: "missing semicolon error",
+ in: "package foo.bar",
+ wantErr: true,
+ },
+ {
+ name: "parser error",
+ in: "/*",
+ wantErr: true,
+ },
+ {
+ name: "parser ident error",
+ in: "package 0foo.bar;",
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ buf := bytes.NewBufferString(tt.in)
+ got, err := JavaPackage(buf, "<test>")
+ if (err != nil) != tt.wantErr {
+ t.Errorf("JavaPackage() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("JavaPackage() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_javaIdentRune(t *testing.T) {
+ // runes that should be valid anywhere in an identifier
+ validAnywhere := []rune{
+ // letters, $, _
+ 'a',
+ 'A',
+ '$',
+ '_',
+
+ // assorted unicode
+ '𐐀',
+ '𐐨',
+ 'Dž',
+ 'ῼ',
+ 'ʰ',
+ '゚',
+ 'ƻ',
+ '㡢',
+ '₩',
+ '_',
+ 'Ⅰ',
+ '𐍊',
+ }
+
+ // runes that should be invalid as the first rune in an identifier, but valid anywhere else
+ validAfterFirst := []rune{
+ // digits
+ '0',
+
+ // assorted unicode
+ '᥍',
+ '𝟎',
+ 'ྂ',
+ '𝆀',
+
+ // control characters
+ '\x00',
+ '\b',
+ '\u000e',
+ '\u001b',
+ '\u007f',
+ '\u009f',
+ '\u00ad',
+ 0xE007F,
+
+ // zero width space
+ '\u200b',
+ }
+
+ // runes that should never be valid in an identifier
+ invalid := []rune{
+ ';',
+ 0x110000,
+ }
+
+ validFirst := validAnywhere
+ invalidFirst := append(validAfterFirst, invalid...)
+ validPart := append(validAnywhere, validAfterFirst...)
+ invalidPart := invalid
+
+ check := func(t *testing.T, ch rune, i int, want bool) {
+ t.Helper()
+ if got := javaIdentRune(ch, i); got != want {
+ t.Errorf("javaIdentRune() = %v, want %v", got, want)
+ }
+ }
+
+ t.Run("first", func(t *testing.T) {
+ t.Run("valid", func(t *testing.T) {
+ for _, ch := range validFirst {
+ t.Run(string(ch), func(t *testing.T) {
+ check(t, ch, 0, true)
+ })
+ }
+ })
+
+ t.Run("invalid", func(t *testing.T) {
+ for _, ch := range invalidFirst {
+ t.Run(string(ch), func(t *testing.T) {
+ check(t, ch, 0, false)
+ })
+ }
+ })
+ })
+
+ t.Run("part", func(t *testing.T) {
+ t.Run("valid", func(t *testing.T) {
+ for _, ch := range validPart {
+ t.Run(string(ch), func(t *testing.T) {
+ check(t, ch, 1, true)
+ })
+ }
+ })
+
+ t.Run("invalid", func(t *testing.T) {
+ for _, ch := range invalidPart {
+ t.Run(string(ch), func(t *testing.T) {
+ check(t, ch, 1, false)
+ })
+ }
+ })
+ })
+}
diff --git a/java/aar.go b/java/aar.go
index 1e8e6d8..47f6e5f 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -188,8 +188,7 @@
return linkFlags, linkDeps, resDirs, overlayDirs, rroDirs, resourceZips
}
-func (a *aapt) deps(ctx android.BottomUpMutatorContext, sdkContext sdkContext) {
- sdkDep := decodeSdkDep(ctx, sdkContext)
+func (a *aapt) deps(ctx android.BottomUpMutatorContext, sdkDep sdkDep) {
if sdkDep.frameworkResModule != "" {
ctx.AddVariationDependencies(nil, frameworkResTag, sdkDep.frameworkResModule)
}
@@ -401,8 +400,9 @@
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, sdkContext(a))
+ sdkDep := decodeSdkDep(ctx, sdkContext(a))
+ if sdkDep.hasFrameworkLibs() {
+ a.aapt.deps(ctx, sdkDep)
}
}
@@ -513,6 +513,10 @@
return a.sdkVersion()
}
+func (a *AARImport) noFrameworkLibs() bool {
+ return false
+}
+
var _ AndroidLibraryDependency = (*AARImport)(nil)
func (a *AARImport) ExportPackage() android.Path {
diff --git a/java/androidmk.go b/java/androidmk.go
index 5491b3e..39c2d13 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -97,14 +97,6 @@
if library.proguardDictionary != nil {
fmt.Fprintln(w, "LOCAL_SOONG_PROGUARD_DICT :=", library.proguardDictionary.String())
}
-
- // Temporary hack: export sources used to compile framework.jar to Make
- // to be used for droiddoc
- // TODO(ccross): remove this once droiddoc is in soong
- if (library.Name() == "framework") || (library.Name() == "framework-annotation-proc") {
- fmt.Fprintln(w, "SOONG_FRAMEWORK_SRCS :=", strings.Join(library.compiledJavaSrcs.Strings(), " "))
- fmt.Fprintln(w, "SOONG_FRAMEWORK_SRCJARS :=", strings.Join(library.compiledSrcJars.Strings(), " "))
- }
},
},
Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
diff --git a/java/app.go b/java/app.go
index cf9354f..cab97de 100644
--- a/java/app.go
+++ b/java/app.go
@@ -159,8 +159,9 @@
ctx.PropertyErrorf("stl", "sdk_version must be set in order to use c++_shared")
}
- if !Bool(a.properties.No_framework_libs) && !Bool(a.properties.No_standard_libs) {
- a.aapt.deps(ctx, sdkContext(a))
+ sdkDep := decodeSdkDep(ctx, sdkContext(a))
+ if sdkDep.hasFrameworkLibs() {
+ a.aapt.deps(ctx, sdkDep)
}
embedJni := a.shouldEmbedJnis(ctx)
@@ -180,7 +181,7 @@
}
}
- a.usesLibrary.deps(ctx, Bool(a.properties.No_framework_libs))
+ a.usesLibrary.deps(ctx, sdkDep.hasFrameworkLibs())
}
func (a *AndroidApp) OverridablePropertiesDepsMutator(ctx android.BottomUpMutatorContext) {
@@ -783,7 +784,7 @@
ctx.AddDependency(ctx.Module(), certificateTag, cert)
}
- a.usesLibrary.deps(ctx, false)
+ a.usesLibrary.deps(ctx, true)
}
func (a *AndroidAppImport) uncompressEmbeddedJniLibs(
@@ -937,17 +938,22 @@
usesLibraryProperties UsesLibraryProperties
}
-func (u *usesLibrary) deps(ctx android.BottomUpMutatorContext, noFrameworkLibs bool) {
- ctx.AddVariationDependencies(nil, usesLibTag, u.usesLibraryProperties.Uses_libs...)
- ctx.AddVariationDependencies(nil, usesLibTag, u.presentOptionalUsesLibs(ctx)...)
- if !noFrameworkLibs {
- // dexpreopt/dexpreopt.go needs the paths to the dex jars of these libraries in case construct_context.sh needs
- // to pass them to dex2oat. Add them as a dependency so we can determine the path to the dex jar of each
- // library to dexpreopt.
- ctx.AddVariationDependencies(nil, usesLibTag,
- "org.apache.http.legacy",
- "android.hidl.base-V1.0-java",
- "android.hidl.manager-V1.0-java")
+func (u *usesLibrary) deps(ctx android.BottomUpMutatorContext, hasFrameworkLibs bool) {
+ if !ctx.Config().UnbundledBuild() {
+ ctx.AddVariationDependencies(nil, usesLibTag, u.usesLibraryProperties.Uses_libs...)
+ ctx.AddVariationDependencies(nil, usesLibTag, u.presentOptionalUsesLibs(ctx)...)
+ // Only add these extra dependencies if the module depends on framework libs. This avoids
+ // creating a cyclic dependency:
+ // e.g. framework-res -> org.apache.http.legacy -> ... -> framework-res.
+ if hasFrameworkLibs {
+ // dexpreopt/dexpreopt.go needs the paths to the dex jars of these libraries in case construct_context.sh needs
+ // to pass them to dex2oat. Add them as a dependency so we can determine the path to the dex jar of each
+ // library to dexpreopt.
+ ctx.AddVariationDependencies(nil, usesLibTag,
+ "org.apache.http.legacy",
+ "android.hidl.base-V1.0-java",
+ "android.hidl.manager-V1.0-java")
+ }
}
}
diff --git a/java/app_test.go b/java/app_test.go
index bb39c16..27802cd 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -546,7 +546,7 @@
}
}
-func TestJNIABI(t *testing.T) {
+func TestJNIABI_no_framework_libs_true(t *testing.T) {
ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
cc_library {
name: "libjni",
@@ -619,7 +619,80 @@
}
}
-func TestJNIPackaging(t *testing.T) {
+func TestJNIABI(t *testing.T) {
+ ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
+ cc_library {
+ name: "libjni",
+ system_shared_libs: [],
+ stl: "none",
+ }
+
+ android_test {
+ name: "test",
+ sdk_version: "core_platform",
+ jni_libs: ["libjni"],
+ }
+
+ android_test {
+ name: "test_first",
+ sdk_version: "core_platform",
+ compile_multilib: "first",
+ jni_libs: ["libjni"],
+ }
+
+ android_test {
+ name: "test_both",
+ sdk_version: "core_platform",
+ compile_multilib: "both",
+ jni_libs: ["libjni"],
+ }
+
+ android_test {
+ name: "test_32",
+ sdk_version: "core_platform",
+ compile_multilib: "32",
+ jni_libs: ["libjni"],
+ }
+
+ android_test {
+ name: "test_64",
+ sdk_version: "core_platform",
+ compile_multilib: "64",
+ jni_libs: ["libjni"],
+ }
+ `)
+
+ testCases := []struct {
+ name string
+ abis []string
+ }{
+ {"test", []string{"arm64-v8a"}},
+ {"test_first", []string{"arm64-v8a"}},
+ {"test_both", []string{"arm64-v8a", "armeabi-v7a"}},
+ {"test_32", []string{"armeabi-v7a"}},
+ {"test_64", []string{"arm64-v8a"}},
+ }
+
+ for _, test := range testCases {
+ t.Run(test.name, func(t *testing.T) {
+ app := ctx.ModuleForTests(test.name, "android_common")
+ jniLibZip := app.Output("jnilibs.zip")
+ var abis []string
+ args := strings.Fields(jniLibZip.Args["jarArgs"])
+ for i := 0; i < len(args); i++ {
+ if args[i] == "-P" {
+ abis = append(abis, filepath.Base(args[i+1]))
+ i++
+ }
+ }
+ if !reflect.DeepEqual(abis, test.abis) {
+ t.Errorf("want abis %v, got %v", test.abis, abis)
+ }
+ })
+ }
+}
+
+func TestJNIPackaging_no_framework_libs_true(t *testing.T) {
ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
cc_library {
name: "libjni",
@@ -700,7 +773,89 @@
}
})
}
+}
+func TestJNIPackaging(t *testing.T) {
+ ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
+ cc_library {
+ name: "libjni",
+ system_shared_libs: [],
+ stl: "none",
+ }
+
+ android_app {
+ name: "app",
+ jni_libs: ["libjni"],
+ }
+
+ android_app {
+ name: "app_noembed",
+ jni_libs: ["libjni"],
+ use_embedded_native_libs: false,
+ }
+
+ android_app {
+ name: "app_embed",
+ jni_libs: ["libjni"],
+ use_embedded_native_libs: true,
+ }
+
+ android_test {
+ name: "test",
+ sdk_version: "core_platform",
+ jni_libs: ["libjni"],
+ }
+
+ android_test {
+ name: "test_noembed",
+ sdk_version: "core_platform",
+ jni_libs: ["libjni"],
+ use_embedded_native_libs: false,
+ }
+
+ android_test_helper_app {
+ name: "test_helper",
+ sdk_version: "core_platform",
+ jni_libs: ["libjni"],
+ }
+
+ android_test_helper_app {
+ name: "test_helper_noembed",
+ sdk_version: "core_platform",
+ jni_libs: ["libjni"],
+ use_embedded_native_libs: false,
+ }
+ `)
+
+ testCases := []struct {
+ name string
+ packaged bool
+ compressed bool
+ }{
+ {"app", false, false},
+ {"app_noembed", false, false},
+ {"app_embed", true, false},
+ {"test", true, false},
+ {"test_noembed", true, true},
+ {"test_helper", true, false},
+ {"test_helper_noembed", true, true},
+ }
+
+ for _, test := range testCases {
+ t.Run(test.name, func(t *testing.T) {
+ app := ctx.ModuleForTests(test.name, "android_common")
+ jniLibZip := app.MaybeOutput("jnilibs.zip")
+ if g, w := (jniLibZip.Rule != nil), test.packaged; g != w {
+ t.Errorf("expected jni packaged %v, got %v", w, g)
+ }
+
+ if jniLibZip.Rule != nil {
+ if g, w := !strings.Contains(jniLibZip.Args["jarArgs"], "-L 0"), test.compressed; g != w {
+ t.Errorf("expected jni compressed %v, got %v", w, g)
+ }
+ }
+ })
+ }
}
func TestCertificates(t *testing.T) {
diff --git a/java/builder.go b/java/builder.go
index e1a912b..a48e8b1 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -148,15 +148,16 @@
}
type javaBuilderFlags struct {
- javacFlags string
- bootClasspath classpath
- classpath classpath
- processorPath classpath
- processor string
- systemModules classpath
- aidlFlags string
- aidlDeps android.Paths
- javaVersion string
+ javacFlags string
+ bootClasspath classpath
+ classpath classpath
+ processorPath classpath
+ processor string
+ systemModules classpath
+ systemModulesDeps android.Paths
+ aidlFlags string
+ aidlDeps android.Paths
+ javaVersion string
errorProneExtraJavacFlags string
errorProneProcessorPath classpath
@@ -248,7 +249,7 @@
var bootClasspath string
if flags.javaVersion == "1.9" {
- deps = append(deps, flags.systemModules...)
+ deps = append(deps, flags.systemModulesDeps...)
bootClasspath = flags.systemModules.FormJavaSystemModulesPath("--system=", ctx.Device())
} else {
deps = append(deps, flags.bootClasspath...)
@@ -430,7 +431,7 @@
if len(*x) > 1 {
panic("more than one system module")
} else if len(*x) == 1 {
- return optName + strings.TrimSuffix((*x)[0].String(), "lib/modules")
+ return optName + (*x)[0].String()
} else if forceEmpty {
return optName + "none"
} else {
diff --git a/java/config/kotlin.go b/java/config/kotlin.go
index 7cea042..fd8e3db 100644
--- a/java/config/kotlin.go
+++ b/java/config/kotlin.go
@@ -32,6 +32,7 @@
pctx.SourcePathVariable("KotlinScriptRuntimeJar", "external/kotlinc/lib/kotlin-script-runtime.jar")
pctx.SourcePathVariable("KotlinTrove4jJar", "external/kotlinc/lib/trove4j.jar")
pctx.SourcePathVariable("KotlinKaptJar", "external/kotlinc/lib/kotlin-annotation-processing.jar")
+ pctx.SourcePathVariable("KotlinAnnotationJar", "external/kotlinc/lib/annotations-13.0.jar")
pctx.SourcePathVariable("KotlinStdlibJar", KotlinStdlibJar)
// These flags silence "Illegal reflective access" warnings when running kotlinc in OpenJDK9
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 23d2aa6..ed12fe6 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -132,8 +132,10 @@
}
var images android.Paths
+ var imagesDeps []android.Paths
for _, arch := range archs {
images = append(images, bootImage.images[arch])
+ imagesDeps = append(imagesDeps, bootImage.imagesDeps[arch])
}
dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath)
@@ -173,8 +175,9 @@
UsesLibraries: d.usesLibs,
LibraryPaths: d.libraryPaths,
- Archs: archs,
- DexPreoptImages: images,
+ Archs: archs,
+ DexPreoptImages: images,
+ DexPreoptImagesDeps: imagesDeps,
// We use the dex paths and dex locations of the default boot image, as it
// contains the full dexpreopt boot classpath. Other images may just contain a subset of
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 2a1a901..eb735c1 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -58,9 +58,32 @@
symbolsDir android.OutputPath
targets []android.Target
images map[android.ArchType]android.OutputPath
+ imagesDeps map[android.ArchType]android.Paths
zip android.WritablePath
}
+func (image bootImageConfig) moduleFiles(ctx android.PathContext, dir android.OutputPath, exts ...string) []android.OutputPath {
+ ret := make([]android.OutputPath, 0, len(image.modules)*len(exts))
+
+ // dex preopt on the bootclasspath produces multiple files. The first dex file
+ // is converted into to 'name'.art (to match the legacy assumption that 'name'.art
+ // exists), and the rest are converted to 'name'-<jar>.art.
+ // In addition, each .art file has an associated .oat and .vdex file, and an
+ // unstripped .oat file
+ for i, m := range image.modules {
+ name := image.name
+ if i != 0 {
+ name += "-" + m
+ }
+
+ for _, ext := range exts {
+ ret = append(ret, dir.Join(ctx, name+ext))
+ }
+ }
+
+ return ret
+}
+
type bootImage struct {
bootImageConfig
@@ -302,49 +325,38 @@
installDir := filepath.Join("/system/framework", arch.String())
vdexInstallDir := filepath.Join("/system/framework")
- var extraFiles android.WritablePaths
var vdexInstalls android.RuleBuilderInstalls
var unstrippedInstalls android.RuleBuilderInstalls
var zipFiles android.WritablePaths
- // dex preopt on the bootclasspath produces multiple files. The first dex file
- // is converted into to 'name'.art (to match the legacy assumption that 'name'.art
- // exists), and the rest are converted to 'name'-<jar>.art.
- // In addition, each .art file has an associated .oat and .vdex file, and an
- // unstripped .oat file
- for i, m := range image.modules {
- name := image.name
- if i != 0 {
- name += "-" + m
- }
+ for _, artOrOat := range image.moduleFiles(ctx, outputDir, ".art", ".oat") {
+ cmd.ImplicitOutput(artOrOat)
+ zipFiles = append(zipFiles, artOrOat)
- art := outputDir.Join(ctx, name+".art")
- oat := outputDir.Join(ctx, name+".oat")
- vdex := outputDir.Join(ctx, name+".vdex")
- unstrippedOat := symbolsDir.Join(ctx, name+".oat")
+ // Install the .oat and .art files
+ rule.Install(artOrOat, filepath.Join(installDir, artOrOat.Base()))
+ }
- extraFiles = append(extraFiles, art, oat, vdex, unstrippedOat)
-
- zipFiles = append(zipFiles, art, oat, vdex)
-
- // Install the .oat and .art files.
- rule.Install(art, filepath.Join(installDir, art.Base()))
- rule.Install(oat, filepath.Join(installDir, oat.Base()))
+ for _, vdex := range image.moduleFiles(ctx, outputDir, ".vdex") {
+ cmd.ImplicitOutput(vdex)
+ zipFiles = append(zipFiles, vdex)
// The vdex files are identical between architectures, install them to a shared location. The Make rules will
// only use the install rules for one architecture, and will create symlinks into the architecture-specific
// directories.
vdexInstalls = append(vdexInstalls,
android.RuleBuilderInstall{vdex, filepath.Join(vdexInstallDir, vdex.Base())})
+ }
+
+ for _, unstrippedOat := range image.moduleFiles(ctx, symbolsDir, ".oat") {
+ cmd.ImplicitOutput(unstrippedOat)
// Install the unstripped oat files. The Make rules will put these in $(TARGET_OUT_UNSTRIPPED)
unstrippedInstalls = append(unstrippedInstalls,
android.RuleBuilderInstall{unstrippedOat, filepath.Join(installDir, unstrippedOat.Base())})
}
- cmd.ImplicitOutputs(extraFiles)
-
rule.Build(pctx, ctx, image.name+"JarsDexpreopt_"+arch.String(), "dexpreopt "+image.name+" jars "+arch.String())
// save output and installed files for makevars
@@ -496,6 +508,7 @@
for _, arch := range arches {
ctx.Strict("DEXPREOPT_IMAGE_VDEX_BUILT_INSTALLED_"+current.name+"_"+arch.String(), current.vdexInstalls[arch].String())
ctx.Strict("DEXPREOPT_IMAGE_"+current.name+"_"+arch.String(), current.images[arch].String())
+ ctx.Strict("DEXPREOPT_IMAGE_DEPS_"+current.name+"_"+arch.String(), strings.Join(current.imagesDeps[arch].Strings(), " "))
ctx.Strict("DEXPREOPT_IMAGE_BUILT_INSTALLED_"+current.name+"_"+arch.String(), current.installs[arch].String())
ctx.Strict("DEXPREOPT_IMAGE_UNSTRIPPED_BUILT_INSTALLED_"+current.name+"_"+arch.String(), current.unstrippedInstalls[arch].String())
if current.zip != nil {
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index d903f45..c396d3e 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -137,27 +137,35 @@
dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "dex_bootjars")
symbolsDir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "dex_bootjars_unstripped")
- images := make(map[android.ArchType]android.OutputPath)
zip := dir.Join(ctx, "boot.zip")
targets := dexpreoptTargets(ctx)
- for _, target := range targets {
- images[target.Arch.ArchType] = dir.Join(ctx,
- "system/framework", target.Arch.ArchType.String()).Join(ctx, "boot.art")
- }
-
- return bootImageConfig{
+ imageConfig := bootImageConfig{
name: "boot",
modules: nonUpdatableBootModules,
dexLocations: nonUpdatableBootLocations,
dexPaths: nonUpdatableBootDexPaths,
dir: dir,
symbolsDir: symbolsDir,
- images: images,
+ images: make(map[android.ArchType]android.OutputPath),
+ imagesDeps: make(map[android.ArchType]android.Paths),
targets: targets,
zip: zip,
}
+
+ for _, target := range targets {
+ imageDir := dir.Join(ctx, "system/framework", target.Arch.ArchType.String())
+ imageConfig.images[target.Arch.ArchType] = imageDir.Join(ctx, "boot.art")
+
+ imagesDeps := make([]android.Path, 0, len(imageConfig.modules)*3)
+ for _, dep := range imageConfig.moduleFiles(ctx, imageDir, ".art", ".oat", ".vdex") {
+ imagesDeps = append(imagesDeps, dep)
+ }
+ imageConfig.imagesDeps[target.Arch.ArchType] = imagesDeps
+ }
+
+ return imageConfig
}).(bootImageConfig)
}
@@ -196,16 +204,10 @@
dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "dex_apexjars")
symbolsDir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "dex_apexjars_unstripped")
- images := make(map[android.ArchType]android.OutputPath)
targets := dexpreoptTargets(ctx)
- for _, target := range targets {
- images[target.Arch.ArchType] = dir.Join(ctx,
- "system/framework", target.Arch.ArchType.String(), "apex.art")
- }
-
- return bootImageConfig{
+ imageConfig := bootImageConfig{
name: "apex",
modules: imageModules,
dexLocations: bootLocations,
@@ -213,8 +215,22 @@
dir: dir,
symbolsDir: symbolsDir,
targets: targets,
- images: images,
+ images: make(map[android.ArchType]android.OutputPath),
+ imagesDeps: make(map[android.ArchType]android.Paths),
}
+
+ for _, target := range targets {
+ imageDir := dir.Join(ctx, "system/framework", target.Arch.ArchType.String())
+ imageConfig.images[target.Arch.ArchType] = imageDir.Join(ctx, "apex.art")
+
+ imagesDeps := make([]android.Path, 0, len(imageConfig.modules)*3)
+ for _, dep := range imageConfig.moduleFiles(ctx, imageDir, ".art", ".oat", ".vdex") {
+ imagesDeps = append(imagesDeps, dep)
+ }
+ imageConfig.imagesDeps[target.Arch.ArchType] = imagesDeps
+ }
+
+ return imageConfig
}).(bootImageConfig)
}
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 992c8b5..be1b281 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -171,10 +171,6 @@
// list of java libraries that will be in the classpath.
Libs []string `android:"arch_variant"`
- // don't build against the default libraries (bootclasspath, ext, and framework for device
- // targets)
- No_standard_libs *bool
-
// don't build against the framework libraries (ext, and framework for device targets)
No_framework_libs *bool
@@ -538,16 +534,20 @@
return j.sdkVersion()
}
+func (j *Javadoc) noFrameworkLibs() bool {
+ return Bool(j.properties.No_framework_libs)
+}
+
func (j *Javadoc) addDeps(ctx android.BottomUpMutatorContext) {
if ctx.Device() {
- if !Bool(j.properties.No_standard_libs) {
- sdkDep := decodeSdkDep(ctx, sdkContext(j))
+ sdkDep := decodeSdkDep(ctx, sdkContext(j))
+ if sdkDep.hasStandardLibs() {
if sdkDep.useDefaultLibs {
ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...)
if ctx.Config().TargetOpenJDK9() {
ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules)
}
- if !Bool(j.properties.No_framework_libs) {
+ if sdkDep.hasFrameworkLibs() {
ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...)
}
} else if sdkDep.useModule {
@@ -692,10 +692,11 @@
panic("Found two system module dependencies")
}
sm := module.(*SystemModules)
- if sm.outputFile == nil {
+ if sm.outputDir == nil && len(sm.outputDeps) == 0 {
panic("Missing directory for system module dependency")
}
- deps.systemModules = sm.outputFile
+ deps.systemModules = sm.outputDir
+ deps.systemModulesDeps = sm.outputDeps
}
})
// do not pass exclude_srcs directly when expanding srcFiles since exclude_srcs
@@ -776,6 +777,7 @@
if deps.systemModules != nil {
systemModules = append(systemModules, deps.systemModules)
}
+ implicits = append(implicits, deps.systemModulesDeps...)
bootClasspathArgs = systemModules.FormJavaSystemModulesPath("--system ", ctx.Device())
bootClasspathArgs = bootClasspathArgs + " --patch-module java.base=."
}
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index b1ddab4..cf9f492 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -15,11 +15,14 @@
package java
import (
+ "fmt"
+
"android/soong/android"
)
func init() {
android.RegisterSingletonType("hiddenapi", hiddenAPISingletonFactory)
+ android.RegisterModuleType("hiddenapi_flags", hiddenAPIFlagsFactory)
}
type hiddenAPISingletonPathsStruct struct {
@@ -307,3 +310,48 @@
Text("fi").
Text(")")
}
+
+type hiddenAPIFlagsProperties struct {
+ // name of the file into which the flags will be copied.
+ Filename *string
+}
+
+type hiddenAPIFlags struct {
+ android.ModuleBase
+
+ properties hiddenAPIFlagsProperties
+
+ outputFilePath android.OutputPath
+}
+
+func (h *hiddenAPIFlags) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ filename := String(h.properties.Filename)
+
+ inputPath := hiddenAPISingletonPaths(ctx).flags
+ h.outputFilePath = android.PathForModuleOut(ctx, filename).OutputPath
+
+ // This ensures that outputFilePath has the correct name for others to
+ // use, as the source file may have a different name.
+ ctx.Build(pctx, android.BuildParams{
+ Rule: android.Cp,
+ Output: h.outputFilePath,
+ Input: inputPath,
+ })
+}
+
+func (h *hiddenAPIFlags) OutputFiles(tag string) (android.Paths, error) {
+ switch tag {
+ case "":
+ return android.Paths{h.outputFilePath}, nil
+ default:
+ return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+ }
+}
+
+// hiddenapi-flags provides access to the hiddenapi-flags.csv file generated during the build.
+func hiddenAPIFlagsFactory() android.Module {
+ module := &hiddenAPIFlags{}
+ module.AddProperties(&module.properties)
+ android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
+ return module
+}
diff --git a/java/java.go b/java/java.go
index a1addb3..a2e9ab0 100644
--- a/java/java.go
+++ b/java/java.go
@@ -82,10 +82,6 @@
// list of files that should be excluded from java_resources and java_resource_dirs
Exclude_java_resources []string `android:"path,arch_variant"`
- // don't build against the default libraries (bootclasspath, ext, and framework for device
- // targets)
- No_standard_libs *bool
-
// don't build against the framework libraries (ext, and framework for device targets)
No_framework_libs *bool
@@ -440,6 +436,16 @@
jars android.Paths
aidl android.OptionalPath
+
+ noStandardLibs, noFrameworksLibs bool
+}
+
+func (s sdkDep) hasStandardLibs() bool {
+ return !s.noStandardLibs
+}
+
+func (s sdkDep) hasFrameworkLibs() bool {
+ return !s.noStandardLibs && !s.noFrameworksLibs
}
type jniLib struct {
@@ -476,14 +482,18 @@
return j.sdkVersion()
}
+func (j *Module) noFrameworkLibs() bool {
+ return Bool(j.properties.No_framework_libs)
+}
+
func (j *Module) deps(ctx android.BottomUpMutatorContext) {
if ctx.Device() {
- if !Bool(j.properties.No_standard_libs) {
- sdkDep := decodeSdkDep(ctx, sdkContext(j))
+ sdkDep := decodeSdkDep(ctx, sdkContext(j))
+ if sdkDep.hasStandardLibs() {
if sdkDep.useDefaultLibs {
ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...)
ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules)
- if !Bool(j.properties.No_framework_libs) {
+ if sdkDep.hasFrameworkLibs() {
ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...)
}
} else if sdkDep.useModule {
@@ -495,8 +505,8 @@
}
}
} else if j.deviceProperties.System_modules == nil {
- ctx.PropertyErrorf("no_standard_libs",
- "system_modules is required to be set when no_standard_libs is true, did you mean no_framework_libs?")
+ ctx.PropertyErrorf("sdk_version",
+ `system_modules is required to be set when sdk_version is "none", did you mean no_framework_libs?`)
} else if *j.deviceProperties.System_modules != "none" {
ctx.AddVariationDependencies(nil, systemModulesTag, *j.deviceProperties.System_modules)
}
@@ -618,6 +628,7 @@
srcs android.Paths
srcJars android.Paths
systemModules android.Path
+ systemModulesDeps android.Paths
aidlPreprocess android.OptionalPath
kotlinStdlib android.Paths
kotlinAnnotations android.Paths
@@ -664,7 +675,7 @@
return javaSdk, true
case ver == "current":
return javaSdk, false
- case ver == "":
+ case ver == "" || ver == "none" || ver == "core_platform":
return javaPlatform, false
default:
if _, err := strconv.Atoi(ver); err != nil {
@@ -825,10 +836,11 @@
panic("Found two system module dependencies")
}
sm := module.(*SystemModules)
- if sm.outputFile == nil {
+ if sm.outputDir == nil || len(sm.outputDeps) == 0 {
panic("Missing directory for system module dependency")
}
- deps.systemModules = sm.outputFile
+ deps.systemModules = sm.outputDir
+ deps.systemModulesDeps = sm.outputDeps
}
}
})
@@ -842,7 +854,8 @@
var ret string
v := sdkContext.sdkVersion()
// For PDK builds, use the latest SDK version instead of "current"
- if ctx.Config().IsPdkBuild() && (v == "" || v == "current") {
+ if ctx.Config().IsPdkBuild() &&
+ (v == "" || v == "none" || v == "core_platform" || v == "current") {
sdkVersions := ctx.Config().Get(sdkVersionsKey).([]int)
latestSdkVersion := 0
if len(sdkVersions) > 0 {
@@ -861,7 +874,11 @@
ret = "1.7"
} else if ctx.Device() && sdk <= 29 || !ctx.Config().TargetOpenJDK9() {
ret = "1.8"
- } else if ctx.Device() && sdkContext.sdkVersion() != "" && sdk == android.FutureApiLevel {
+ } else if ctx.Device() &&
+ sdkContext.sdkVersion() != "" &&
+ sdkContext.sdkVersion() != "none" &&
+ sdkContext.sdkVersion() != "core_platform" &&
+ 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 {
@@ -913,7 +930,7 @@
flags.processor = strings.Join(deps.processorClasses, ",")
if len(flags.bootClasspath) == 0 && ctx.Host() && flags.javaVersion != "1.9" &&
- !Bool(j.properties.No_standard_libs) &&
+ decodeSdkDep(ctx, sdkContext(j)).hasStandardLibs() &&
inList(flags.javaVersion, []string{"1.6", "1.7", "1.8"}) {
// Give host-side tools a version of OpenJDK's standard libraries
// close to what they're targeting. As of Dec 2017, AOSP is only
@@ -953,6 +970,7 @@
// systemModules
if deps.systemModules != nil {
flags.systemModules = append(flags.systemModules, deps.systemModules)
+ flags.systemModulesDeps = append(flags.systemModulesDeps, deps.systemModulesDeps...)
}
// aidl flags.
diff --git a/java/java_test.go b/java/java_test.go
index 4d161c5..22dec07 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -212,7 +212,7 @@
setDexpreoptTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
ctx.Register()
- _, errs := ctx.ParseFileList(".", []string{"Android.bp", "prebuilts/sdk/Android.bp"})
+ _, errs := ctx.ParseBlueprintsFiles("Android.bp")
android.FailIfErrored(t, errs)
_, errs = ctx.PrepareBuildActions(config)
android.FailIfErrored(t, errs)
@@ -842,6 +842,19 @@
}
}
+func TestJavaLibrary(t *testing.T) {
+ config := testConfig(nil)
+ ctx := testContext(config, "", map[string][]byte{
+ "libcore/Android.bp": []byte(`
+ java_library {
+ name: "core",
+ sdk_version: "none",
+ system_modules: "none",
+ }`),
+ })
+ run(t, ctx, config)
+}
+
func TestJavaSdkLibrary(t *testing.T) {
ctx := testJava(t, `
droiddoc_template {
@@ -1000,7 +1013,7 @@
java_library {
name: "bar",
srcs: ["b.java"],
- no_standard_libs: true,
+ sdk_version: "none",
system_modules: "none",
patch_module: "java.base",
}
diff --git a/java/kotlin.go b/java/kotlin.go
index 33167ba..8306907 100644
--- a/java/kotlin.go
+++ b/java/kotlin.go
@@ -44,6 +44,7 @@
"${config.KotlinScriptRuntimeJar}",
"${config.KotlinStdlibJar}",
"${config.KotlinTrove4jJar}",
+ "${config.KotlinAnnotationJar}",
"${config.GenKotlinBuildFileCmd}",
"${config.SoongZipCmd}",
"${config.ZipSyncCmd}",
diff --git a/java/robolectric.go b/java/robolectric.go
index b87ee0d..1de56a5 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -17,6 +17,7 @@
import (
"fmt"
"io"
+ "strconv"
"strings"
"android/soong/android"
@@ -40,6 +41,9 @@
Test_options struct {
// Timeout in seconds when running the tests.
Timeout *int64
+
+ // Number of shards to use when running the tests.
+ Shards *int64
}
}
@@ -48,7 +52,8 @@
robolectricProperties robolectricProperties
- libs []string
+ libs []string
+ tests []string
}
func (r *robolectricTest) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -69,6 +74,39 @@
for _, dep := range ctx.GetDirectDepsWithTag(libTag) {
r.libs = append(r.libs, ctx.OtherModuleName(dep))
}
+
+ // TODO: this could all be removed if tradefed was used as the test runner, it will find everything
+ // annotated as a test and run it.
+ for _, src := range r.compiledJavaSrcs {
+ s := src.Rel()
+ if !strings.HasSuffix(s, "Test.java") {
+ continue
+ } else if strings.HasSuffix(s, "/BaseRobolectricTest.java") {
+ continue
+ } else if strings.HasPrefix(s, "src/") {
+ s = strings.TrimPrefix(s, "src/")
+ }
+ r.tests = append(r.tests, s)
+ }
+}
+
+func shardTests(paths []string, shards int) [][]string {
+ if shards > len(paths) {
+ shards = len(paths)
+ }
+ if shards == 0 {
+ return nil
+ }
+ ret := make([][]string, 0, shards)
+ shardSize := (len(paths) + shards - 1) / shards
+ for len(paths) > shardSize {
+ ret = append(ret, paths[0:shardSize])
+ paths = paths[shardSize:]
+ }
+ if len(paths) > 0 {
+ ret = append(ret, paths)
+ }
+ return ret
}
func (r *robolectricTest) AndroidMk() android.AndroidMkData {
@@ -77,24 +115,50 @@
data.Custom = func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
android.WriteAndroidMkData(w, data)
- fmt.Fprintln(w, "")
- fmt.Fprintln(w, "include $(CLEAR_VARS)")
- fmt.Fprintln(w, "LOCAL_MODULE := Run"+name)
- fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES :=", name)
- fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES += ", strings.Join(r.libs, " "))
- fmt.Fprintln(w, "LOCAL_TEST_PACKAGE :=", String(r.robolectricProperties.Instrumentation_for))
- if t := r.robolectricProperties.Test_options.Timeout; t != nil {
- fmt.Fprintln(w, "LOCAL_ROBOTEST_TIMEOUT :=", *t)
+ if s := r.robolectricProperties.Test_options.Shards; s != nil && *s > 1 {
+ shards := shardTests(r.tests, int(*s))
+ for i, shard := range shards {
+ r.writeTestRunner(w, name, "Run"+name+strconv.Itoa(i), shard)
+ }
+
+ // TODO: add rules to dist the outputs of the individual tests, or combine them together?
+ fmt.Fprintln(w, "")
+ fmt.Fprintln(w, ".PHONY:", "Run"+name)
+ fmt.Fprintln(w, "Run"+name, ": \\")
+ for i := range shards {
+ fmt.Fprintln(w, " ", "Run"+name+strconv.Itoa(i), "\\")
+ }
+ fmt.Fprintln(w, "")
+ } else {
+ r.writeTestRunner(w, name, "Run"+name, r.tests)
}
- fmt.Fprintln(w, "-include external/robolectric-shadows/run_robotests.mk")
}
return data
}
+func (r *robolectricTest) writeTestRunner(w io.Writer, module, name string, tests []string) {
+ fmt.Fprintln(w, "")
+ fmt.Fprintln(w, "include $(CLEAR_VARS)")
+ fmt.Fprintln(w, "LOCAL_MODULE :=", name)
+ fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES :=", module)
+ fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES += ", strings.Join(r.libs, " "))
+ fmt.Fprintln(w, "LOCAL_TEST_PACKAGE :=", String(r.robolectricProperties.Instrumentation_for))
+ fmt.Fprintln(w, "LOCAL_ROBOTEST_FILES :=", strings.Join(tests, " "))
+ if t := r.robolectricProperties.Test_options.Timeout; t != nil {
+ fmt.Fprintln(w, "LOCAL_ROBOTEST_TIMEOUT :=", *t)
+ }
+ fmt.Fprintln(w, "-include external/robolectric-shadows/run_robotests.mk")
+
+}
+
// An android_robolectric_test module compiles tests against the Robolectric framework that can run on the local host
// instead of on a device. It also generates a rule with the name of the module prefixed with "Run" that can be
// used to run the tests. Running the tests with build rule will eventually be deprecated and replaced with atest.
+//
+// The test runner considers any file listed in srcs whose name ends with Test.java to be a test class, unless
+// it is named BaseRobolectricTest.java. The path to the each source file must exactly match the package
+// name, or match the package name when the prefix "src/" is removed.
func RobolectricTestFactory() android.Module {
module := &robolectricTest{}
diff --git a/java/robolectric_test.go b/java/robolectric_test.go
new file mode 100644
index 0000000..e89c6e7
--- /dev/null
+++ b/java/robolectric_test.go
@@ -0,0 +1,88 @@
+// Copyright 2019 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 java
+
+import (
+ "reflect"
+ "testing"
+)
+
+func Test_shardTests(t *testing.T) {
+ type args struct {
+ paths []string
+ shards int
+ }
+ tests := []struct {
+ name string
+ args args
+ want [][]string
+ }{
+ {
+ name: "empty",
+ args: args{
+ paths: nil,
+ shards: 1,
+ },
+ want: [][]string(nil),
+ },
+ {
+ name: "too many shards",
+ args: args{
+ paths: []string{"a", "b"},
+ shards: 3,
+ },
+ want: [][]string{{"a"}, {"b"}},
+ },
+ {
+ name: "single shard",
+ args: args{
+ paths: []string{"a", "b"},
+ shards: 1,
+ },
+ want: [][]string{{"a", "b"}},
+ },
+ {
+ name: "shard per input",
+ args: args{
+ paths: []string{"a", "b", "c"},
+ shards: 3,
+ },
+ want: [][]string{{"a"}, {"b"}, {"c"}},
+ },
+ {
+ name: "balanced shards",
+ args: args{
+ paths: []string{"a", "b", "c", "d"},
+ shards: 2,
+ },
+ want: [][]string{{"a", "b"}, {"c", "d"}},
+ },
+ {
+ name: "unbalanced shards",
+ args: args{
+ paths: []string{"a", "b", "c"},
+ shards: 2,
+ },
+ want: [][]string{{"a", "b"}, {"c"}},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := shardTests(tt.args.paths, tt.args.shards); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("shardTests() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/java/sdk.go b/java/sdk.go
index 90b8fac..6ffe399 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -38,17 +38,20 @@
var apiFingerprintPathKey = android.NewOnceKey("apiFingerprintPathKey")
type sdkContext interface {
- // sdkVersion eturns the sdk_version property of the current module, or an empty string if it is not set.
+ // sdkVersion returns 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
// targetSdkVersion returns the target_sdk_version property of the current module, or sdkVersion() if it is not set.
targetSdkVersion() string
+
+ // Temporarily provide access to the no_frameworks_libs property (where present).
+ noFrameworkLibs() bool
}
func sdkVersionOrDefault(ctx android.BaseModuleContext, v string) string {
switch v {
- case "", "current", "system_current", "test_current", "core_current":
+ case "", "none", "current", "test_current", "system_current", "core_current", "core_platform":
return ctx.Config().DefaultAppTargetSdk()
default:
return v
@@ -59,7 +62,7 @@
// it returns android.FutureApiLevel (10000).
func sdkVersionToNumber(ctx android.BaseModuleContext, v string) (int, error) {
switch v {
- case "", "current", "test_current", "system_current", "core_current":
+ case "", "none", "current", "test_current", "system_current", "core_current", "core_platform":
return ctx.Config().DefaultAppTargetSdkInt(), nil
default:
n := android.GetNumericSdkVersion(v)
@@ -138,6 +141,9 @@
useFiles: true,
jars: android.Paths{jarPath.Path(), lambdaStubsPath},
aidl: android.OptionalPathForPath(aidlPath.Path()),
+
+ // Pass value straight through for now to match previous behavior.
+ noFrameworksLibs: sdkContext.noFrameworkLibs(),
}
}
@@ -148,6 +154,9 @@
systemModules: m + "_system_modules",
frameworkResModule: r,
aidl: android.OptionalPathForPath(aidl),
+
+ // Pass value straight through for now to match previous behavior.
+ noFrameworksLibs: sdkContext.noFrameworkLibs(),
}
if m == "core.current.stubs" {
@@ -173,7 +182,8 @@
}
}
- if ctx.Config().UnbundledBuildUsePrebuiltSdks() && v != "" {
+ if ctx.Config().UnbundledBuildUsePrebuiltSdks() &&
+ v != "" && v != "none" && v != "core_platform" {
return toPrebuilt(v)
}
@@ -182,6 +192,19 @@
return sdkDep{
useDefaultLibs: true,
frameworkResModule: "framework-res",
+
+ // Pass value straight through for now to match previous behavior.
+ noFrameworksLibs: sdkContext.noFrameworkLibs(),
+ }
+ case "none":
+ return sdkDep{
+ noStandardLibs: true,
+ }
+ case "core_platform":
+ return sdkDep{
+ useDefaultLibs: true,
+ frameworkResModule: "framework-res",
+ noFrameworksLibs: true,
}
case "current":
return toModule("android_stubs_current", "framework-res", sdkFrameworkAidlPath(ctx))
diff --git a/java/sdk_library.go b/java/sdk_library.go
index e383533..b4a3f29 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -159,7 +159,8 @@
ctx.AddVariationDependencies(nil, publicApiStubsTag, module.stubsName(apiScopePublic))
ctx.AddVariationDependencies(nil, publicApiFileTag, module.docsName(apiScopePublic))
- if !Bool(module.properties.No_standard_libs) {
+ sdkDep := decodeSdkDep(ctx, sdkContext(&module.Library))
+ if sdkDep.hasStandardLibs() {
ctx.AddVariationDependencies(nil, systemApiStubsTag, module.stubsName(apiScopeSystem))
ctx.AddVariationDependencies(nil, systemApiFileTag, module.docsName(apiScopeSystem))
ctx.AddVariationDependencies(nil, testApiFileTag, module.docsName(apiScopeTest))
@@ -384,7 +385,6 @@
Device_specific *bool
Product_specific *bool
Compile_dex *bool
- No_standard_libs *bool
System_modules *string
Java_version *string
Product_variables struct {
@@ -401,17 +401,22 @@
}
}{}
+ sdkVersion := module.sdkVersion(apiScope)
+ sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library))
+ if !sdkDep.hasStandardLibs() {
+ sdkVersion = "none"
+ }
+
props.Name = proptools.StringPtr(module.stubsName(apiScope))
// sources are generated from the droiddoc
props.Srcs = []string{":" + module.docsName(apiScope)}
- props.Sdk_version = proptools.StringPtr(module.sdkVersion(apiScope))
+ props.Sdk_version = proptools.StringPtr(sdkVersion)
props.Libs = module.sdkLibraryProperties.Stub_only_libs
// Unbundled apps will use the prebult one from /prebuilts/sdk
if mctx.Config().UnbundledBuildUsePrebuiltSdks() {
props.Product_variables.Unbundled_build.Enabled = proptools.BoolPtr(false)
}
props.Product_variables.Pdk.Enabled = proptools.BoolPtr(false)
- props.No_standard_libs = module.Library.Module.properties.No_standard_libs
props.System_modules = module.Library.Module.deviceProperties.System_modules
props.Openjdk9.Srcs = module.Library.Module.properties.Openjdk9.Srcs
props.Openjdk9.Javacflags = module.Library.Module.properties.Openjdk9.Javacflags
@@ -441,13 +446,13 @@
Srcs_lib *string
Srcs_lib_whitelist_dirs []string
Srcs_lib_whitelist_pkgs []string
+ Sdk_version *string
Libs []string
Arg_files []string
Args *string
Api_tag_name *string
Api_filename *string
Removed_api_filename *string
- No_standard_libs *bool
Java_version *string
Merge_annotations_dirs []string
Merge_inclusion_annotations_dirs []string
@@ -462,9 +467,16 @@
}
}{}
+ sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library))
+ sdkVersion := ""
+ if !sdkDep.hasStandardLibs() {
+ sdkVersion = "none"
+ }
+
props.Name = proptools.StringPtr(module.docsName(apiScope))
props.Srcs = append(props.Srcs, module.Library.Module.properties.Srcs...)
props.Srcs = append(props.Srcs, module.sdkLibraryProperties.Api_srcs...)
+ props.Sdk_version = proptools.StringPtr(sdkVersion)
props.Installable = proptools.BoolPtr(false)
// A droiddoc module has only one Libs property and doesn't distinguish between
// shared libs and static libs. So we need to add both of these libs to Libs property.
@@ -472,7 +484,6 @@
props.Libs = append(props.Libs, module.Library.Module.properties.Static_libs...)
props.Aidl.Include_dirs = module.Library.Module.deviceProperties.Aidl.Include_dirs
props.Aidl.Local_include_dirs = module.Library.Module.deviceProperties.Aidl.Local_include_dirs
- props.No_standard_libs = module.Library.Module.properties.No_standard_libs
props.Java_version = module.Library.Module.properties.Java_version
props.Merge_annotations_dirs = module.sdkLibraryProperties.Merge_annotations_dirs
@@ -593,7 +604,7 @@
func (module *SdkLibrary) PrebuiltJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths {
var api, v string
- if sdkVersion == "" {
+ if sdkVersion == "" || sdkVersion == "none" {
api = "system"
v = "current"
} else if strings.Contains(sdkVersion, "_") {
@@ -701,7 +712,8 @@
module.createStubsLibrary(mctx, apiScopePublic)
module.createDocs(mctx, apiScopePublic)
- if !Bool(module.properties.No_standard_libs) {
+ sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library))
+ if sdkDep.hasStandardLibs() {
// for system API stubs
module.createStubsLibrary(mctx, apiScopeSystem)
module.createDocs(mctx, apiScopeSystem)
diff --git a/java/sdk_test.go b/java/sdk_test.go
index cc4da2e..953c372 100644
--- a/java/sdk_test.go
+++ b/java/sdk_test.go
@@ -47,6 +47,22 @@
aidl: "-Iframework/aidl",
},
{
+ name: "no_framework_libs:true",
+ properties: `no_framework_libs:true`,
+ bootclasspath: config.DefaultBootclasspathLibraries,
+ system: config.DefaultSystemModules,
+ classpath: []string{},
+ aidl: "",
+ },
+ {
+ name: `sdk_version:"core_platform"`,
+ properties: `sdk_version:"core_platform"`,
+ bootclasspath: config.DefaultBootclasspathLibraries,
+ system: config.DefaultSystemModules,
+ classpath: []string{},
+ aidl: "",
+ },
+ {
name: "blank sdk version",
properties: `sdk_version: "",`,
bootclasspath: config.DefaultBootclasspathLibraries,
@@ -106,7 +122,7 @@
{
name: "nostdlib",
- properties: `no_standard_libs: true, system_modules: "none"`,
+ properties: `sdk_version: "none", system_modules: "none"`,
system: "none",
bootclasspath: []string{`""`},
classpath: []string{},
@@ -114,7 +130,7 @@
{
name: "nostdlib system_modules",
- properties: `no_standard_libs: true, system_modules: "core-platform-api-stubs-system-modules"`,
+ properties: `sdk_version: "none", system_modules: "core-platform-api-stubs-system-modules"`,
system: "core-platform-api-stubs-system-modules",
bootclasspath: []string{`""`},
classpath: []string{},
@@ -129,13 +145,6 @@
classpath: []string{},
},
{
- name: "host nostdlib",
- moduleType: "java_library_host",
- host: android.Host,
- properties: `no_standard_libs: true`,
- classpath: []string{},
- },
- {
name: "host supported default",
host: android.Host,
@@ -146,7 +155,7 @@
{
name: "host supported nostdlib",
host: android.Host,
- properties: `host_supported: true, no_standard_libs: true, system_modules: "none"`,
+ properties: `host_supported: true, sdk_version: "none", system_modules: "none"`,
classpath: []string{},
},
{
@@ -245,7 +254,7 @@
if testcase.system == "none" {
system = "--system=none"
} else if testcase.system != "" {
- system = "--system=" + filepath.Join(buildDir, ".intermediates", testcase.system, "android_common", "system") + "/"
+ system = "--system=" + filepath.Join(buildDir, ".intermediates", testcase.system, "android_common", "system")
}
checkClasspath := func(t *testing.T, ctx *android.TestContext) {
diff --git a/java/system_modules.go b/java/system_modules.go
index 2ec3dfb..f71f452 100644
--- a/java/system_modules.go
+++ b/java/system_modules.go
@@ -61,7 +61,7 @@
"moduleName", "classpath", "outDir", "workDir")
)
-func TransformJarsToSystemModules(ctx android.ModuleContext, moduleName string, jars android.Paths) android.WritablePath {
+func TransformJarsToSystemModules(ctx android.ModuleContext, moduleName string, jars android.Paths) (android.Path, android.Paths) {
outDir := android.PathForModuleOut(ctx, "system")
workDir := android.PathForModuleOut(ctx, "modules")
outputFile := android.PathForModuleOut(ctx, "system/lib/modules")
@@ -84,7 +84,7 @@
},
})
- return outputFile
+ return outDir, outputs.Paths()
}
func SystemModulesFactory() android.Module {
@@ -101,18 +101,13 @@
properties SystemModulesProperties
- outputFile android.Path
+ outputDir android.Path
+ outputDeps android.Paths
}
type SystemModulesProperties struct {
// List of java library modules that should be included in the system modules
Libs []string
-
- // List of prebuilt jars that should be included in the system modules
- Jars []string
-
- // Sdk version that should be included in the system modules
- Sdk_version *string
}
func (system *SystemModules) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -123,9 +118,7 @@
jars = append(jars, dep.HeaderJars()...)
})
- jars = append(jars, android.PathsForModuleSrc(ctx, system.properties.Jars)...)
-
- system.outputFile = TransformJarsToSystemModules(ctx, "java.base", jars)
+ system.outputDir, system.outputDeps = TransformJarsToSystemModules(ctx, "java.base", jars)
}
func (system *SystemModules) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -135,16 +128,22 @@
func (system *SystemModules) AndroidMk() android.AndroidMkData {
return android.AndroidMkData{
Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
- makevar := "SOONG_SYSTEM_MODULES_" + name
fmt.Fprintln(w)
- fmt.Fprintln(w, makevar, ":=", system.outputFile.String())
- fmt.Fprintln(w, ".KATI_READONLY", ":=", makevar)
+
+ makevar := "SOONG_SYSTEM_MODULES_" + name
+ fmt.Fprintln(w, makevar, ":=$=", system.outputDir.String())
+ fmt.Fprintln(w)
+
+ makevar = "SOONG_SYSTEM_MODULES_LIBS_" + name
+ fmt.Fprintln(w, makevar, ":=$=", strings.Join(system.properties.Libs, " "))
+ fmt.Fprintln(w)
+
+ makevar = "SOONG_SYSTEM_MODULE_DEPS_" + name
+ fmt.Fprintln(w, makevar, ":=$=", strings.Join(system.outputDeps.Strings(), " "))
+ fmt.Fprintln(w)
+
fmt.Fprintln(w, name+":", "$("+makevar+")")
fmt.Fprintln(w, ".PHONY:", name)
- fmt.Fprintln(w)
- makevar = "SOONG_SYSTEM_MODULES_LIBS_" + name
- fmt.Fprintln(w, makevar, ":=", strings.Join(system.properties.Libs, " "))
- fmt.Fprintln(w, ".KATI_READONLY :=", makevar)
},
}
}
diff --git a/java/testing.go b/java/testing.go
index a9d4670..5d116a7 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -54,7 +54,7 @@
java_library {
name: "%s",
srcs: ["a.java"],
- no_standard_libs: true,
+ sdk_version: "none",
system_modules: "core-platform-api-stubs-system-modules",
}
`, extra)
@@ -64,7 +64,7 @@
java_library {
name: "framework",
srcs: ["a.java"],
- no_standard_libs: true,
+ sdk_version: "none",
system_modules: "core-platform-api-stubs-system-modules",
aidl: {
export_include_dirs: ["framework/aidl"],
@@ -73,13 +73,13 @@
android_app {
name: "framework-res",
- no_framework_libs: true,
+ sdk_version: "core_platform",
}
java_library {
name: "android.hidl.base-V1.0-java",
srcs: ["a.java"],
- no_standard_libs: true,
+ sdk_version: "none",
system_modules: "core-platform-api-stubs-system-modules",
installable: true,
}
@@ -87,7 +87,7 @@
java_library {
name: "android.hidl.manager-V1.0-java",
srcs: ["a.java"],
- no_standard_libs: true,
+ sdk_version: "none",
system_modules: "core-platform-api-stubs-system-modules",
installable: true,
}
@@ -95,7 +95,7 @@
java_library {
name: "org.apache.http.legacy",
srcs: ["a.java"],
- no_standard_libs: true,
+ sdk_version: "none",
system_modules: "core-platform-api-stubs-system-modules",
installable: true,
}
diff --git a/sysprop/sysprop_test.go b/sysprop/sysprop_test.go
index b470ba5..0566036 100644
--- a/sysprop/sysprop_test.go
+++ b/sysprop/sysprop_test.go
@@ -313,13 +313,13 @@
vendorVariant := "android_arm64_armv8-a_vendor_static"
platformInternalPath := "libsysprop-platform/android_arm64_armv8-a_core_static/gen/sysprop/include"
- platformSystemCorePath := "libsysprop-platform/android_arm64_armv8-a_core_static/gen/sysprop/system/include"
- platformSystemVendorPath := "libsysprop-platform/android_arm64_armv8-a_vendor_static/gen/sysprop/system/include"
+ platformPublicCorePath := "libsysprop-platform/android_arm64_armv8-a_core_static/gen/sysprop/public/include"
+ platformPublicVendorPath := "libsysprop-platform/android_arm64_armv8-a_vendor_static/gen/sysprop/public/include"
- platformOnProductPath := "libsysprop-platform-on-product/android_arm64_armv8-a_core_static/gen/sysprop/system/include"
+ platformOnProductPath := "libsysprop-platform-on-product/android_arm64_armv8-a_core_static/gen/sysprop/public/include"
vendorInternalPath := "libsysprop-vendor/android_arm64_armv8-a_vendor_static/gen/sysprop/include"
- vendorSystemPath := "libsysprop-vendor/android_arm64_armv8-a_core_static/gen/sysprop/system/include"
+ vendorPublicPath := "libsysprop-vendor/android_arm64_armv8-a_core_static/gen/sysprop/public/include"
platformClient := ctx.ModuleForTests("cc-client-platform", coreVariant)
platformFlags := platformClient.Rule("cc").Args["cFlags"]
@@ -342,20 +342,20 @@
productClient := ctx.ModuleForTests("cc-client-product", coreVariant)
productFlags := productClient.Rule("cc").Args["cFlags"]
- // Product should use platform's and vendor's system headers
+ // Product should use platform's and vendor's public headers
if !strings.Contains(productFlags, platformOnProductPath) ||
- !strings.Contains(productFlags, vendorSystemPath) {
+ !strings.Contains(productFlags, vendorPublicPath) {
t.Errorf("flags for product must contain %#v and %#v, but was %#v.",
- platformSystemCorePath, vendorSystemPath, productFlags)
+ platformPublicCorePath, vendorPublicPath, productFlags)
}
vendorClient := ctx.ModuleForTests("cc-client-vendor", vendorVariant)
vendorFlags := vendorClient.Rule("cc").Args["cFlags"]
- // Vendor should use platform's system header and vendor's internal header
- if !strings.Contains(vendorFlags, platformSystemVendorPath) ||
+ // Vendor should use platform's public header and vendor's internal header
+ if !strings.Contains(vendorFlags, platformPublicVendorPath) ||
!strings.Contains(vendorFlags, vendorInternalPath) {
t.Errorf("flags for vendor must contain %#v and %#v, but was %#v.",
- platformSystemVendorPath, vendorInternalPath, vendorFlags)
+ platformPublicVendorPath, vendorInternalPath, vendorFlags)
}
}
diff --git a/tradefed/autogen.go b/tradefed/autogen.go
index da5dabe..952b022 100644
--- a/tradefed/autogen.go
+++ b/tradefed/autogen.go
@@ -16,7 +16,6 @@
import (
"fmt"
- "sort"
"strings"
"github.com/google/blueprint"
@@ -39,9 +38,9 @@
}
var autogenTestConfig = pctx.StaticRule("autogenTestConfig", blueprint.RuleParams{
- Command: "sed 's&{MODULE}&${name}&g;s&{EXTRA_OPTIONS}&'${extraOptions}'&g' $template > $out",
+ Command: "sed 's&{MODULE}&${name}&g;s&{EXTRA_CONFIGS}&'${extraConfigs}'&g' $template > $out",
CommandDeps: []string{"$template"},
-}, "name", "template", "extraOptions")
+}, "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 {
@@ -57,17 +56,38 @@
}
}
-func autogenTemplate(ctx android.ModuleContext, output android.WritablePath, template string, optionsMap map[string]string) {
- // If no test option found, delete {EXTRA_OPTIONS} line.
- var options []string
- for optionName, value := range optionsMap {
- if value != "" {
- options = append(options, fmt.Sprintf(`<option name="%s" value="%s" />`, optionName, value))
- }
+type Config interface {
+ Config() string
+}
+
+type Option struct {
+ Name string
+ Value string
+}
+
+var _ Config = Option{}
+
+func (o Option) Config() string {
+ return fmt.Sprintf(`<option name="%s" value="%s" />`, o.Name, o.Value)
+}
+
+type Preparer struct {
+ Class string
+}
+
+var _ Config = Preparer{}
+
+func (p Preparer) Config() string {
+ return fmt.Sprintf(`<target_preparer class="%s" />`, p.Class)
+}
+
+func autogenTemplate(ctx android.ModuleContext, output android.WritablePath, template string, configs []Config) {
+ var configStrings []string
+ for _, config := range configs {
+ configStrings = append(configStrings, config.Config())
}
- sort.Strings(options)
- extraOptions := strings.Join(options, "\n ")
- extraOptions = proptools.NinjaAndShellEscape(extraOptions)
+ extraConfigs := strings.Join(configStrings, "\n ")
+ extraConfigs = proptools.NinjaAndShellEscape(extraConfigs)
ctx.Build(pctx, android.BuildParams{
Rule: autogenTestConfig,
@@ -76,26 +96,23 @@
Args: map[string]string{
"name": ctx.ModuleName(),
"template": template,
- "extraOptions": extraOptions,
+ "extraConfigs": extraConfigs,
},
})
}
func AutoGenNativeTestConfig(ctx android.ModuleContext, testConfigProp *string,
- testConfigTemplateProp *string, testSuites []string,
- optionsMap map[string]string) android.Path {
+ testConfigTemplateProp *string, testSuites []string, config []Config) android.Path {
path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites)
if autogenPath != nil {
templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
if templatePath.Valid() {
- autogenTemplate(ctx, autogenPath, templatePath.String(), optionsMap)
+ autogenTemplate(ctx, autogenPath, templatePath.String(), config)
} else {
if ctx.Device() {
- autogenTemplate(ctx, autogenPath, "${NativeTestConfigTemplate}",
- optionsMap)
+ autogenTemplate(ctx, autogenPath, "${NativeTestConfigTemplate}", config)
} else {
- autogenTemplate(ctx, autogenPath, "${NativeHostTestConfigTemplate}",
- optionsMap)
+ autogenTemplate(ctx, autogenPath, "${NativeHostTestConfigTemplate}", config)
}
}
return autogenPath
@@ -104,14 +121,14 @@
}
func AutoGenNativeBenchmarkTestConfig(ctx android.ModuleContext, testConfigProp *string,
- testConfigTemplateProp *string, testSuites []string) android.Path {
+ testConfigTemplateProp *string, testSuites []string, configs []Config) android.Path {
path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites)
if autogenPath != nil {
templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
if templatePath.Valid() {
- autogenTemplate(ctx, autogenPath, templatePath.String(), nil)
+ autogenTemplate(ctx, autogenPath, templatePath.String(), configs)
} else {
- autogenTemplate(ctx, autogenPath, "${NativeBenchmarkTestConfigTemplate}", nil)
+ autogenTemplate(ctx, autogenPath, "${NativeBenchmarkTestConfigTemplate}", configs)
}
return autogenPath
}
diff --git a/ui/build/config.go b/ui/build/config.go
index c298f00..6df9529 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -61,6 +61,28 @@
const srcDirFileCheck = "build/soong/root.bp"
+type BuildAction uint
+
+const (
+ // Builds all of the modules and their dependencies of a specified directory, relative to the root
+ // directory of the source tree.
+ BUILD_MODULES_IN_A_DIRECTORY BuildAction = iota
+
+ // Builds all of the modules and their dependencies of a list of specified directories. All specified
+ // directories are relative to the root directory of the source tree.
+ BUILD_MODULES_IN_DIRECTORIES
+)
+
+// checkTopDir validates that the current directory is at the root directory of the source tree.
+func checkTopDir(ctx Context) {
+ if _, err := os.Stat(srcDirFileCheck); err != nil {
+ if os.IsNotExist(err) {
+ ctx.Fatalf("Current working directory must be the source tree. %q not found.", srcDirFileCheck)
+ }
+ ctx.Fatalln("Error verifying tree state:", err)
+ }
+}
+
func NewConfig(ctx Context, args ...string) Config {
ret := &configImpl{
environ: OsEnvironment(),
@@ -154,12 +176,7 @@
ret.environ.Set("TMPDIR", absPath(ctx, ret.TempDir()))
// Precondition: the current directory is the top of the source tree
- if _, err := os.Stat(srcDirFileCheck); err != nil {
- if os.IsNotExist(err) {
- log.Fatalf("Current working directory must be the source tree. %q not found", srcDirFileCheck)
- }
- log.Fatalln("Error verifying tree state:", err)
- }
+ checkTopDir(ctx)
if srcDir := absPath(ctx, "."); strings.ContainsRune(srcDir, ' ') {
log.Println("You are building in a directory whose absolute path contains a space character:")
@@ -229,6 +246,203 @@
return Config{ret}
}
+// NewBuildActionConfig returns a build configuration based on the build action. The arguments are
+// processed based on the build action and extracts any arguments that belongs to the build action.
+func NewBuildActionConfig(action BuildAction, dir string, buildDependencies bool, ctx Context, args ...string) Config {
+ return NewConfig(ctx, getConfigArgs(action, dir, buildDependencies, ctx, args)...)
+}
+
+// getConfigArgs processes the command arguments based on the build action and creates a set of new
+// arguments to be accepted by Config.
+func getConfigArgs(action BuildAction, dir string, buildDependencies bool, ctx Context, args []string) []string {
+ // The next block of code verifies that the current directory is the root directory of the source
+ // tree. It then finds the relative path of dir based on the root directory of the source tree
+ // and verify that dir is inside of the source tree.
+ checkTopDir(ctx)
+ topDir, err := os.Getwd()
+ if err != nil {
+ ctx.Fatalf("Error retrieving top directory: %v", err)
+ }
+ dir, err = filepath.Abs(dir)
+ if err != nil {
+ ctx.Fatalf("Unable to find absolute path %s: %v", dir, err)
+ }
+ relDir, err := filepath.Rel(topDir, dir)
+ if err != nil {
+ ctx.Fatalf("Unable to find relative path %s of %s: %v", relDir, topDir, err)
+ }
+ // If there are ".." in the path, it's not in the source tree.
+ if strings.Contains(relDir, "..") {
+ ctx.Fatalf("Directory %s is not under the source tree %s", dir, topDir)
+ }
+
+ configArgs := args[:]
+
+ // If the arguments contains GET-INSTALL-PATH, change the target name prefix from MODULES-IN- to
+ // GET-INSTALL-PATH-IN- to extract the installation path instead of building the modules.
+ targetNamePrefix := "MODULES-IN-"
+ if inList("GET-INSTALL-PATH", configArgs) {
+ targetNamePrefix = "GET-INSTALL-PATH-IN-"
+ configArgs = removeFromList("GET-INSTALL-PATH", configArgs)
+ }
+
+ var buildFiles []string
+ var targets []string
+
+ switch action {
+ case BUILD_MODULES_IN_A_DIRECTORY:
+ // If dir is the root source tree, all the modules are built of the source tree are built so
+ // no need to find the build file.
+ if topDir == dir {
+ break
+ }
+ // Find the build file from the directory where the build action was triggered by traversing up
+ // the source tree. If a blank build filename is returned, simply use the directory where the build
+ // action was invoked.
+ buildFile := findBuildFile(ctx, relDir)
+ if buildFile == "" {
+ buildFile = filepath.Join(relDir, "Android.mk")
+ }
+ buildFiles = []string{buildFile}
+ targets = []string{convertToTarget(filepath.Dir(buildFile), targetNamePrefix)}
+ case BUILD_MODULES_IN_DIRECTORIES:
+ newConfigArgs, dirs := splitArgs(configArgs)
+ configArgs = newConfigArgs
+ targets, buildFiles = getTargetsFromDirs(ctx, relDir, dirs, targetNamePrefix)
+ }
+
+ // This is to support building modules without building their dependencies. Soon, this will be
+ // deprecated.
+ if !buildDependencies && len(buildFiles) > 0 {
+ if err := os.Setenv("ONE_SHOT_MAKEFILE", strings.Join(buildFiles, " ")); err != nil {
+ ctx.Fatalf("Unable to set ONE_SHOT_MAKEFILE environment variable: %v", err)
+ }
+ }
+
+ // Tidy only override all other specified targets.
+ tidyOnly := os.Getenv("WITH_TIDY_ONLY")
+ if tidyOnly == "true" || tidyOnly == "1" {
+ configArgs = append(configArgs, "tidy_only")
+ } else {
+ configArgs = append(configArgs, targets...)
+ }
+
+ return configArgs
+}
+
+// convertToTarget replaces "/" to "-" in dir and pre-append the targetNamePrefix to the target name.
+func convertToTarget(dir string, targetNamePrefix string) string {
+ return targetNamePrefix + strings.ReplaceAll(dir, "/", "-")
+}
+
+// findBuildFile finds a build file (makefile or blueprint file) by looking at dir first. If not
+// found, go up one level and repeat again until one is found and the path of that build file
+// relative to the root directory of the source tree is returned. The returned filename of build
+// file is "Android.mk". If one was not found, a blank string is returned.
+func findBuildFile(ctx Context, dir string) string {
+ // If the string is empty, assume it is top directory of the source tree.
+ if dir == "" {
+ return ""
+ }
+
+ for ; dir != "."; dir = filepath.Dir(dir) {
+ for _, buildFile := range []string{"Android.bp", "Android.mk"} {
+ _, err := os.Stat(filepath.Join(dir, buildFile))
+ if err == nil {
+ // Returning the filename Android.mk as it might be used for ONE_SHOT_MAKEFILE variable.
+ return filepath.Join(dir, "Android.mk")
+ }
+ if !os.IsNotExist(err) {
+ ctx.Fatalf("Error retrieving the build file stats: %v", err)
+ }
+ }
+ }
+
+ return ""
+}
+
+// splitArgs iterates over the arguments list and splits into two lists: arguments and directories.
+func splitArgs(args []string) (newArgs []string, dirs []string) {
+ specialArgs := map[string]bool{
+ "showcommands": true,
+ "snod": true,
+ "dist": true,
+ "checkbuild": true,
+ }
+
+ newArgs = []string{}
+ dirs = []string{}
+
+ for _, arg := range args {
+ // It's a dash argument if it starts with "-" or it's a key=value pair, it's not a directory.
+ if strings.IndexRune(arg, '-') == 0 || strings.IndexRune(arg, '=') != -1 {
+ newArgs = append(newArgs, arg)
+ continue
+ }
+
+ if _, ok := specialArgs[arg]; ok {
+ newArgs = append(newArgs, arg)
+ continue
+ }
+
+ dirs = append(dirs, arg)
+ }
+
+ return newArgs, dirs
+}
+
+// getTargetsFromDirs iterates over the dirs list and creates a list of targets to build. If a
+// directory from the dirs list does not exist, a fatal error is raised. relDir is related to the
+// source root tree where the build action command was invoked. Each directory is validated if the
+// build file can be found and follows the format "dir1:target1,target2,...". Target is optional.
+func getTargetsFromDirs(ctx Context, relDir string, dirs []string, targetNamePrefix string) (targets []string, buildFiles []string) {
+ for _, dir := range dirs {
+ // The directory may have specified specific modules to build. ":" is the separator to separate
+ // the directory and the list of modules.
+ s := strings.Split(dir, ":")
+ l := len(s)
+ if l > 2 { // more than one ":" was specified.
+ ctx.Fatalf("%s not in proper directory:target1,target2,... format (\":\" was specified more than once)", dir)
+ }
+
+ dir = filepath.Join(relDir, s[0])
+ if _, err := os.Stat(dir); err != nil {
+ ctx.Fatalf("couldn't find directory %s", dir)
+ }
+
+ // Verify that if there are any targets specified after ":". Each target is separated by ",".
+ var newTargets []string
+ if l == 2 && s[1] != "" {
+ newTargets = strings.Split(s[1], ",")
+ if inList("", newTargets) {
+ ctx.Fatalf("%s not in proper directory:target1,target2,... format", dir)
+ }
+ }
+
+ buildFile := findBuildFile(ctx, dir)
+ if buildFile == "" {
+ ctx.Fatalf("Build file not found for %s directory", dir)
+ }
+ buildFileDir := filepath.Dir(buildFile)
+
+ // If there are specified targets, find the build file in the directory. If dir does not
+ // contain the build file, bail out as it is required for one shot build. If there are no
+ // target specified, build all the modules in dir (or the closest one in the dir path).
+ if len(newTargets) > 0 {
+ if buildFileDir != dir {
+ ctx.Fatalf("Couldn't locate a build file from %s directory", dir)
+ }
+ } else {
+ newTargets = []string{convertToTarget(buildFileDir, targetNamePrefix)}
+ }
+
+ buildFiles = append(buildFiles, buildFile)
+ targets = append(targets, newTargets...)
+ }
+
+ return targets, buildFiles
+}
+
func (c *configImpl) parseArgs(ctx Context, args []string) {
for i := 0; i < len(args); i++ {
arg := strings.TrimSpace(args[i])
diff --git a/ui/build/config_test.go b/ui/build/config_test.go
index 242e3af..1ef5456 100644
--- a/ui/build/config_test.go
+++ b/ui/build/config_test.go
@@ -17,19 +17,22 @@
import (
"bytes"
"context"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
"reflect"
"strings"
"testing"
"android/soong/ui/logger"
- "android/soong/ui/terminal"
)
func testContext() Context {
return Context{&ContextImpl{
Context: context.Background(),
Logger: logger.New(&bytes.Buffer{}),
- Writer: terminal.NewWriter(terminal.NewCustomStdio(&bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{})),
+ Writer: &bytes.Buffer{},
}}
}
@@ -173,3 +176,877 @@
})
}
}
+
+func TestConfigCheckTopDir(t *testing.T) {
+ ctx := testContext()
+ buildRootDir := filepath.Dir(srcDirFileCheck)
+ expectedErrStr := fmt.Sprintf("Current working directory must be the source tree. %q not found.", srcDirFileCheck)
+
+ tests := []struct {
+ // ********* Setup *********
+ // Test description.
+ description string
+
+ // ********* Action *********
+ // If set to true, the build root file is created.
+ rootBuildFile bool
+
+ // The current path where Soong is being executed.
+ path string
+
+ // ********* Validation *********
+ // Expecting error and validate the error string against expectedErrStr.
+ wantErr bool
+ }{{
+ description: "current directory is the root source tree",
+ rootBuildFile: true,
+ path: ".",
+ wantErr: false,
+ }, {
+ description: "one level deep in the source tree",
+ rootBuildFile: true,
+ path: "1",
+ wantErr: true,
+ }, {
+ description: "very deep in the source tree",
+ rootBuildFile: true,
+ path: "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7",
+ wantErr: true,
+ }, {
+ description: "outside of source tree",
+ rootBuildFile: false,
+ path: "1/2/3/4/5",
+ wantErr: true,
+ }}
+
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ defer logger.Recover(func(err error) {
+ if !tt.wantErr {
+ t.Fatalf("Got unexpected error: %v", err)
+ }
+ if expectedErrStr != err.Error() {
+ t.Fatalf("expected %s, got %s", expectedErrStr, err.Error())
+ }
+ })
+
+ // Create the root source tree.
+ rootDir, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(rootDir)
+
+ // Create the build root file. This is to test if topDir returns an error if the build root
+ // file does not exist.
+ if tt.rootBuildFile {
+ dir := filepath.Join(rootDir, buildRootDir)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ t.Errorf("failed to create %s directory: %v", dir, err)
+ }
+ f := filepath.Join(rootDir, srcDirFileCheck)
+ if err := ioutil.WriteFile(f, []byte{}, 0644); err != nil {
+ t.Errorf("failed to create file %s: %v", f, err)
+ }
+ }
+
+ // Next block of code is to set the current directory.
+ dir := rootDir
+ if tt.path != "" {
+ dir = filepath.Join(dir, tt.path)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ t.Errorf("failed to create %s directory: %v", dir, err)
+ }
+ }
+ curDir, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("failed to get the current directory: %v", err)
+ }
+ defer func() { os.Chdir(curDir) }()
+
+ if err := os.Chdir(dir); err != nil {
+ t.Fatalf("failed to change directory to %s: %v", dir, err)
+ }
+
+ checkTopDir(ctx)
+ })
+ }
+}
+
+func TestConfigConvertToTarget(t *testing.T) {
+ tests := []struct {
+ // ********* Setup *********
+ // Test description.
+ description string
+
+ // ********* Action *********
+ // The current directory where Soong is being executed.
+ dir string
+
+ // The current prefix string to be pre-appended to the target.
+ prefix string
+
+ // ********* Validation *********
+ // The expected target to be invoked in ninja.
+ expectedTarget string
+ }{{
+ description: "one level directory in source tree",
+ dir: "test1",
+ prefix: "MODULES-IN-",
+ expectedTarget: "MODULES-IN-test1",
+ }, {
+ description: "multiple level directories in source tree",
+ dir: "test1/test2/test3/test4",
+ prefix: "GET-INSTALL-PATH-IN-",
+ expectedTarget: "GET-INSTALL-PATH-IN-test1-test2-test3-test4",
+ }}
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ target := convertToTarget(tt.dir, tt.prefix)
+ if target != tt.expectedTarget {
+ t.Errorf("expected %s, got %s for target", tt.expectedTarget, target)
+ }
+ })
+ }
+}
+
+func setTop(t *testing.T, dir string) func() {
+ curDir, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("failed to get current directory: %v", err)
+ }
+ if err := os.Chdir(dir); err != nil {
+ t.Fatalf("failed to change directory to top dir %s: %v", dir, err)
+ }
+ return func() { os.Chdir(curDir) }
+}
+
+func createBuildFiles(t *testing.T, topDir string, buildFiles []string) {
+ for _, buildFile := range buildFiles {
+ buildFile = filepath.Join(topDir, buildFile)
+ if err := ioutil.WriteFile(buildFile, []byte{}, 0644); err != nil {
+ t.Errorf("failed to create file %s: %v", buildFile, err)
+ }
+ }
+}
+
+func createDirectories(t *testing.T, topDir string, dirs []string) {
+ for _, dir := range dirs {
+ dir = filepath.Join(topDir, dir)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ t.Errorf("failed to create %s directory: %v", dir, err)
+ }
+ }
+}
+
+func TestConfigGetTargets(t *testing.T) {
+ ctx := testContext()
+ tests := []struct {
+ // ********* Setup *********
+ // Test description.
+ description string
+
+ // Directories that exist in the source tree.
+ dirsInTrees []string
+
+ // Build files that exists in the source tree.
+ buildFiles []string
+
+ // ********* Action *********
+ // Directories passed in to soong_ui.
+ dirs []string
+
+ // Current directory that the user executed the build action command.
+ curDir string
+
+ // ********* Validation *********
+ // Expected targets from the function.
+ expectedTargets []string
+
+ // Expected build from the build system.
+ expectedBuildFiles []string
+
+ // Expecting error from running test case.
+ errStr string
+ }{{
+ description: "one target dir specified",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3"},
+ curDir: "0",
+ expectedTargets: []string{"MODULES-IN-0-1-2-3"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk"},
+ }, {
+ description: "one target dir specified, build file does not exist",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{},
+ dirs: []string{"1/2/3"},
+ curDir: "0",
+ errStr: "Build file not found for 0/1/2/3 directory",
+ }, {
+ description: "one target dir specified, invalid targets specified",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{},
+ dirs: []string{"1/2/3:t1:t2"},
+ curDir: "0",
+ errStr: "1/2/3:t1:t2 not in proper directory:target1,target2,... format (\":\" was specified more than once)",
+ }, {
+ description: "one target dir specified, no targets specified but has colon",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3:"},
+ curDir: "0",
+ expectedTargets: []string{"MODULES-IN-0-1-2-3"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk"},
+ }, {
+ description: "one target dir specified, two targets specified",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3:t1,t2"},
+ curDir: "0",
+ expectedTargets: []string{"t1", "t2"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk"},
+ }, {
+ description: "one target dir specified, no targets and has a comma",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3:,"},
+ curDir: "0",
+ errStr: "0/1/2/3 not in proper directory:target1,target2,... format",
+ }, {
+ description: "one target dir specified, improper targets defined",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3:,t1"},
+ curDir: "0",
+ errStr: "0/1/2/3 not in proper directory:target1,target2,... format",
+ }, {
+ description: "one target dir specified, blank target",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3:t1,"},
+ curDir: "0",
+ errStr: "0/1/2/3 not in proper directory:target1,target2,... format",
+ }, {
+ description: "one target dir specified, many targets specified",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3:t1,t2,t3,t4,t5,t6,t7,t8,t9,t10"},
+ curDir: "0",
+ expectedTargets: []string{"t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk"},
+ }, {
+ description: "one target dir specified, one target specified, build file does not exist",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{},
+ dirs: []string{"1/2/3:t1"},
+ curDir: "0",
+ errStr: "Build file not found for 0/1/2/3 directory",
+ }, {
+ description: "one target dir specified, one target specified, build file not in target dir",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/Android.mk"},
+ dirs: []string{"1/2/3:t1"},
+ curDir: "0",
+ errStr: "Couldn't locate a build file from 0/1/2/3 directory",
+ }, {
+ description: "one target dir specified, build file not in target dir",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/Android.mk"},
+ dirs: []string{"1/2/3"},
+ curDir: "0",
+ expectedTargets: []string{"MODULES-IN-0-1-2"},
+ expectedBuildFiles: []string{"0/1/2/Android.mk"},
+ }, {
+ description: "multiple targets dir specified, targets specified",
+ dirsInTrees: []string{"0/1/2/3", "0/3/4"},
+ buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"},
+ dirs: []string{"1/2/3:t1,t2", "3/4:t3,t4,t5"},
+ curDir: "0",
+ expectedTargets: []string{"t1", "t2", "t3", "t4", "t5"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk", "0/3/4/Android.mk"},
+ }, {
+ description: "multiple targets dir specified, one directory has targets specified",
+ dirsInTrees: []string{"0/1/2/3", "0/3/4"},
+ buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"},
+ dirs: []string{"1/2/3:t1,t2", "3/4"},
+ curDir: "0",
+ expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk", "0/3/4/Android.mk"},
+ }, {
+ description: "two dirs specified, only one dir exist",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.mk"},
+ dirs: []string{"1/2/3:t1", "3/4"},
+ curDir: "0",
+ errStr: "couldn't find directory 0/3/4",
+ }, {
+ description: "multiple targets dirs specified at root source tree",
+ dirsInTrees: []string{"0/1/2/3", "0/3/4"},
+ buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"},
+ dirs: []string{"0/1/2/3:t1,t2", "0/3/4"},
+ curDir: ".",
+ expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk", "0/3/4/Android.mk"},
+ }, {
+ description: "no directories specified",
+ dirsInTrees: []string{"0/1/2/3", "0/3/4"},
+ buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"},
+ dirs: []string{},
+ curDir: ".",
+ }}
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ defer logger.Recover(func(err error) {
+ if tt.errStr == "" {
+ t.Fatalf("Got unexpected error: %v", err)
+ }
+ if tt.errStr != err.Error() {
+ t.Errorf("expected %s, got %s", tt.errStr, err.Error())
+ }
+ })
+
+ // Create the root source tree.
+ topDir, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatalf("failed to create temp dir: %v", err)
+ }
+ defer os.RemoveAll(topDir)
+
+ createDirectories(t, topDir, tt.dirsInTrees)
+ createBuildFiles(t, topDir, tt.buildFiles)
+ r := setTop(t, topDir)
+ defer r()
+
+ targets, buildFiles := getTargetsFromDirs(ctx, tt.curDir, tt.dirs, "MODULES-IN-")
+ if !reflect.DeepEqual(targets, tt.expectedTargets) {
+ t.Errorf("expected %v, got %v for targets", tt.expectedTargets, targets)
+ }
+ if !reflect.DeepEqual(buildFiles, tt.expectedBuildFiles) {
+ t.Errorf("expected %v, got %v for build files", tt.expectedBuildFiles, buildFiles)
+ }
+
+ // If the execution reached here and there was an expected error code, the unit test case failed.
+ if tt.errStr != "" {
+ t.Errorf("expecting error %s", tt.errStr)
+ }
+ })
+ }
+}
+
+func TestConfigFindBuildFile(t *testing.T) {
+ ctx := testContext()
+
+ tests := []struct {
+ // ********* Setup *********
+ // Test description.
+ description string
+
+ // Array of build files to create in dir.
+ buildFiles []string
+
+ // ********* Action *********
+ // Directory to create, also the base directory is where findBuildFile is invoked.
+ dir string
+
+ // ********* Validation *********
+ // Expected build file path to find.
+ expectedBuildFile string
+ }{{
+ description: "build file exists at leaf directory",
+ buildFiles: []string{"1/2/3/Android.bp"},
+ dir: "1/2/3",
+ expectedBuildFile: "1/2/3/Android.mk",
+ }, {
+ description: "build file exists in all directory paths",
+ buildFiles: []string{"1/Android.mk", "1/2/Android.mk", "1/2/3/Android.mk"},
+ dir: "1/2/3",
+ expectedBuildFile: "1/2/3/Android.mk",
+ }, {
+ description: "build file does not exist in all directory paths",
+ buildFiles: []string{},
+ dir: "1/2/3",
+ expectedBuildFile: "",
+ }, {
+ description: "build file exists only at top directory",
+ buildFiles: []string{"Android.bp"},
+ dir: "1/2/3",
+ expectedBuildFile: "",
+ }, {
+ description: "build file exist in a subdirectory",
+ buildFiles: []string{"1/2/Android.bp"},
+ dir: "1/2/3",
+ expectedBuildFile: "1/2/Android.mk",
+ }, {
+ description: "build file exists in a subdirectory",
+ buildFiles: []string{"1/Android.mk"},
+ dir: "1/2/3",
+ expectedBuildFile: "1/Android.mk",
+ }, {
+ description: "top directory",
+ buildFiles: []string{"Android.bp"},
+ dir: ".",
+ expectedBuildFile: "",
+ }}
+
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ defer logger.Recover(func(err error) {
+ t.Fatalf("Got unexpected error: %v", err)
+ })
+
+ topDir, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatalf("failed to create temp dir: %v", err)
+ }
+ defer os.RemoveAll(topDir)
+
+ if tt.dir != "" {
+ createDirectories(t, topDir, []string{tt.dir})
+ }
+
+ createBuildFiles(t, topDir, tt.buildFiles)
+
+ curDir, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("Could not get working directory: %v", err)
+ }
+ defer func() { os.Chdir(curDir) }()
+ if err := os.Chdir(topDir); err != nil {
+ t.Fatalf("Could not change top dir to %s: %v", topDir, err)
+ }
+
+ buildFile := findBuildFile(ctx, tt.dir)
+ if buildFile != tt.expectedBuildFile {
+ t.Errorf("expected %q, got %q for build file", tt.expectedBuildFile, buildFile)
+ }
+ })
+ }
+}
+
+func TestConfigSplitArgs(t *testing.T) {
+ tests := []struct {
+ // ********* Setup *********
+ // Test description.
+ description string
+
+ // ********* Action *********
+ // Arguments passed in to soong_ui.
+ args []string
+
+ // ********* Validation *********
+ // Expected newArgs list after extracting the directories.
+ expectedNewArgs []string
+
+ // Expected directories
+ expectedDirs []string
+ }{{
+ description: "flags but no directories specified",
+ args: []string{"showcommands", "-j", "-k"},
+ expectedNewArgs: []string{"showcommands", "-j", "-k"},
+ expectedDirs: []string{},
+ }, {
+ description: "flags and one directory specified",
+ args: []string{"snod", "-j", "dir:target1,target2"},
+ expectedNewArgs: []string{"snod", "-j"},
+ expectedDirs: []string{"dir:target1,target2"},
+ }, {
+ description: "flags and directories specified",
+ args: []string{"dist", "-k", "dir1", "dir2:target1,target2"},
+ expectedNewArgs: []string{"dist", "-k"},
+ expectedDirs: []string{"dir1", "dir2:target1,target2"},
+ }, {
+ description: "only directories specified",
+ args: []string{"dir1", "dir2", "dir3:target1,target2"},
+ expectedNewArgs: []string{},
+ expectedDirs: []string{"dir1", "dir2", "dir3:target1,target2"},
+ }}
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ args, dirs := splitArgs(tt.args)
+ if !reflect.DeepEqual(tt.expectedNewArgs, args) {
+ t.Errorf("expected %v, got %v for arguments", tt.expectedNewArgs, args)
+ }
+ if !reflect.DeepEqual(tt.expectedDirs, dirs) {
+ t.Errorf("expected %v, got %v for directories", tt.expectedDirs, dirs)
+ }
+ })
+ }
+}
+
+type envVar struct {
+ name string
+ value string
+}
+
+type buildActionTestCase struct {
+ // ********* Setup *********
+ // Test description.
+ description string
+
+ // Directories that exist in the source tree.
+ dirsInTrees []string
+
+ // Build files that exists in the source tree.
+ buildFiles []string
+
+ // ********* Action *********
+ // Arguments passed in to soong_ui.
+ args []string
+
+ // Directory where the build action was invoked.
+ curDir string
+
+ // WITH_TIDY_ONLY environment variable specified.
+ tidyOnly string
+
+ // ********* Validation *********
+ // Expected arguments to be in Config instance.
+ expectedArgs []string
+
+ // Expected environment variables to be set.
+ expectedEnvVars []envVar
+}
+
+func testGetConfigArgs(t *testing.T, tt buildActionTestCase, action BuildAction, buildDependencies bool) {
+ ctx := testContext()
+
+ // Environment variables to set it to blank on every test case run.
+ resetEnvVars := []string{
+ "ONE_SHOT_MAKEFILE",
+ "WITH_TIDY_ONLY",
+ }
+
+ for _, name := range resetEnvVars {
+ if err := os.Unsetenv(name); err != nil {
+ t.Fatalf("failed to unset environment variable %s: %v", name, err)
+ }
+ }
+ if tt.tidyOnly != "" {
+ if err := os.Setenv("WITH_TIDY_ONLY", tt.tidyOnly); err != nil {
+ t.Errorf("failed to set WITH_TIDY_ONLY to %s: %v", tt.tidyOnly, err)
+ }
+ }
+
+ // Create the root source tree.
+ topDir, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatalf("failed to create temp dir: %v", err)
+ }
+ defer os.RemoveAll(topDir)
+
+ createDirectories(t, topDir, tt.dirsInTrees)
+ createBuildFiles(t, topDir, tt.buildFiles)
+
+ r := setTop(t, topDir)
+ defer r()
+
+ // The next block is to create the root build file.
+ rootBuildFileDir := filepath.Dir(srcDirFileCheck)
+ if err := os.MkdirAll(rootBuildFileDir, 0755); err != nil {
+ t.Fatalf("Failed to create %s directory: %v", rootBuildFileDir, err)
+ }
+
+ if err := ioutil.WriteFile(srcDirFileCheck, []byte{}, 0644); err != nil {
+ t.Fatalf("failed to create %s file: %v", srcDirFileCheck, err)
+ }
+
+ args := getConfigArgs(action, tt.curDir, buildDependencies, ctx, tt.args)
+ if !reflect.DeepEqual(tt.expectedArgs, args) {
+ t.Fatalf("expected %v, got %v for config arguments", tt.expectedArgs, args)
+ }
+
+ for _, env := range tt.expectedEnvVars {
+ if val := os.Getenv(env.name); val != env.value {
+ t.Errorf("expecting %s, got %s for environment variable %s", env.value, val, env.name)
+ }
+ }
+}
+
+// TODO: Remove this test case once mm shell build command has been deprecated.
+func TestGetConfigArgsBuildModulesInDirecotoryNoDeps(t *testing.T) {
+ tests := []buildActionTestCase{{
+ description: "normal execution in a directory",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/2/Android.mk"},
+ args: []string{"-j", "-k", "showcommands", "fake-module"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"-j", "-k", "showcommands", "fake-module", "MODULES-IN-0-1-2"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/2/Android.mk"}},
+ }, {
+ description: "makefile in parent directory",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/Android.mk"}},
+ }, {
+ description: "build file not found",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{},
+ args: []string{},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1-2"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/2/Android.mk"}},
+ }, {
+ description: "build action executed at root directory",
+ dirsInTrees: []string{},
+ buildFiles: []string{},
+ args: []string{},
+ curDir: ".",
+ tidyOnly: "",
+ expectedArgs: []string{},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: ""}},
+ }, {
+ description: "GET-INSTALL-PATH specified,",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{"GET-INSTALL-PATH"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/Android.mk"}},
+ }, {
+ description: "tidy only environment variable specified,",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{"GET-INSTALL-PATH"},
+ curDir: "0/1/2",
+ tidyOnly: "true",
+ expectedArgs: []string{"tidy_only"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/Android.mk"}},
+ }}
+ for _, tt := range tests {
+ t.Run("build action BUILD_MODULES_IN_DIR without their dependencies, "+tt.description, func(t *testing.T) {
+ testGetConfigArgs(t, tt, BUILD_MODULES_IN_A_DIRECTORY, false)
+ })
+ }
+}
+
+func TestGetConfigArgsBuildModulesInDirectory(t *testing.T) {
+ tests := []buildActionTestCase{{
+ description: "normal execution in a directory",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/2/Android.mk"},
+ args: []string{"fake-module"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"fake-module", "MODULES-IN-0-1-2"},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "build file in parent directory",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1"},
+ expectedEnvVars: []envVar{},
+ },
+ {
+ description: "build file in parent directory, multiple module names passed in",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{"fake-module1", "fake-module2", "fake-module3"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"fake-module1", "fake-module2", "fake-module3", "MODULES-IN-0-1"},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "build file in 2nd level parent directory",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/Android.bp"},
+ args: []string{},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0"},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "build action executed at root directory",
+ dirsInTrees: []string{},
+ buildFiles: []string{},
+ args: []string{},
+ curDir: ".",
+ tidyOnly: "",
+ expectedArgs: []string{},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "build file not found - no error is expected to return",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{},
+ args: []string{},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1-2"},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "GET-INSTALL-PATH specified,",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{"GET-INSTALL-PATH", "-j", "-k", "GET-INSTALL-PATH"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"-j", "-k", "GET-INSTALL-PATH-IN-0-1"},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "tidy only environment variable specified,",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{"GET-INSTALL-PATH"},
+ curDir: "0/1/2",
+ tidyOnly: "true",
+ expectedArgs: []string{"tidy_only"},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "normal execution in root directory with args",
+ dirsInTrees: []string{},
+ buildFiles: []string{},
+ args: []string{"-j", "-k", "fake_module"},
+ curDir: "",
+ tidyOnly: "",
+ expectedArgs: []string{"-j", "-k", "fake_module"},
+ expectedEnvVars: []envVar{},
+ }}
+ for _, tt := range tests {
+ t.Run("build action BUILD_MODULES_IN_DIR, "+tt.description, func(t *testing.T) {
+ testGetConfigArgs(t, tt, BUILD_MODULES_IN_A_DIRECTORY, true)
+ })
+ }
+}
+
+// TODO: Remove this test case once mmm shell build command has been deprecated.
+func TestGetConfigArgsBuildModulesInDirectoriesNoDeps(t *testing.T) {
+ tests := []buildActionTestCase{{
+ description: "normal execution in a directory",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
+ args: []string{"3.1/:t1,t2", "3.2/:t3,t4", "3.3/:t5,t6"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"t1", "t2", "t3", "t4", "t5", "t6"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}},
+ }, {
+ description: "GET-INSTALL-PATH specified",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
+ args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3/:t6"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1-2-3.1", "GET-INSTALL-PATH-IN-0-1-2-3.2", "t6"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}},
+ }, {
+ description: "tidy only environment variable specified",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
+ args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3/:t6"},
+ curDir: "0/1/2",
+ tidyOnly: "1",
+ expectedArgs: []string{"tidy_only"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}},
+ }, {
+ description: "normal execution from top dir directory",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
+ args: []string{"0/1/2/3.1", "0/1/2/3.2/:t3,t4", "0/1/2/3.3/:t5,t6"},
+ curDir: ".",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "t3", "t4", "t5", "t6"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}},
+ }}
+ for _, tt := range tests {
+ t.Run("build action BUILD_MODULES_IN_DIRS_NO_DEPS, "+tt.description, func(t *testing.T) {
+ testGetConfigArgs(t, tt, BUILD_MODULES_IN_DIRECTORIES, false)
+ })
+ }
+}
+
+func TestGetConfigArgsBuildModulesInDirectories(t *testing.T) {
+ tests := []buildActionTestCase{{
+ description: "normal execution in a directory",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
+ args: []string{"3.1/", "3.2/", "3.3/"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-2-3.3"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: ""}},
+ }, {
+ description: "GET-INSTALL-PATH specified",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/Android.bp"},
+ args: []string{"GET-INSTALL-PATH", "2/3.1/", "2/3.2", "3"},
+ curDir: "0/1",
+ tidyOnly: "",
+ expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1-2-3.1", "GET-INSTALL-PATH-IN-0-1-2-3.2", "GET-INSTALL-PATH-IN-0-1"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: ""}},
+ }, {
+ description: "tidy only environment variable specified",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
+ args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3"},
+ curDir: "0/1/2",
+ tidyOnly: "1",
+ expectedArgs: []string{"tidy_only"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: ""}},
+ }, {
+ description: "normal execution from top dir directory",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/3/Android.bp", "0/2/Android.bp"},
+ args: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
+ curDir: ".",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-3", "MODULES-IN-0-2"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: ""}},
+ }}
+ for _, tt := range tests {
+ t.Run("build action BUILD_MODULES_IN_DIRS, "+tt.description, func(t *testing.T) {
+ testGetConfigArgs(t, tt, BUILD_MODULES_IN_DIRECTORIES, true)
+ })
+ }
+}
diff --git a/ui/build/context.go b/ui/build/context.go
index 249e898..3945ce0 100644
--- a/ui/build/context.go
+++ b/ui/build/context.go
@@ -16,12 +16,12 @@
import (
"context"
+ "io"
"android/soong/ui/logger"
"android/soong/ui/metrics"
"android/soong/ui/metrics/metrics_proto"
"android/soong/ui/status"
- "android/soong/ui/terminal"
"android/soong/ui/tracer"
)
@@ -35,7 +35,7 @@
Metrics *metrics.Metrics
- Writer terminal.Writer
+ Writer io.Writer
Status *status.Status
Thread tracer.Thread
@@ -70,7 +70,7 @@
if c.Metrics != nil {
realTime := end - begin
c.Metrics.SetTimeMetrics(
- metrics_proto.PerfInfo{
+ soong_metrics_proto.PerfInfo{
Desc: &desc,
Name: &name,
StartTime: &begin,
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index 4335667..266130f 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -249,7 +249,7 @@
env := config.Environment()
// Print the banner like make does
if !env.IsEnvTrue("ANDROID_QUIET_BUILD") {
- ctx.Writer.Print(Banner(make_vars))
+ fmt.Fprintln(ctx.Writer, Banner(make_vars))
}
// Populate the environment
diff --git a/ui/build/paths/config.go b/ui/build/paths/config.go
index a50a880..c9946e2 100644
--- a/ui/build/paths/config.go
+++ b/ui/build/paths/config.go
@@ -103,7 +103,6 @@
"rsync": Allowed,
"sh": Allowed,
"tar": Allowed,
- "timeout": Allowed,
"tr": Allowed,
"unzip": Allowed,
"zip": Allowed,
@@ -166,6 +165,7 @@
"stat": LinuxOnlyPrebuilt,
"tail": LinuxOnlyPrebuilt,
"tee": LinuxOnlyPrebuilt,
+ "timeout": LinuxOnlyPrebuilt,
"touch": LinuxOnlyPrebuilt,
"true": LinuxOnlyPrebuilt,
"uname": LinuxOnlyPrebuilt,
diff --git a/ui/build/util.go b/ui/build/util.go
index 0676a86..75e6753 100644
--- a/ui/build/util.go
+++ b/ui/build/util.go
@@ -44,6 +44,17 @@
return indexList(s, list) != -1
}
+// removeFromlist removes all occurrences of the string in list.
+func removeFromList(s string, list []string) []string {
+ filteredList := make([]string, 0, len(list))
+ for _, ls := range list {
+ if s != ls {
+ filteredList = append(filteredList, ls)
+ }
+ }
+ return filteredList
+}
+
// ensureDirectoriesExist is a shortcut to os.MkdirAll, sending errors to the ctx logger.
func ensureDirectoriesExist(ctx Context, dirs ...string) {
for _, dir := range dirs {
diff --git a/ui/metrics/metrics.go b/ui/metrics/metrics.go
index 790b67a..bc86f0a 100644
--- a/ui/metrics/metrics.go
+++ b/ui/metrics/metrics.go
@@ -33,19 +33,19 @@
)
type Metrics struct {
- metrics metrics_proto.MetricsBase
+ metrics soong_metrics_proto.MetricsBase
TimeTracer TimeTracer
}
func New() (metrics *Metrics) {
m := &Metrics{
- metrics: metrics_proto.MetricsBase{},
+ metrics: soong_metrics_proto.MetricsBase{},
TimeTracer: &timeTracerImpl{},
}
return m
}
-func (m *Metrics) SetTimeMetrics(perf metrics_proto.PerfInfo) {
+func (m *Metrics) SetTimeMetrics(perf soong_metrics_proto.PerfInfo) {
switch perf.GetName() {
case RunKati:
m.metrics.KatiRuns = append(m.metrics.KatiRuns, &perf)
@@ -76,11 +76,11 @@
case "TARGET_BUILD_VARIANT":
switch v {
case "user":
- m.metrics.TargetBuildVariant = metrics_proto.MetricsBase_USER.Enum()
+ m.metrics.TargetBuildVariant = soong_metrics_proto.MetricsBase_USER.Enum()
case "userdebug":
- m.metrics.TargetBuildVariant = metrics_proto.MetricsBase_USERDEBUG.Enum()
+ m.metrics.TargetBuildVariant = soong_metrics_proto.MetricsBase_USERDEBUG.Enum()
case "eng":
- m.metrics.TargetBuildVariant = metrics_proto.MetricsBase_ENG.Enum()
+ m.metrics.TargetBuildVariant = soong_metrics_proto.MetricsBase_ENG.Enum()
default:
// ignored
}
@@ -112,18 +112,18 @@
}
}
-func (m *Metrics) getArch(arch string) *metrics_proto.MetricsBase_ARCH {
+func (m *Metrics) getArch(arch string) *soong_metrics_proto.MetricsBase_Arch {
switch arch {
case "arm":
- return metrics_proto.MetricsBase_ARM.Enum()
+ return soong_metrics_proto.MetricsBase_ARM.Enum()
case "arm64":
- return metrics_proto.MetricsBase_ARM64.Enum()
+ return soong_metrics_proto.MetricsBase_ARM64.Enum()
case "x86":
- return metrics_proto.MetricsBase_X86.Enum()
+ return soong_metrics_proto.MetricsBase_X86.Enum()
case "x86_64":
- return metrics_proto.MetricsBase_X86_64.Enum()
+ return soong_metrics_proto.MetricsBase_X86_64.Enum()
default:
- return metrics_proto.MetricsBase_UNKNOWN.Enum()
+ return soong_metrics_proto.MetricsBase_UNKNOWN.Enum()
}
}
@@ -148,7 +148,7 @@
return err
}
tempPath := outputPath + ".tmp"
- err = ioutil.WriteFile(tempPath, []byte(data), 0777)
+ err = ioutil.WriteFile(tempPath, []byte(data), 0644)
if err != nil {
return err
}
diff --git a/ui/metrics/metrics_proto/metrics.pb.go b/ui/metrics/metrics_proto/metrics.pb.go
index feefc89..5486ec1 100644
--- a/ui/metrics/metrics_proto/metrics.pb.go
+++ b/ui/metrics/metrics_proto/metrics.pb.go
@@ -1,11 +1,13 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: metrics.proto
-package metrics_proto
+package soong_metrics_proto
-import proto "github.com/golang/protobuf/proto"
-import fmt "fmt"
-import math "math"
+import (
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ math "math"
+)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@@ -16,65 +18,70 @@
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
-type MetricsBase_BUILDVARIANT int32
+type MetricsBase_BuildVariant int32
const (
- MetricsBase_USER MetricsBase_BUILDVARIANT = 0
- MetricsBase_USERDEBUG MetricsBase_BUILDVARIANT = 1
- MetricsBase_ENG MetricsBase_BUILDVARIANT = 2
+ MetricsBase_USER MetricsBase_BuildVariant = 0
+ MetricsBase_USERDEBUG MetricsBase_BuildVariant = 1
+ MetricsBase_ENG MetricsBase_BuildVariant = 2
)
-var MetricsBase_BUILDVARIANT_name = map[int32]string{
+var MetricsBase_BuildVariant_name = map[int32]string{
0: "USER",
1: "USERDEBUG",
2: "ENG",
}
-var MetricsBase_BUILDVARIANT_value = map[string]int32{
+
+var MetricsBase_BuildVariant_value = map[string]int32{
"USER": 0,
"USERDEBUG": 1,
"ENG": 2,
}
-func (x MetricsBase_BUILDVARIANT) Enum() *MetricsBase_BUILDVARIANT {
- p := new(MetricsBase_BUILDVARIANT)
+func (x MetricsBase_BuildVariant) Enum() *MetricsBase_BuildVariant {
+ p := new(MetricsBase_BuildVariant)
*p = x
return p
}
-func (x MetricsBase_BUILDVARIANT) String() string {
- return proto.EnumName(MetricsBase_BUILDVARIANT_name, int32(x))
+
+func (x MetricsBase_BuildVariant) String() string {
+ return proto.EnumName(MetricsBase_BuildVariant_name, int32(x))
}
-func (x *MetricsBase_BUILDVARIANT) UnmarshalJSON(data []byte) error {
- value, err := proto.UnmarshalJSONEnum(MetricsBase_BUILDVARIANT_value, data, "MetricsBase_BUILDVARIANT")
+
+func (x *MetricsBase_BuildVariant) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(MetricsBase_BuildVariant_value, data, "MetricsBase_BuildVariant")
if err != nil {
return err
}
- *x = MetricsBase_BUILDVARIANT(value)
+ *x = MetricsBase_BuildVariant(value)
return nil
}
-func (MetricsBase_BUILDVARIANT) EnumDescriptor() ([]byte, []int) {
- return fileDescriptor_metrics_9e7b895801991242, []int{0, 0}
+
+func (MetricsBase_BuildVariant) EnumDescriptor() ([]byte, []int) {
+ return fileDescriptor_6039342a2ba47b72, []int{0, 0}
}
-type MetricsBase_ARCH int32
+type MetricsBase_Arch int32
const (
- MetricsBase_UNKNOWN MetricsBase_ARCH = 0
- MetricsBase_ARM MetricsBase_ARCH = 1
- MetricsBase_ARM64 MetricsBase_ARCH = 2
- MetricsBase_X86 MetricsBase_ARCH = 3
- MetricsBase_X86_64 MetricsBase_ARCH = 4
+ MetricsBase_UNKNOWN MetricsBase_Arch = 0
+ MetricsBase_ARM MetricsBase_Arch = 1
+ MetricsBase_ARM64 MetricsBase_Arch = 2
+ MetricsBase_X86 MetricsBase_Arch = 3
+ MetricsBase_X86_64 MetricsBase_Arch = 4
)
-var MetricsBase_ARCH_name = map[int32]string{
+var MetricsBase_Arch_name = map[int32]string{
0: "UNKNOWN",
1: "ARM",
2: "ARM64",
3: "X86",
4: "X86_64",
}
-var MetricsBase_ARCH_value = map[string]int32{
+
+var MetricsBase_Arch_value = map[string]int32{
"UNKNOWN": 0,
"ARM": 1,
"ARM64": 2,
@@ -82,63 +89,70 @@
"X86_64": 4,
}
-func (x MetricsBase_ARCH) Enum() *MetricsBase_ARCH {
- p := new(MetricsBase_ARCH)
+func (x MetricsBase_Arch) Enum() *MetricsBase_Arch {
+ p := new(MetricsBase_Arch)
*p = x
return p
}
-func (x MetricsBase_ARCH) String() string {
- return proto.EnumName(MetricsBase_ARCH_name, int32(x))
+
+func (x MetricsBase_Arch) String() string {
+ return proto.EnumName(MetricsBase_Arch_name, int32(x))
}
-func (x *MetricsBase_ARCH) UnmarshalJSON(data []byte) error {
- value, err := proto.UnmarshalJSONEnum(MetricsBase_ARCH_value, data, "MetricsBase_ARCH")
+
+func (x *MetricsBase_Arch) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(MetricsBase_Arch_value, data, "MetricsBase_Arch")
if err != nil {
return err
}
- *x = MetricsBase_ARCH(value)
+ *x = MetricsBase_Arch(value)
return nil
}
-func (MetricsBase_ARCH) EnumDescriptor() ([]byte, []int) {
- return fileDescriptor_metrics_9e7b895801991242, []int{0, 1}
+
+func (MetricsBase_Arch) EnumDescriptor() ([]byte, []int) {
+ return fileDescriptor_6039342a2ba47b72, []int{0, 1}
}
-type ModuleTypeInfo_BUILDSYSTEM int32
+type ModuleTypeInfo_BuildSystem int32
const (
- ModuleTypeInfo_UNKNOWN ModuleTypeInfo_BUILDSYSTEM = 0
- ModuleTypeInfo_SOONG ModuleTypeInfo_BUILDSYSTEM = 1
- ModuleTypeInfo_MAKE ModuleTypeInfo_BUILDSYSTEM = 2
+ ModuleTypeInfo_UNKNOWN ModuleTypeInfo_BuildSystem = 0
+ ModuleTypeInfo_SOONG ModuleTypeInfo_BuildSystem = 1
+ ModuleTypeInfo_MAKE ModuleTypeInfo_BuildSystem = 2
)
-var ModuleTypeInfo_BUILDSYSTEM_name = map[int32]string{
+var ModuleTypeInfo_BuildSystem_name = map[int32]string{
0: "UNKNOWN",
1: "SOONG",
2: "MAKE",
}
-var ModuleTypeInfo_BUILDSYSTEM_value = map[string]int32{
+
+var ModuleTypeInfo_BuildSystem_value = map[string]int32{
"UNKNOWN": 0,
"SOONG": 1,
"MAKE": 2,
}
-func (x ModuleTypeInfo_BUILDSYSTEM) Enum() *ModuleTypeInfo_BUILDSYSTEM {
- p := new(ModuleTypeInfo_BUILDSYSTEM)
+func (x ModuleTypeInfo_BuildSystem) Enum() *ModuleTypeInfo_BuildSystem {
+ p := new(ModuleTypeInfo_BuildSystem)
*p = x
return p
}
-func (x ModuleTypeInfo_BUILDSYSTEM) String() string {
- return proto.EnumName(ModuleTypeInfo_BUILDSYSTEM_name, int32(x))
+
+func (x ModuleTypeInfo_BuildSystem) String() string {
+ return proto.EnumName(ModuleTypeInfo_BuildSystem_name, int32(x))
}
-func (x *ModuleTypeInfo_BUILDSYSTEM) UnmarshalJSON(data []byte) error {
- value, err := proto.UnmarshalJSONEnum(ModuleTypeInfo_BUILDSYSTEM_value, data, "ModuleTypeInfo_BUILDSYSTEM")
+
+func (x *ModuleTypeInfo_BuildSystem) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(ModuleTypeInfo_BuildSystem_value, data, "ModuleTypeInfo_BuildSystem")
if err != nil {
return err
}
- *x = ModuleTypeInfo_BUILDSYSTEM(value)
+ *x = ModuleTypeInfo_BuildSystem(value)
return nil
}
-func (ModuleTypeInfo_BUILDSYSTEM) EnumDescriptor() ([]byte, []int) {
- return fileDescriptor_metrics_9e7b895801991242, []int{2, 0}
+
+func (ModuleTypeInfo_BuildSystem) EnumDescriptor() ([]byte, []int) {
+ return fileDescriptor_6039342a2ba47b72, []int{2, 0}
}
type MetricsBase struct {
@@ -151,17 +165,17 @@
// The target product information, eg. aosp_arm.
TargetProduct *string `protobuf:"bytes,4,opt,name=target_product,json=targetProduct" json:"target_product,omitempty"`
// The target build variant information, eg. eng.
- TargetBuildVariant *MetricsBase_BUILDVARIANT `protobuf:"varint,5,opt,name=target_build_variant,json=targetBuildVariant,enum=build_metrics.MetricsBase_BUILDVARIANT,def=2" json:"target_build_variant,omitempty"`
+ TargetBuildVariant *MetricsBase_BuildVariant `protobuf:"varint,5,opt,name=target_build_variant,json=targetBuildVariant,enum=soong_build_metrics.MetricsBase_BuildVariant,def=2" json:"target_build_variant,omitempty"`
// The target arch information, eg. arm.
- TargetArch *MetricsBase_ARCH `protobuf:"varint,6,opt,name=target_arch,json=targetArch,enum=build_metrics.MetricsBase_ARCH,def=0" json:"target_arch,omitempty"`
+ TargetArch *MetricsBase_Arch `protobuf:"varint,6,opt,name=target_arch,json=targetArch,enum=soong_build_metrics.MetricsBase_Arch,def=0" json:"target_arch,omitempty"`
// The target arch variant information, eg. armv7-a-neon.
TargetArchVariant *string `protobuf:"bytes,7,opt,name=target_arch_variant,json=targetArchVariant" json:"target_arch_variant,omitempty"`
// The target cpu variant information, eg. generic.
TargetCpuVariant *string `protobuf:"bytes,8,opt,name=target_cpu_variant,json=targetCpuVariant" json:"target_cpu_variant,omitempty"`
// The host arch information, eg. x86_64.
- HostArch *MetricsBase_ARCH `protobuf:"varint,9,opt,name=host_arch,json=hostArch,enum=build_metrics.MetricsBase_ARCH,def=0" json:"host_arch,omitempty"`
+ HostArch *MetricsBase_Arch `protobuf:"varint,9,opt,name=host_arch,json=hostArch,enum=soong_build_metrics.MetricsBase_Arch,def=0" json:"host_arch,omitempty"`
// The host 2nd arch information, eg. x86.
- Host_2NdArch *MetricsBase_ARCH `protobuf:"varint,10,opt,name=host_2nd_arch,json=host2ndArch,enum=build_metrics.MetricsBase_ARCH,def=0" json:"host_2nd_arch,omitempty"`
+ Host_2NdArch *MetricsBase_Arch `protobuf:"varint,10,opt,name=host_2nd_arch,json=host2ndArch,enum=soong_build_metrics.MetricsBase_Arch,def=0" json:"host_2nd_arch,omitempty"`
// The host os information, eg. linux.
HostOs *string `protobuf:"bytes,11,opt,name=host_os,json=hostOs" json:"host_os,omitempty"`
// The host os extra information, eg. Linux-4.17.0-3rodete2-amd64-x86_64-Debian-GNU.
@@ -191,16 +205,17 @@
func (m *MetricsBase) String() string { return proto.CompactTextString(m) }
func (*MetricsBase) ProtoMessage() {}
func (*MetricsBase) Descriptor() ([]byte, []int) {
- return fileDescriptor_metrics_9e7b895801991242, []int{0}
+ return fileDescriptor_6039342a2ba47b72, []int{0}
}
+
func (m *MetricsBase) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_MetricsBase.Unmarshal(m, b)
}
func (m *MetricsBase) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_MetricsBase.Marshal(b, m, deterministic)
}
-func (dst *MetricsBase) XXX_Merge(src proto.Message) {
- xxx_messageInfo_MetricsBase.Merge(dst, src)
+func (m *MetricsBase) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_MetricsBase.Merge(m, src)
}
func (m *MetricsBase) XXX_Size() int {
return xxx_messageInfo_MetricsBase.Size(m)
@@ -211,10 +226,10 @@
var xxx_messageInfo_MetricsBase proto.InternalMessageInfo
-const Default_MetricsBase_TargetBuildVariant MetricsBase_BUILDVARIANT = MetricsBase_ENG
-const Default_MetricsBase_TargetArch MetricsBase_ARCH = MetricsBase_UNKNOWN
-const Default_MetricsBase_HostArch MetricsBase_ARCH = MetricsBase_UNKNOWN
-const Default_MetricsBase_Host_2NdArch MetricsBase_ARCH = MetricsBase_UNKNOWN
+const Default_MetricsBase_TargetBuildVariant MetricsBase_BuildVariant = MetricsBase_ENG
+const Default_MetricsBase_TargetArch MetricsBase_Arch = MetricsBase_UNKNOWN
+const Default_MetricsBase_HostArch MetricsBase_Arch = MetricsBase_UNKNOWN
+const Default_MetricsBase_Host_2NdArch MetricsBase_Arch = MetricsBase_UNKNOWN
func (m *MetricsBase) GetBuildDateTimestamp() int64 {
if m != nil && m.BuildDateTimestamp != nil {
@@ -244,14 +259,14 @@
return ""
}
-func (m *MetricsBase) GetTargetBuildVariant() MetricsBase_BUILDVARIANT {
+func (m *MetricsBase) GetTargetBuildVariant() MetricsBase_BuildVariant {
if m != nil && m.TargetBuildVariant != nil {
return *m.TargetBuildVariant
}
return Default_MetricsBase_TargetBuildVariant
}
-func (m *MetricsBase) GetTargetArch() MetricsBase_ARCH {
+func (m *MetricsBase) GetTargetArch() MetricsBase_Arch {
if m != nil && m.TargetArch != nil {
return *m.TargetArch
}
@@ -272,14 +287,14 @@
return ""
}
-func (m *MetricsBase) GetHostArch() MetricsBase_ARCH {
+func (m *MetricsBase) GetHostArch() MetricsBase_Arch {
if m != nil && m.HostArch != nil {
return *m.HostArch
}
return Default_MetricsBase_HostArch
}
-func (m *MetricsBase) GetHost_2NdArch() MetricsBase_ARCH {
+func (m *MetricsBase) GetHost_2NdArch() MetricsBase_Arch {
if m != nil && m.Host_2NdArch != nil {
return *m.Host_2NdArch
}
@@ -378,16 +393,17 @@
func (m *PerfInfo) String() string { return proto.CompactTextString(m) }
func (*PerfInfo) ProtoMessage() {}
func (*PerfInfo) Descriptor() ([]byte, []int) {
- return fileDescriptor_metrics_9e7b895801991242, []int{1}
+ return fileDescriptor_6039342a2ba47b72, []int{1}
}
+
func (m *PerfInfo) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PerfInfo.Unmarshal(m, b)
}
func (m *PerfInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_PerfInfo.Marshal(b, m, deterministic)
}
-func (dst *PerfInfo) XXX_Merge(src proto.Message) {
- xxx_messageInfo_PerfInfo.Merge(dst, src)
+func (m *PerfInfo) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_PerfInfo.Merge(m, src)
}
func (m *PerfInfo) XXX_Size() int {
return xxx_messageInfo_PerfInfo.Size(m)
@@ -435,7 +451,7 @@
type ModuleTypeInfo struct {
// The build system, eg. Soong or Make.
- BuildSystem *ModuleTypeInfo_BUILDSYSTEM `protobuf:"varint,1,opt,name=build_system,json=buildSystem,enum=build_metrics.ModuleTypeInfo_BUILDSYSTEM,def=0" json:"build_system,omitempty"`
+ BuildSystem *ModuleTypeInfo_BuildSystem `protobuf:"varint,1,opt,name=build_system,json=buildSystem,enum=soong_build_metrics.ModuleTypeInfo_BuildSystem,def=0" json:"build_system,omitempty"`
// The module type, eg. java_library, cc_binary, and etc.
ModuleType *string `protobuf:"bytes,2,opt,name=module_type,json=moduleType" json:"module_type,omitempty"`
// The number of logical modules.
@@ -449,16 +465,17 @@
func (m *ModuleTypeInfo) String() string { return proto.CompactTextString(m) }
func (*ModuleTypeInfo) ProtoMessage() {}
func (*ModuleTypeInfo) Descriptor() ([]byte, []int) {
- return fileDescriptor_metrics_9e7b895801991242, []int{2}
+ return fileDescriptor_6039342a2ba47b72, []int{2}
}
+
func (m *ModuleTypeInfo) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ModuleTypeInfo.Unmarshal(m, b)
}
func (m *ModuleTypeInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ModuleTypeInfo.Marshal(b, m, deterministic)
}
-func (dst *ModuleTypeInfo) XXX_Merge(src proto.Message) {
- xxx_messageInfo_ModuleTypeInfo.Merge(dst, src)
+func (m *ModuleTypeInfo) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ModuleTypeInfo.Merge(m, src)
}
func (m *ModuleTypeInfo) XXX_Size() int {
return xxx_messageInfo_ModuleTypeInfo.Size(m)
@@ -469,9 +486,9 @@
var xxx_messageInfo_ModuleTypeInfo proto.InternalMessageInfo
-const Default_ModuleTypeInfo_BuildSystem ModuleTypeInfo_BUILDSYSTEM = ModuleTypeInfo_UNKNOWN
+const Default_ModuleTypeInfo_BuildSystem ModuleTypeInfo_BuildSystem = ModuleTypeInfo_UNKNOWN
-func (m *ModuleTypeInfo) GetBuildSystem() ModuleTypeInfo_BUILDSYSTEM {
+func (m *ModuleTypeInfo) GetBuildSystem() ModuleTypeInfo_BuildSystem {
if m != nil && m.BuildSystem != nil {
return *m.BuildSystem
}
@@ -493,65 +510,65 @@
}
func init() {
- proto.RegisterType((*MetricsBase)(nil), "build_metrics.MetricsBase")
- proto.RegisterType((*PerfInfo)(nil), "build_metrics.PerfInfo")
- proto.RegisterType((*ModuleTypeInfo)(nil), "build_metrics.ModuleTypeInfo")
- proto.RegisterEnum("build_metrics.MetricsBase_BUILDVARIANT", MetricsBase_BUILDVARIANT_name, MetricsBase_BUILDVARIANT_value)
- proto.RegisterEnum("build_metrics.MetricsBase_ARCH", MetricsBase_ARCH_name, MetricsBase_ARCH_value)
- proto.RegisterEnum("build_metrics.ModuleTypeInfo_BUILDSYSTEM", ModuleTypeInfo_BUILDSYSTEM_name, ModuleTypeInfo_BUILDSYSTEM_value)
+ proto.RegisterEnum("soong_build_metrics.MetricsBase_BuildVariant", MetricsBase_BuildVariant_name, MetricsBase_BuildVariant_value)
+ proto.RegisterEnum("soong_build_metrics.MetricsBase_Arch", MetricsBase_Arch_name, MetricsBase_Arch_value)
+ proto.RegisterEnum("soong_build_metrics.ModuleTypeInfo_BuildSystem", ModuleTypeInfo_BuildSystem_name, ModuleTypeInfo_BuildSystem_value)
+ proto.RegisterType((*MetricsBase)(nil), "soong_build_metrics.MetricsBase")
+ proto.RegisterType((*PerfInfo)(nil), "soong_build_metrics.PerfInfo")
+ proto.RegisterType((*ModuleTypeInfo)(nil), "soong_build_metrics.ModuleTypeInfo")
}
-func init() { proto.RegisterFile("metrics.proto", fileDescriptor_metrics_9e7b895801991242) }
+func init() { proto.RegisterFile("metrics.proto", fileDescriptor_6039342a2ba47b72) }
-var fileDescriptor_metrics_9e7b895801991242 = []byte{
- // 783 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0xdd, 0x6e, 0xdb, 0x36,
- 0x14, 0xae, 0x62, 0x25, 0x96, 0x8e, 0x62, 0x57, 0x61, 0x02, 0x44, 0xc5, 0x50, 0x34, 0x30, 0xf6,
- 0x93, 0x01, 0x9b, 0x57, 0x18, 0x81, 0x11, 0x04, 0xbb, 0xb1, 0x13, 0xa3, 0x35, 0x5a, 0xdb, 0x85,
- 0x6c, 0x67, 0xdd, 0x2e, 0x46, 0x68, 0x12, 0xdd, 0x68, 0xb3, 0x44, 0x81, 0xa4, 0x8a, 0xf9, 0x21,
- 0xf6, 0x8c, 0x7b, 0x91, 0x5d, 0x0c, 0x3c, 0xb4, 0x5c, 0xa5, 0x17, 0x29, 0x72, 0x47, 0x9d, 0xef,
- 0x87, 0xdf, 0x91, 0xc8, 0x23, 0x68, 0x65, 0x4c, 0x89, 0x34, 0x96, 0xdd, 0x42, 0x70, 0xc5, 0x49,
- 0xeb, 0x8f, 0x32, 0x5d, 0x27, 0x74, 0x5b, 0xec, 0xfc, 0xe7, 0x80, 0x37, 0x31, 0xeb, 0x61, 0x24,
- 0x19, 0x79, 0x09, 0x27, 0x86, 0x90, 0x44, 0x8a, 0x51, 0x95, 0x66, 0x4c, 0xaa, 0x28, 0x2b, 0x02,
- 0xeb, 0xcc, 0x3a, 0x6f, 0x84, 0x04, 0xb1, 0x9b, 0x48, 0xb1, 0x45, 0x85, 0x90, 0x67, 0xe0, 0x18,
- 0x45, 0x9a, 0x04, 0x7b, 0x67, 0xd6, 0xb9, 0x1b, 0x36, 0xf1, 0x79, 0x9c, 0x90, 0x2b, 0x78, 0x56,
- 0xac, 0x23, 0xb5, 0xe2, 0x22, 0xa3, 0x1f, 0x99, 0x90, 0x29, 0xcf, 0x69, 0xcc, 0x13, 0x96, 0x47,
- 0x19, 0x0b, 0x1a, 0xc8, 0x3d, 0xad, 0x08, 0xb7, 0x06, 0xbf, 0xde, 0xc2, 0xe4, 0x1b, 0x68, 0xab,
- 0x48, 0x7c, 0x60, 0x8a, 0x16, 0x82, 0x27, 0x65, 0xac, 0x02, 0x1b, 0x05, 0x2d, 0x53, 0x7d, 0x67,
- 0x8a, 0xe4, 0x77, 0x38, 0xd9, 0xd2, 0x4c, 0x88, 0x8f, 0x91, 0x48, 0xa3, 0x5c, 0x05, 0xfb, 0x67,
- 0xd6, 0x79, 0xbb, 0xf7, 0x5d, 0xf7, 0x5e, 0xb7, 0xdd, 0x5a, 0xa7, 0xdd, 0xe1, 0x72, 0xfc, 0xf6,
- 0xe6, 0x76, 0x10, 0x8e, 0x07, 0xd3, 0xc5, 0x55, 0x63, 0x34, 0x7d, 0x15, 0x12, 0xe3, 0x34, 0xd4,
- 0x92, 0x5b, 0xe3, 0x43, 0xc6, 0xe0, 0x6d, 0xfd, 0x23, 0x11, 0xdf, 0x05, 0x07, 0x68, 0xfb, 0xe2,
- 0x01, 0xdb, 0x41, 0x78, 0xfd, 0xfa, 0xaa, 0xb9, 0x9c, 0xbe, 0x99, 0xce, 0x7e, 0x99, 0x86, 0x60,
- 0xc4, 0x03, 0x11, 0xdf, 0x91, 0x2e, 0x1c, 0xd7, 0xac, 0x76, 0x49, 0x9b, 0xd8, 0xd6, 0xd1, 0x27,
- 0x62, 0xb5, 0xf5, 0x0f, 0xb0, 0x0d, 0x44, 0xe3, 0xa2, 0xdc, 0xd1, 0x1d, 0xa4, 0xfb, 0x06, 0xb9,
- 0x2e, 0xca, 0x8a, 0x3d, 0x02, 0xf7, 0x8e, 0xcb, 0x6d, 0x4c, 0xf7, 0x91, 0x31, 0x1d, 0x2d, 0xc5,
- 0x90, 0x6f, 0xa1, 0x85, 0x36, 0xbd, 0x3c, 0x31, 0x56, 0xf0, 0x48, 0x2b, 0x4f, 0xcb, 0x7b, 0x79,
- 0x82, 0x6e, 0xa7, 0xd0, 0x44, 0x37, 0x2e, 0x03, 0x0f, 0x73, 0x1f, 0xe8, 0xc7, 0x99, 0x24, 0x9d,
- 0xed, 0x36, 0x5c, 0x52, 0xf6, 0xb7, 0x12, 0x51, 0x70, 0x88, 0xb0, 0x67, 0xe0, 0x91, 0x2e, 0xed,
- 0x38, 0xb1, 0xe0, 0x52, 0x6a, 0x8b, 0xd6, 0x27, 0xce, 0xb5, 0xae, 0xcd, 0x24, 0xf9, 0x16, 0x9e,
- 0xd6, 0x38, 0x18, 0xb8, 0x6d, 0x8e, 0xc9, 0x8e, 0x85, 0x41, 0x7e, 0x84, 0xe3, 0x1a, 0x6f, 0xd7,
- 0xdc, 0x53, 0xf3, 0x32, 0x77, 0xdc, 0x5a, 0x6e, 0x5e, 0x2a, 0x9a, 0xa4, 0x22, 0xf0, 0x4d, 0x6e,
- 0x5e, 0xaa, 0x9b, 0x54, 0x90, 0x4b, 0xf0, 0x24, 0x53, 0x65, 0x41, 0x15, 0xe7, 0x6b, 0x19, 0x1c,
- 0x9d, 0x35, 0xce, 0xbd, 0xde, 0xe9, 0x67, 0x2f, 0xe7, 0x1d, 0x13, 0xab, 0x71, 0xbe, 0xe2, 0x21,
- 0x20, 0x77, 0xa1, 0xa9, 0xe4, 0x02, 0xdc, 0xbf, 0x22, 0x95, 0x52, 0x51, 0xe6, 0x32, 0x20, 0x0f,
- 0xeb, 0x1c, 0xcd, 0x0c, 0xcb, 0x5c, 0x92, 0x3e, 0x80, 0xe4, 0x3c, 0xff, 0x60, 0x64, 0xc7, 0x0f,
- 0xcb, 0x5c, 0xa4, 0x56, 0xba, 0x3c, 0xcd, 0xff, 0x8c, 0x8c, 0xee, 0xe4, 0x0b, 0x3a, 0xa4, 0x6a,
- 0x5d, 0xe7, 0x25, 0x1c, 0xd6, 0xef, 0x05, 0x71, 0xc0, 0x5e, 0xce, 0x47, 0xa1, 0xff, 0x84, 0xb4,
- 0xc0, 0xd5, 0xab, 0x9b, 0xd1, 0x70, 0xf9, 0xca, 0xb7, 0x48, 0x13, 0xf4, 0x95, 0xf1, 0xf7, 0x3a,
- 0x3f, 0x83, 0xad, 0x0f, 0x00, 0xf1, 0xa0, 0x3a, 0x02, 0xfe, 0x13, 0x8d, 0x0e, 0xc2, 0x89, 0x6f,
- 0x11, 0x17, 0xf6, 0x07, 0xe1, 0xa4, 0x7f, 0xe1, 0xef, 0xe9, 0xda, 0xfb, 0xcb, 0xbe, 0xdf, 0x20,
- 0x00, 0x07, 0xef, 0x2f, 0xfb, 0xb4, 0x7f, 0xe1, 0xdb, 0x9d, 0x7f, 0x2c, 0x70, 0xaa, 0x1c, 0x84,
- 0x80, 0x9d, 0x30, 0x19, 0xe3, 0xac, 0x71, 0x43, 0x5c, 0xeb, 0x1a, 0x4e, 0x0b, 0x33, 0x59, 0x70,
- 0x4d, 0x9e, 0x03, 0x48, 0x15, 0x09, 0x85, 0xe3, 0x09, 0xe7, 0x88, 0x1d, 0xba, 0x58, 0xd1, 0x53,
- 0x89, 0x7c, 0x05, 0xae, 0x60, 0xd1, 0xda, 0xa0, 0x36, 0xa2, 0x8e, 0x2e, 0x20, 0xf8, 0x1c, 0x20,
- 0x63, 0x19, 0x17, 0x1b, 0x5a, 0x4a, 0x86, 0x53, 0xc2, 0x0e, 0x5d, 0x53, 0x59, 0x4a, 0xd6, 0xf9,
- 0xd7, 0x82, 0xf6, 0x84, 0x27, 0xe5, 0x9a, 0x2d, 0x36, 0x05, 0xc3, 0x54, 0x4b, 0x38, 0x34, 0xef,
- 0x4d, 0x6e, 0xa4, 0x62, 0x19, 0xa6, 0x6b, 0xf7, 0xbe, 0xff, 0xfc, 0x42, 0xdc, 0x13, 0x99, 0xe1,
- 0x32, 0xff, 0x75, 0xbe, 0x18, 0x4d, 0x6a, 0x57, 0x03, 0x25, 0x73, 0xb4, 0x21, 0x2f, 0xc0, 0xcb,
- 0x50, 0x43, 0xd5, 0xa6, 0xa8, 0xfa, 0x83, 0x6c, 0x67, 0x43, 0xbe, 0x86, 0x76, 0x5e, 0x66, 0x94,
- 0xaf, 0xa8, 0x29, 0x4a, 0xec, 0xb4, 0x15, 0x1e, 0xe6, 0x65, 0x36, 0x5b, 0x99, 0xfd, 0x64, 0xe7,
- 0x27, 0xf0, 0x6a, 0x7b, 0xdd, 0xff, 0x0a, 0x2e, 0xec, 0xcf, 0x67, 0xb3, 0xa9, 0xfe, 0x5c, 0x0e,
- 0xd8, 0x93, 0xc1, 0x9b, 0x91, 0xbf, 0x37, 0x3c, 0x7a, 0xdd, 0xf8, 0xad, 0xfa, 0x25, 0x50, 0xfc,
- 0x25, 0xfc, 0x1f, 0x00, 0x00, 0xff, 0xff, 0xd4, 0x8d, 0x19, 0x89, 0x22, 0x06, 0x00, 0x00,
+var fileDescriptor_6039342a2ba47b72 = []byte{
+ // 769 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0x6f, 0x6b, 0xdb, 0x46,
+ 0x18, 0xaf, 0x62, 0x25, 0x96, 0x1e, 0xc5, 0xae, 0x7a, 0xc9, 0xa8, 0xca, 0x08, 0x33, 0x66, 0x1d,
+ 0x7e, 0xb1, 0xba, 0xc5, 0x14, 0x53, 0x4c, 0x19, 0xd8, 0x89, 0x29, 0x25, 0xd8, 0x2e, 0x4a, 0xdc,
+ 0x95, 0xed, 0xc5, 0xa1, 0x4a, 0xe7, 0x46, 0x9b, 0xa5, 0x13, 0x77, 0xa7, 0x32, 0x7f, 0x88, 0x7d,
+ 0x93, 0x7d, 0xad, 0x7d, 0x8f, 0x71, 0xcf, 0x49, 0x8e, 0x02, 0x81, 0x85, 0xbe, 0x3b, 0x3d, 0xbf,
+ 0x3f, 0xf7, 0x7b, 0x4e, 0xba, 0x47, 0xd0, 0xc9, 0x98, 0x12, 0x69, 0x2c, 0x87, 0x85, 0xe0, 0x8a,
+ 0x93, 0x13, 0xc9, 0x79, 0xfe, 0x85, 0x7e, 0x2e, 0xd3, 0x6d, 0x42, 0x2b, 0xa8, 0xff, 0x8f, 0x0b,
+ 0xde, 0xc2, 0xac, 0x67, 0x91, 0x64, 0xe4, 0x15, 0x9c, 0x1a, 0x42, 0x12, 0x29, 0x46, 0x55, 0x9a,
+ 0x31, 0xa9, 0xa2, 0xac, 0x08, 0xac, 0x9e, 0x35, 0x68, 0x85, 0x04, 0xb1, 0x8b, 0x48, 0xb1, 0xeb,
+ 0x1a, 0x21, 0xcf, 0xc0, 0x31, 0x8a, 0x34, 0x09, 0x0e, 0x7a, 0xd6, 0xc0, 0x0d, 0xdb, 0xf8, 0xfc,
+ 0x3e, 0x21, 0x13, 0x78, 0x56, 0x6c, 0x23, 0xb5, 0xe1, 0x22, 0xa3, 0x5f, 0x99, 0x90, 0x29, 0xcf,
+ 0x69, 0xcc, 0x13, 0x96, 0x47, 0x19, 0x0b, 0x5a, 0xc8, 0x7d, 0x5a, 0x13, 0x3e, 0x1a, 0xfc, 0xbc,
+ 0x82, 0xc9, 0x73, 0xe8, 0xaa, 0x48, 0x7c, 0x61, 0x8a, 0x16, 0x82, 0x27, 0x65, 0xac, 0x02, 0x1b,
+ 0x05, 0x1d, 0x53, 0xfd, 0x60, 0x8a, 0x24, 0x81, 0xd3, 0x8a, 0x66, 0x42, 0x7c, 0x8d, 0x44, 0x1a,
+ 0xe5, 0x2a, 0x38, 0xec, 0x59, 0x83, 0xee, 0xe8, 0xc5, 0xf0, 0x9e, 0x9e, 0x87, 0x8d, 0x7e, 0x87,
+ 0x33, 0x8d, 0x7c, 0x34, 0xa2, 0x49, 0x6b, 0xbe, 0x7c, 0x17, 0x12, 0xe3, 0xd7, 0x04, 0xc8, 0x0a,
+ 0xbc, 0x6a, 0x97, 0x48, 0xc4, 0x37, 0xc1, 0x11, 0x9a, 0x3f, 0xff, 0x5f, 0xf3, 0xa9, 0x88, 0x6f,
+ 0x26, 0xed, 0xf5, 0xf2, 0x72, 0xb9, 0xfa, 0x75, 0x19, 0x82, 0xb1, 0xd0, 0x45, 0x32, 0x84, 0x93,
+ 0x86, 0xe1, 0x3e, 0x75, 0x1b, 0x5b, 0x7c, 0x72, 0x4b, 0xac, 0x03, 0xfc, 0x0c, 0x55, 0x2c, 0x1a,
+ 0x17, 0xe5, 0x9e, 0xee, 0x20, 0xdd, 0x37, 0xc8, 0x79, 0x51, 0xd6, 0xec, 0x4b, 0x70, 0x6f, 0xb8,
+ 0xac, 0xc2, 0xba, 0xdf, 0x14, 0xd6, 0xd1, 0x06, 0x18, 0x35, 0x84, 0x0e, 0x9a, 0x8d, 0xf2, 0xc4,
+ 0x18, 0xc2, 0x37, 0x19, 0x7a, 0xda, 0x64, 0x94, 0x27, 0xe8, 0xf9, 0x14, 0xda, 0xe8, 0xc9, 0x65,
+ 0xe0, 0x61, 0x0f, 0x47, 0xfa, 0x71, 0x25, 0x49, 0xbf, 0xda, 0x8c, 0x4b, 0xca, 0xfe, 0x52, 0x22,
+ 0x0a, 0x8e, 0x11, 0xf6, 0x0c, 0x3c, 0xd7, 0xa5, 0x3d, 0x27, 0x16, 0x5c, 0x4a, 0x6d, 0xd1, 0xb9,
+ 0xe5, 0x9c, 0xeb, 0xda, 0x4a, 0x92, 0x9f, 0xe0, 0x71, 0x83, 0x83, 0xb1, 0xbb, 0xe6, 0xf3, 0xd9,
+ 0xb3, 0x30, 0xc8, 0x0b, 0x38, 0x69, 0xf0, 0xf6, 0x2d, 0x3e, 0x36, 0x07, 0xbb, 0xe7, 0x36, 0x72,
+ 0xf3, 0x52, 0xd1, 0x24, 0x15, 0x81, 0x6f, 0x72, 0xf3, 0x52, 0x5d, 0xa4, 0x82, 0xfc, 0x02, 0x9e,
+ 0x64, 0xaa, 0x2c, 0xa8, 0xe2, 0x7c, 0x2b, 0x83, 0x27, 0xbd, 0xd6, 0xc0, 0x1b, 0x9d, 0xdd, 0x7b,
+ 0x44, 0x1f, 0x98, 0xd8, 0xbc, 0xcf, 0x37, 0x3c, 0x04, 0x54, 0x5c, 0x6b, 0x01, 0x99, 0x80, 0xfb,
+ 0x67, 0xa4, 0x52, 0x2a, 0xca, 0x5c, 0x06, 0xe4, 0x21, 0x6a, 0x47, 0xf3, 0xc3, 0x32, 0x97, 0xe4,
+ 0x2d, 0x80, 0x61, 0xa2, 0xf8, 0xe4, 0x21, 0x62, 0x17, 0xd1, 0x5a, 0x9d, 0xa7, 0xf9, 0x1f, 0x91,
+ 0x51, 0x9f, 0x3e, 0x48, 0x8d, 0x02, 0xad, 0xee, 0xbf, 0x82, 0xe3, 0x3b, 0x17, 0xc5, 0x01, 0x7b,
+ 0x7d, 0x35, 0x0f, 0xfd, 0x47, 0xa4, 0x03, 0xae, 0x5e, 0x5d, 0xcc, 0x67, 0xeb, 0x77, 0xbe, 0x45,
+ 0xda, 0xa0, 0x2f, 0x97, 0x7f, 0xd0, 0x7f, 0x0b, 0x36, 0x1e, 0xa5, 0x07, 0xf5, 0xa7, 0xe1, 0x3f,
+ 0xd2, 0xe8, 0x34, 0x5c, 0xf8, 0x16, 0x71, 0xe1, 0x70, 0x1a, 0x2e, 0xc6, 0xaf, 0xfd, 0x03, 0x5d,
+ 0xfb, 0xf4, 0x66, 0xec, 0xb7, 0x08, 0xc0, 0xd1, 0xa7, 0x37, 0x63, 0x3a, 0x7e, 0xed, 0xdb, 0xfd,
+ 0xbf, 0x2d, 0x70, 0xea, 0x1c, 0x84, 0x80, 0x9d, 0x30, 0x19, 0xe3, 0x6c, 0x72, 0x43, 0x5c, 0xeb,
+ 0x1a, 0x4e, 0x17, 0x33, 0x89, 0x70, 0x4d, 0xce, 0x00, 0xa4, 0x8a, 0x84, 0xc2, 0x71, 0x86, 0x73,
+ 0xc7, 0x0e, 0x5d, 0xac, 0xe8, 0x29, 0x46, 0xbe, 0x07, 0x57, 0xb0, 0x68, 0x6b, 0x50, 0x1b, 0x51,
+ 0x47, 0x17, 0x10, 0x3c, 0x03, 0xc8, 0x58, 0xc6, 0xc5, 0x8e, 0x96, 0x92, 0xe1, 0x54, 0xb1, 0x43,
+ 0xd7, 0x54, 0xd6, 0x92, 0xf5, 0xff, 0xb5, 0xa0, 0xbb, 0xe0, 0x49, 0xb9, 0x65, 0xd7, 0xbb, 0x82,
+ 0x61, 0xaa, 0xdf, 0xe1, 0xd8, 0x9c, 0x9b, 0xdc, 0x49, 0xc5, 0x32, 0x4c, 0xd7, 0x1d, 0xbd, 0xbc,
+ 0xff, 0xba, 0xdc, 0x91, 0x9a, 0x61, 0x74, 0x85, 0xb2, 0xc6, 0xc5, 0xf9, 0x7c, 0x5b, 0x25, 0x3f,
+ 0x80, 0x97, 0xa1, 0x86, 0xaa, 0x5d, 0x51, 0x77, 0x09, 0xd9, 0xde, 0x86, 0xfc, 0x08, 0xdd, 0xbc,
+ 0xcc, 0x28, 0xdf, 0x50, 0x53, 0x94, 0xd8, 0x6f, 0x27, 0x3c, 0xce, 0xcb, 0x6c, 0xb5, 0x31, 0xfb,
+ 0xc9, 0xfe, 0x4b, 0xf0, 0x1a, 0x7b, 0xdd, 0x7d, 0x17, 0x2e, 0x1c, 0x5e, 0xad, 0x56, 0x4b, 0xfd,
+ 0xd2, 0x1c, 0xb0, 0x17, 0xd3, 0xcb, 0xb9, 0x7f, 0x30, 0xfb, 0xee, 0xb7, 0xea, 0xef, 0x51, 0x25,
+ 0xa7, 0xf8, 0x4b, 0xf9, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x81, 0xd0, 0x84, 0x23, 0x62, 0x06, 0x00,
+ 0x00,
}
diff --git a/ui/metrics/metrics_proto/metrics.proto b/ui/metrics/metrics_proto/metrics.proto
index b3de2f4..93034eb 100644
--- a/ui/metrics/metrics_proto/metrics.proto
+++ b/ui/metrics/metrics_proto/metrics.proto
@@ -14,10 +14,8 @@
syntax = "proto2";
-option optimize_for = LITE_RUNTIME;
-
-package build_metrics;
-option go_package = "metrics_proto";
+package soong_build_metrics;
+option go_package = "soong_metrics_proto";
message MetricsBase {
// Timestamp generated when the build starts.
@@ -32,15 +30,15 @@
// The target product information, eg. aosp_arm.
optional string target_product = 4;
- enum BUILDVARIANT {
+ enum BuildVariant {
USER = 0;
USERDEBUG = 1;
ENG = 2;
}
// The target build variant information, eg. eng.
- optional BUILDVARIANT target_build_variant = 5 [default = ENG];
+ optional BuildVariant target_build_variant = 5 [default = ENG];
- enum ARCH {
+ enum Arch {
UNKNOWN = 0;
ARM = 1;
ARM64 = 2;
@@ -48,7 +46,7 @@
X86_64 = 4;
}
// The target arch information, eg. arm.
- optional ARCH target_arch = 6 [default = UNKNOWN];
+ optional Arch target_arch = 6 [default = UNKNOWN];
// The target arch variant information, eg. armv7-a-neon.
optional string target_arch_variant = 7;
@@ -57,10 +55,10 @@
optional string target_cpu_variant = 8;
// The host arch information, eg. x86_64.
- optional ARCH host_arch = 9 [default = UNKNOWN];
+ optional Arch host_arch = 9 [default = UNKNOWN];
// The host 2nd arch information, eg. x86.
- optional ARCH host_2nd_arch = 10 [default = UNKNOWN];
+ optional Arch host_2nd_arch = 10 [default = UNKNOWN];
// The host os information, eg. linux.
optional string host_os = 11;
@@ -113,13 +111,13 @@
}
message ModuleTypeInfo {
- enum BUILDSYSTEM {
+ enum BuildSystem {
UNKNOWN = 0;
SOONG = 1;
MAKE = 2;
}
// The build system, eg. Soong or Make.
- optional BUILDSYSTEM build_system = 1 [default = UNKNOWN];
+ optional BuildSystem build_system = 1 [default = UNKNOWN];
// The module type, eg. java_library, cc_binary, and etc.
optional string module_type = 2;
diff --git a/ui/metrics/time.go b/ui/metrics/time.go
index 7e8801a..b8baf16 100644
--- a/ui/metrics/time.go
+++ b/ui/metrics/time.go
@@ -30,7 +30,7 @@
type TimeTracer interface {
Begin(name, desc string, thread tracer.Thread)
- End(thread tracer.Thread) metrics_proto.PerfInfo
+ End(thread tracer.Thread) soong_metrics_proto.PerfInfo
}
type timeTracerImpl struct {
@@ -51,11 +51,11 @@
t.activeEvents = append(t.activeEvents, timeEvent{name: name, desc: desc, atNanos: atNanos})
}
-func (t *timeTracerImpl) End(thread tracer.Thread) metrics_proto.PerfInfo {
+func (t *timeTracerImpl) End(thread tracer.Thread) soong_metrics_proto.PerfInfo {
return t.endAt(t.now())
}
-func (t *timeTracerImpl) endAt(atNanos uint64) metrics_proto.PerfInfo {
+func (t *timeTracerImpl) endAt(atNanos uint64) soong_metrics_proto.PerfInfo {
if len(t.activeEvents) < 1 {
panic("Internal error: No pending events for endAt to end!")
}
@@ -63,7 +63,7 @@
t.activeEvents = t.activeEvents[:len(t.activeEvents)-1]
realTime := atNanos - lastEvent.atNanos
- return metrics_proto.PerfInfo{
+ return soong_metrics_proto.PerfInfo{
Desc: &lastEvent.desc,
Name: &lastEvent.name,
StartTime: &lastEvent.atNanos,
diff --git a/ui/status/log.go b/ui/status/log.go
index 921aa44..7badac7 100644
--- a/ui/status/log.go
+++ b/ui/status/log.go
@@ -71,6 +71,11 @@
fmt.Fprintf(v.w, "%s%s\n", level.Prefix(), message)
}
+func (v *verboseLog) Write(p []byte) (int, error) {
+ fmt.Fprint(v.w, string(p))
+ return len(p), nil
+}
+
type errorLog struct {
w io.WriteCloser
@@ -134,3 +139,8 @@
fmt.Fprintf(e.w, "error: %s\n", message)
}
+
+func (e *errorLog) Write(p []byte) (int, error) {
+ fmt.Fprint(e.w, string(p))
+ return len(p), nil
+}
diff --git a/ui/status/status.go b/ui/status/status.go
index 46ec72e..3d8cd7a 100644
--- a/ui/status/status.go
+++ b/ui/status/status.go
@@ -173,6 +173,9 @@
// Flush is called when your outputs should be flushed / closed. No
// output is expected after this call.
Flush()
+
+ // Write lets StatusOutput implement io.Writer
+ Write(p []byte) (n int, err error)
}
// Status is the multiplexer / accumulator between ToolStatus instances (via
diff --git a/ui/status/status_test.go b/ui/status/status_test.go
index e62785f..9494582 100644
--- a/ui/status/status_test.go
+++ b/ui/status/status_test.go
@@ -27,6 +27,11 @@
func (c counterOutput) Message(level MsgLevel, msg string) {}
func (c counterOutput) Flush() {}
+func (c counterOutput) Write(p []byte) (int, error) {
+ // Discard writes
+ return len(p), nil
+}
+
func (c counterOutput) Expect(t *testing.T, counts Counts) {
if Counts(c) == counts {
return
diff --git a/ui/terminal/Android.bp b/ui/terminal/Android.bp
index 7104a50..b533b0d 100644
--- a/ui/terminal/Android.bp
+++ b/ui/terminal/Android.bp
@@ -17,11 +17,15 @@
pkgPath: "android/soong/ui/terminal",
deps: ["soong-ui-status"],
srcs: [
+ "dumb_status.go",
+ "format.go",
+ "smart_status.go",
"status.go",
- "writer.go",
+ "stdio.go",
"util.go",
],
testSrcs: [
+ "status_test.go",
"util_test.go",
],
darwin: {
diff --git a/ui/terminal/dumb_status.go b/ui/terminal/dumb_status.go
new file mode 100644
index 0000000..201770f
--- /dev/null
+++ b/ui/terminal/dumb_status.go
@@ -0,0 +1,71 @@
+// Copyright 2019 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 terminal
+
+import (
+ "fmt"
+ "io"
+
+ "android/soong/ui/status"
+)
+
+type dumbStatusOutput struct {
+ writer io.Writer
+ formatter formatter
+}
+
+// NewDumbStatusOutput returns a StatusOutput that represents the
+// current build status similarly to Ninja's built-in terminal
+// output.
+func NewDumbStatusOutput(w io.Writer, formatter formatter) status.StatusOutput {
+ return &dumbStatusOutput{
+ writer: w,
+ formatter: formatter,
+ }
+}
+
+func (s *dumbStatusOutput) Message(level status.MsgLevel, message string) {
+ if level >= status.StatusLvl {
+ fmt.Fprintln(s.writer, s.formatter.message(level, message))
+ }
+}
+
+func (s *dumbStatusOutput) StartAction(action *status.Action, counts status.Counts) {
+}
+
+func (s *dumbStatusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
+ str := result.Description
+ if str == "" {
+ str = result.Command
+ }
+
+ progress := s.formatter.progress(counts) + str
+
+ output := s.formatter.result(result)
+ output = string(stripAnsiEscapes([]byte(output)))
+
+ if output != "" {
+ fmt.Fprint(s.writer, progress, "\n", output)
+ } else {
+ fmt.Fprintln(s.writer, progress)
+ }
+}
+
+func (s *dumbStatusOutput) Flush() {}
+
+func (s *dumbStatusOutput) Write(p []byte) (int, error) {
+ fmt.Fprint(s.writer, string(p))
+ return len(p), nil
+}
diff --git a/ui/terminal/format.go b/ui/terminal/format.go
new file mode 100644
index 0000000..4205bdc
--- /dev/null
+++ b/ui/terminal/format.go
@@ -0,0 +1,123 @@
+// Copyright 2019 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 terminal
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "android/soong/ui/status"
+)
+
+type formatter struct {
+ format string
+ quiet bool
+ start time.Time
+}
+
+// newFormatter returns a formatter for formatting output to
+// the terminal in a format similar to Ninja.
+// format takes nearly all the same options as NINJA_STATUS.
+// %c is currently unsupported.
+func newFormatter(format string, quiet bool) formatter {
+ return formatter{
+ format: format,
+ quiet: quiet,
+ start: time.Now(),
+ }
+}
+
+func (s formatter) message(level status.MsgLevel, message string) string {
+ if level >= status.ErrorLvl {
+ return fmt.Sprintf("FAILED: %s", message)
+ } else if level > status.StatusLvl {
+ return fmt.Sprintf("%s%s", level.Prefix(), message)
+ } else if level == status.StatusLvl {
+ return message
+ }
+ return ""
+}
+
+func (s formatter) progress(counts status.Counts) string {
+ if s.format == "" {
+ return fmt.Sprintf("[%3d%% %d/%d] ", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
+ }
+
+ buf := &strings.Builder{}
+ for i := 0; i < len(s.format); i++ {
+ c := s.format[i]
+ if c != '%' {
+ buf.WriteByte(c)
+ continue
+ }
+
+ i = i + 1
+ if i == len(s.format) {
+ buf.WriteByte(c)
+ break
+ }
+
+ c = s.format[i]
+ switch c {
+ case '%':
+ buf.WriteByte(c)
+ case 's':
+ fmt.Fprintf(buf, "%d", counts.StartedActions)
+ case 't':
+ fmt.Fprintf(buf, "%d", counts.TotalActions)
+ case 'r':
+ fmt.Fprintf(buf, "%d", counts.RunningActions)
+ case 'u':
+ fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions)
+ case 'f':
+ fmt.Fprintf(buf, "%d", counts.FinishedActions)
+ case 'o':
+ fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds())
+ case 'c':
+ // TODO: implement?
+ buf.WriteRune('?')
+ case 'p':
+ fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions)
+ case 'e':
+ fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds())
+ default:
+ buf.WriteString("unknown placeholder '")
+ buf.WriteByte(c)
+ buf.WriteString("'")
+ }
+ }
+ return buf.String()
+}
+
+func (s formatter) result(result status.ActionResult) string {
+ var ret string
+ if result.Error != nil {
+ targets := strings.Join(result.Outputs, " ")
+ if s.quiet || result.Command == "" {
+ ret = fmt.Sprintf("FAILED: %s\n%s", targets, result.Output)
+ } else {
+ ret = fmt.Sprintf("FAILED: %s\n%s\n%s", targets, result.Command, result.Output)
+ }
+ } else if result.Output != "" {
+ ret = result.Output
+ }
+
+ if len(ret) > 0 && ret[len(ret)-1] != '\n' {
+ ret += "\n"
+ }
+
+ return ret
+}
diff --git a/ui/terminal/smart_status.go b/ui/terminal/smart_status.go
new file mode 100644
index 0000000..9638cdf
--- /dev/null
+++ b/ui/terminal/smart_status.go
@@ -0,0 +1,412 @@
+// Copyright 2019 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 terminal
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "os/signal"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+ "time"
+
+ "android/soong/ui/status"
+)
+
+const tableHeightEnVar = "SOONG_UI_TABLE_HEIGHT"
+
+type actionTableEntry struct {
+ action *status.Action
+ startTime time.Time
+}
+
+type smartStatusOutput struct {
+ writer io.Writer
+ formatter formatter
+
+ lock sync.Mutex
+
+ haveBlankLine bool
+
+ tableMode bool
+ tableHeight int
+ requestedTableHeight int
+ termWidth, termHeight int
+
+ runningActions []actionTableEntry
+ ticker *time.Ticker
+ done chan bool
+ sigwinch chan os.Signal
+ sigwinchHandled chan bool
+}
+
+// NewSmartStatusOutput returns a StatusOutput that represents the
+// current build status similarly to Ninja's built-in terminal
+// output.
+func NewSmartStatusOutput(w io.Writer, formatter formatter) status.StatusOutput {
+ tableHeight, _ := strconv.Atoi(os.Getenv(tableHeightEnVar))
+
+ s := &smartStatusOutput{
+ writer: w,
+ formatter: formatter,
+
+ haveBlankLine: true,
+
+ tableMode: tableHeight > 0,
+ requestedTableHeight: tableHeight,
+
+ done: make(chan bool),
+ sigwinch: make(chan os.Signal),
+ }
+
+ s.updateTermSize()
+
+ if s.tableMode {
+ // Add empty lines at the bottom of the screen to scroll back the existing history
+ // and make room for the action table.
+ // TODO: read the cursor position to see if the empty lines are necessary?
+ for i := 0; i < s.tableHeight; i++ {
+ fmt.Fprintln(w)
+ }
+
+ // Hide the cursor to prevent seeing it bouncing around
+ fmt.Fprintf(s.writer, ansi.hideCursor())
+
+ // Configure the empty action table
+ s.actionTable()
+
+ // Start a tick to update the action table periodically
+ s.startActionTableTick()
+ }
+
+ s.startSigwinch()
+
+ return s
+}
+
+func (s *smartStatusOutput) Message(level status.MsgLevel, message string) {
+ if level < status.StatusLvl {
+ return
+ }
+
+ str := s.formatter.message(level, message)
+
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ if level > status.StatusLvl {
+ s.print(str)
+ } else {
+ s.statusLine(str)
+ }
+}
+
+func (s *smartStatusOutput) StartAction(action *status.Action, counts status.Counts) {
+ startTime := time.Now()
+
+ str := action.Description
+ if str == "" {
+ str = action.Command
+ }
+
+ progress := s.formatter.progress(counts)
+
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ s.runningActions = append(s.runningActions, actionTableEntry{
+ action: action,
+ startTime: startTime,
+ })
+
+ s.statusLine(progress + str)
+}
+
+func (s *smartStatusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
+ str := result.Description
+ if str == "" {
+ str = result.Command
+ }
+
+ progress := s.formatter.progress(counts) + str
+
+ output := s.formatter.result(result)
+
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ for i, runningAction := range s.runningActions {
+ if runningAction.action == result.Action {
+ s.runningActions = append(s.runningActions[:i], s.runningActions[i+1:]...)
+ break
+ }
+ }
+
+ if output != "" {
+ s.statusLine(progress)
+ s.requestLine()
+ s.print(output)
+ } else {
+ s.statusLine(progress)
+ }
+}
+
+func (s *smartStatusOutput) Flush() {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ s.stopSigwinch()
+
+ s.requestLine()
+
+ s.runningActions = nil
+
+ if s.tableMode {
+ s.stopActionTableTick()
+
+ // Update the table after clearing runningActions to clear it
+ s.actionTable()
+
+ // Reset the scrolling region to the whole terminal
+ 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))
+ // Turn the cursor back on
+ fmt.Fprintf(s.writer, ansi.showCursor())
+ }
+}
+
+func (s *smartStatusOutput) Write(p []byte) (int, error) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+ s.print(string(p))
+ return len(p), nil
+}
+
+func (s *smartStatusOutput) requestLine() {
+ if !s.haveBlankLine {
+ fmt.Fprintln(s.writer)
+ s.haveBlankLine = true
+ }
+}
+
+func (s *smartStatusOutput) print(str string) {
+ if !s.haveBlankLine {
+ fmt.Fprint(s.writer, "\r", ansi.clearToEndOfLine())
+ s.haveBlankLine = true
+ }
+ fmt.Fprint(s.writer, str)
+ if len(str) == 0 || str[len(str)-1] != '\n' {
+ fmt.Fprint(s.writer, "\n")
+ }
+}
+
+func (s *smartStatusOutput) statusLine(str string) {
+ idx := strings.IndexRune(str, '\n')
+ if idx != -1 {
+ str = str[0:idx]
+ }
+
+ // Limit line width to the terminal width, otherwise we'll wrap onto
+ // another line and we won't delete the previous line.
+ if s.termWidth > 0 {
+ str = s.elide(str)
+ }
+
+ // Move to the beginning on the line, turn on bold, print the output,
+ // turn off bold, then clear the rest of the line.
+ start := "\r" + ansi.bold()
+ end := ansi.regular() + ansi.clearToEndOfLine()
+ fmt.Fprint(s.writer, start, str, end)
+ s.haveBlankLine = false
+}
+
+func (s *smartStatusOutput) elide(str string) string {
+ if len(str) > s.termWidth {
+ // TODO: Just do a max. Ninja elides the middle, but that's
+ // more complicated and these lines aren't that important.
+ str = str[:s.termWidth]
+ }
+
+ return str
+}
+
+func (s *smartStatusOutput) startActionTableTick() {
+ s.ticker = time.NewTicker(time.Second)
+ go func() {
+ for {
+ select {
+ case <-s.ticker.C:
+ s.lock.Lock()
+ s.actionTable()
+ s.lock.Unlock()
+ case <-s.done:
+ return
+ }
+ }
+ }()
+}
+
+func (s *smartStatusOutput) stopActionTableTick() {
+ s.ticker.Stop()
+ s.done <- true
+}
+
+func (s *smartStatusOutput) startSigwinch() {
+ signal.Notify(s.sigwinch, syscall.SIGWINCH)
+ go func() {
+ for _ = range s.sigwinch {
+ s.lock.Lock()
+ s.updateTermSize()
+ if s.tableMode {
+ s.actionTable()
+ }
+ s.lock.Unlock()
+ if s.sigwinchHandled != nil {
+ s.sigwinchHandled <- true
+ }
+ }
+ }()
+}
+
+func (s *smartStatusOutput) stopSigwinch() {
+ signal.Stop(s.sigwinch)
+ close(s.sigwinch)
+}
+
+func (s *smartStatusOutput) updateTermSize() {
+ if w, h, ok := termSize(s.writer); ok {
+ firstUpdate := s.termHeight == 0 && s.termWidth == 0
+ oldScrollingHeight := s.termHeight - s.tableHeight
+
+ s.termWidth, s.termHeight = w, h
+
+ if s.tableMode {
+ tableHeight := s.requestedTableHeight
+ if tableHeight > s.termHeight-1 {
+ tableHeight = s.termHeight - 1
+ }
+ s.tableHeight = tableHeight
+
+ scrollingHeight := s.termHeight - s.tableHeight
+
+ if !firstUpdate {
+ // If the scrolling region has changed, attempt to pan the existing text so that it is
+ // not overwritten by the table.
+ if scrollingHeight < oldScrollingHeight {
+ pan := oldScrollingHeight - scrollingHeight
+ if pan > s.tableHeight {
+ pan = s.tableHeight
+ }
+ fmt.Fprint(s.writer, ansi.panDown(pan))
+ }
+ }
+ }
+ }
+}
+
+func (s *smartStatusOutput) actionTable() {
+ 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))
+
+ // Write as many status lines as fit in the table
+ var tableLine int
+ var runningAction actionTableEntry
+ for tableLine, runningAction = range s.runningActions {
+ if tableLine >= s.tableHeight {
+ break
+ }
+
+ seconds := int(time.Since(runningAction.startTime).Round(time.Second).Seconds())
+
+ desc := runningAction.action.Description
+ if desc == "" {
+ desc = runningAction.action.Command
+ }
+
+ str := fmt.Sprintf(" %2d:%02d %s", seconds/60, seconds%60, desc)
+ str = s.elide(str)
+ fmt.Fprint(s.writer, str, 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))
+}
+
+var ansi = ansiImpl{}
+
+type ansiImpl struct{}
+
+func (ansiImpl) clearToEndOfLine() string {
+ return "\x1b[K"
+}
+
+func (ansiImpl) setCursor(row, column int) string {
+ // Direct cursor address
+ return fmt.Sprintf("\x1b[%d;%dH", row, column)
+}
+
+func (ansiImpl) setScrollingMargins(top, bottom int) string {
+ // Set Top and Bottom Margins DECSTBM
+ return fmt.Sprintf("\x1b[%d;%dr", top, bottom)
+}
+
+func (ansiImpl) resetScrollingMargins() string {
+ // Set Top and Bottom Margins DECSTBM
+ return fmt.Sprintf("\x1b[r")
+}
+
+func (ansiImpl) bold() string {
+ return "\x1b[1m"
+}
+
+func (ansiImpl) regular() string {
+ return "\x1b[0m"
+}
+
+func (ansiImpl) showCursor() string {
+ return "\x1b[?25h"
+}
+
+func (ansiImpl) hideCursor() string {
+ return "\x1b[?25l"
+}
+
+func (ansiImpl) panDown(lines int) string {
+ return fmt.Sprintf("\x1b[%dS", lines)
+}
+
+func (ansiImpl) panUp(lines int) string {
+ return fmt.Sprintf("\x1b[%dT", lines)
+}
diff --git a/ui/terminal/status.go b/ui/terminal/status.go
index 2445c5b..69a2a09 100644
--- a/ui/terminal/status.go
+++ b/ui/terminal/status.go
@@ -15,131 +15,23 @@
package terminal
import (
- "fmt"
- "strings"
- "time"
+ "io"
"android/soong/ui/status"
)
-type statusOutput struct {
- writer Writer
- format string
-
- start time.Time
- quiet bool
-}
-
// NewStatusOutput returns a StatusOutput that represents the
// current build status similarly to Ninja's built-in terminal
// output.
//
// statusFormat takes nearly all the same options as NINJA_STATUS.
// %c is currently unsupported.
-func NewStatusOutput(w Writer, statusFormat string, quietBuild bool) status.StatusOutput {
- return &statusOutput{
- writer: w,
- format: statusFormat,
+func NewStatusOutput(w io.Writer, statusFormat string, quietBuild bool) status.StatusOutput {
+ formatter := newFormatter(statusFormat, quietBuild)
- start: time.Now(),
- quiet: quietBuild,
- }
-}
-
-func (s *statusOutput) Message(level status.MsgLevel, message string) {
- if level >= status.ErrorLvl {
- s.writer.Print(fmt.Sprintf("FAILED: %s", message))
- } else if level > status.StatusLvl {
- s.writer.Print(fmt.Sprintf("%s%s", level.Prefix(), message))
- } else if level == status.StatusLvl {
- s.writer.StatusLine(message)
- }
-}
-
-func (s *statusOutput) StartAction(action *status.Action, counts status.Counts) {
- if !s.writer.isSmartTerminal() {
- return
- }
-
- str := action.Description
- if str == "" {
- str = action.Command
- }
-
- s.writer.StatusLine(s.progress(counts) + str)
-}
-
-func (s *statusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
- str := result.Description
- if str == "" {
- str = result.Command
- }
-
- progress := s.progress(counts) + str
-
- if result.Error != nil {
- targets := strings.Join(result.Outputs, " ")
- if s.quiet || result.Command == "" {
- s.writer.StatusAndMessage(progress, fmt.Sprintf("FAILED: %s\n%s", targets, result.Output))
- } else {
- s.writer.StatusAndMessage(progress, fmt.Sprintf("FAILED: %s\n%s\n%s", targets, result.Command, result.Output))
- }
- } else if result.Output != "" {
- s.writer.StatusAndMessage(progress, result.Output)
+ if isSmartTerminal(w) {
+ return NewSmartStatusOutput(w, formatter)
} else {
- s.writer.StatusLine(progress)
+ return NewDumbStatusOutput(w, formatter)
}
}
-
-func (s *statusOutput) Flush() {}
-
-func (s *statusOutput) progress(counts status.Counts) string {
- if s.format == "" {
- return fmt.Sprintf("[%3d%% %d/%d] ", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
- }
-
- buf := &strings.Builder{}
- for i := 0; i < len(s.format); i++ {
- c := s.format[i]
- if c != '%' {
- buf.WriteByte(c)
- continue
- }
-
- i = i + 1
- if i == len(s.format) {
- buf.WriteByte(c)
- break
- }
-
- c = s.format[i]
- switch c {
- case '%':
- buf.WriteByte(c)
- case 's':
- fmt.Fprintf(buf, "%d", counts.StartedActions)
- case 't':
- fmt.Fprintf(buf, "%d", counts.TotalActions)
- case 'r':
- fmt.Fprintf(buf, "%d", counts.RunningActions)
- case 'u':
- fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions)
- case 'f':
- fmt.Fprintf(buf, "%d", counts.FinishedActions)
- case 'o':
- fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds())
- case 'c':
- // TODO: implement?
- buf.WriteRune('?')
- case 'p':
- fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions)
- case 'e':
- fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds())
- default:
- buf.WriteString("unknown placeholder '")
- buf.WriteByte(c)
- buf.WriteString("'")
- }
- }
- return buf.String()
-}
diff --git a/ui/terminal/status_test.go b/ui/terminal/status_test.go
new file mode 100644
index 0000000..81aa238
--- /dev/null
+++ b/ui/terminal/status_test.go
@@ -0,0 +1,284 @@
+// Copyright 2018 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 terminal
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "syscall"
+ "testing"
+
+ "android/soong/ui/status"
+)
+
+func TestStatusOutput(t *testing.T) {
+ tests := []struct {
+ name string
+ calls func(stat status.StatusOutput)
+ smart string
+ dumb string
+ }{
+ {
+ name: "two actions",
+ calls: twoActions,
+ smart: "\r\x1b[1m[ 0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action2\x1b[0m\x1b[K\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n",
+ dumb: "[ 50% 1/2] action1\n[100% 2/2] action2\n",
+ },
+ {
+ name: "two parallel actions",
+ calls: twoParallelActions,
+ smart: "\r\x1b[1m[ 0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n",
+ dumb: "[ 50% 1/2] action1\n[100% 2/2] action2\n",
+ },
+ {
+ name: "action with output",
+ calls: actionsWithOutput,
+ smart: "\r\x1b[1m[ 0% 0/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action2\x1b[0m\x1b[K\r\x1b[1m[ 66% 2/3] action2\x1b[0m\x1b[K\noutput1\noutput2\n\r\x1b[1m[ 66% 2/3] action3\x1b[0m\x1b[K\r\x1b[1m[100% 3/3] action3\x1b[0m\x1b[K\n",
+ dumb: "[ 33% 1/3] action1\n[ 66% 2/3] action2\noutput1\noutput2\n[100% 3/3] action3\n",
+ },
+ {
+ name: "action with output without newline",
+ calls: actionsWithOutputWithoutNewline,
+ smart: "\r\x1b[1m[ 0% 0/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action2\x1b[0m\x1b[K\r\x1b[1m[ 66% 2/3] action2\x1b[0m\x1b[K\noutput1\noutput2\n\r\x1b[1m[ 66% 2/3] action3\x1b[0m\x1b[K\r\x1b[1m[100% 3/3] action3\x1b[0m\x1b[K\n",
+ dumb: "[ 33% 1/3] action1\n[ 66% 2/3] action2\noutput1\noutput2\n[100% 3/3] action3\n",
+ },
+ {
+ name: "action with error",
+ calls: actionsWithError,
+ smart: "\r\x1b[1m[ 0% 0/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action2\x1b[0m\x1b[K\r\x1b[1m[ 66% 2/3] action2\x1b[0m\x1b[K\nFAILED: f1 f2\ntouch f1 f2\nerror1\nerror2\n\r\x1b[1m[ 66% 2/3] action3\x1b[0m\x1b[K\r\x1b[1m[100% 3/3] action3\x1b[0m\x1b[K\n",
+ dumb: "[ 33% 1/3] action1\n[ 66% 2/3] action2\nFAILED: f1 f2\ntouch f1 f2\nerror1\nerror2\n[100% 3/3] action3\n",
+ },
+ {
+ name: "action with empty description",
+ calls: actionWithEmptyDescription,
+ smart: "\r\x1b[1m[ 0% 0/1] command1\x1b[0m\x1b[K\r\x1b[1m[100% 1/1] command1\x1b[0m\x1b[K\n",
+ dumb: "[100% 1/1] command1\n",
+ },
+ {
+ name: "messages",
+ calls: actionsWithMessages,
+ smart: "\r\x1b[1m[ 0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\r\x1b[1mstatus\x1b[0m\x1b[K\r\x1b[Kprint\nFAILED: error\n\r\x1b[1m[ 50% 1/2] action2\x1b[0m\x1b[K\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n",
+ dumb: "[ 50% 1/2] action1\nstatus\nprint\nFAILED: error\n[100% 2/2] action2\n",
+ },
+ {
+ name: "action with long description",
+ calls: actionWithLongDescription,
+ smart: "\r\x1b[1m[ 0% 0/2] action with very long descrip\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action with very long descrip\x1b[0m\x1b[K\n",
+ dumb: "[ 50% 1/2] action with very long description to test eliding\n",
+ },
+ {
+ name: "action with output with ansi codes",
+ calls: actionWithOuptutWithAnsiCodes,
+ smart: "\r\x1b[1m[ 0% 0/1] action1\x1b[0m\x1b[K\r\x1b[1m[100% 1/1] action1\x1b[0m\x1b[K\n\x1b[31mcolor\x1b[0m\n",
+ dumb: "[100% 1/1] action1\ncolor\n",
+ },
+ }
+
+ os.Setenv(tableHeightEnVar, "")
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+
+ t.Run("smart", func(t *testing.T) {
+ smart := &fakeSmartTerminal{termWidth: 40}
+ stat := NewStatusOutput(smart, "", false)
+ tt.calls(stat)
+ stat.Flush()
+
+ if g, w := smart.String(), tt.smart; g != w {
+ t.Errorf("want:\n%q\ngot:\n%q", w, g)
+ }
+ })
+
+ t.Run("dumb", func(t *testing.T) {
+ dumb := &bytes.Buffer{}
+ stat := NewStatusOutput(dumb, "", false)
+ tt.calls(stat)
+ stat.Flush()
+
+ if g, w := dumb.String(), tt.dumb; g != w {
+ t.Errorf("want:\n%q\ngot:\n%q", w, g)
+ }
+ })
+ })
+ }
+}
+
+type runner struct {
+ counts status.Counts
+ stat status.StatusOutput
+}
+
+func newRunner(stat status.StatusOutput, totalActions int) *runner {
+ return &runner{
+ counts: status.Counts{TotalActions: totalActions},
+ stat: stat,
+ }
+}
+
+func (r *runner) startAction(action *status.Action) {
+ r.counts.StartedActions++
+ r.counts.RunningActions++
+ r.stat.StartAction(action, r.counts)
+}
+
+func (r *runner) finishAction(result status.ActionResult) {
+ r.counts.FinishedActions++
+ r.counts.RunningActions--
+ r.stat.FinishAction(result, r.counts)
+}
+
+func (r *runner) finishAndStartAction(result status.ActionResult, action *status.Action) {
+ r.counts.FinishedActions++
+ r.stat.FinishAction(result, r.counts)
+
+ r.counts.StartedActions++
+ r.stat.StartAction(action, r.counts)
+}
+
+var (
+ action1 = &status.Action{Description: "action1"}
+ result1 = status.ActionResult{Action: action1}
+ action2 = &status.Action{Description: "action2"}
+ result2 = status.ActionResult{Action: action2}
+ action3 = &status.Action{Description: "action3"}
+ result3 = status.ActionResult{Action: action3}
+)
+
+func twoActions(stat status.StatusOutput) {
+ runner := newRunner(stat, 2)
+ runner.startAction(action1)
+ runner.finishAction(result1)
+ runner.startAction(action2)
+ runner.finishAction(result2)
+}
+
+func twoParallelActions(stat status.StatusOutput) {
+ runner := newRunner(stat, 2)
+ runner.startAction(action1)
+ runner.startAction(action2)
+ runner.finishAction(result1)
+ runner.finishAction(result2)
+}
+
+func actionsWithOutput(stat status.StatusOutput) {
+ result2WithOutput := status.ActionResult{Action: action2, Output: "output1\noutput2\n"}
+
+ runner := newRunner(stat, 3)
+ runner.startAction(action1)
+ runner.finishAction(result1)
+ runner.startAction(action2)
+ runner.finishAction(result2WithOutput)
+ runner.startAction(action3)
+ runner.finishAction(result3)
+}
+
+func actionsWithOutputWithoutNewline(stat status.StatusOutput) {
+ result2WithOutputWithoutNewline := status.ActionResult{Action: action2, Output: "output1\noutput2"}
+
+ runner := newRunner(stat, 3)
+ runner.startAction(action1)
+ runner.finishAction(result1)
+ runner.startAction(action2)
+ runner.finishAction(result2WithOutputWithoutNewline)
+ runner.startAction(action3)
+ runner.finishAction(result3)
+}
+
+func actionsWithError(stat status.StatusOutput) {
+ action2WithError := &status.Action{Description: "action2", Outputs: []string{"f1", "f2"}, Command: "touch f1 f2"}
+ result2WithError := status.ActionResult{Action: action2WithError, Output: "error1\nerror2\n", Error: fmt.Errorf("error1")}
+
+ runner := newRunner(stat, 3)
+ runner.startAction(action1)
+ runner.finishAction(result1)
+ runner.startAction(action2WithError)
+ runner.finishAction(result2WithError)
+ runner.startAction(action3)
+ runner.finishAction(result3)
+}
+
+func actionWithEmptyDescription(stat status.StatusOutput) {
+ action1 := &status.Action{Command: "command1"}
+ result1 := status.ActionResult{Action: action1}
+
+ runner := newRunner(stat, 1)
+ runner.startAction(action1)
+ runner.finishAction(result1)
+}
+
+func actionsWithMessages(stat status.StatusOutput) {
+ runner := newRunner(stat, 2)
+
+ runner.startAction(action1)
+ runner.finishAction(result1)
+
+ stat.Message(status.VerboseLvl, "verbose")
+ stat.Message(status.StatusLvl, "status")
+ stat.Message(status.PrintLvl, "print")
+ stat.Message(status.ErrorLvl, "error")
+
+ runner.startAction(action2)
+ runner.finishAction(result2)
+}
+
+func actionWithLongDescription(stat status.StatusOutput) {
+ action1 := &status.Action{Description: "action with very long description to test eliding"}
+ result1 := status.ActionResult{Action: action1}
+
+ runner := newRunner(stat, 2)
+
+ runner.startAction(action1)
+
+ runner.finishAction(result1)
+}
+
+func actionWithOuptutWithAnsiCodes(stat status.StatusOutput) {
+ result1WithOutputWithAnsiCodes := status.ActionResult{Action: action1, Output: "\x1b[31mcolor\x1b[0m"}
+
+ runner := newRunner(stat, 1)
+ runner.startAction(action1)
+ runner.finishAction(result1WithOutputWithAnsiCodes)
+}
+
+func TestSmartStatusOutputWidthChange(t *testing.T) {
+ os.Setenv(tableHeightEnVar, "")
+
+ smart := &fakeSmartTerminal{termWidth: 40}
+ stat := NewStatusOutput(smart, "", false)
+ smartStat := stat.(*smartStatusOutput)
+ smartStat.sigwinchHandled = make(chan bool)
+
+ runner := newRunner(stat, 2)
+
+ action := &status.Action{Description: "action with very long description to test eliding"}
+ result := status.ActionResult{Action: action}
+
+ runner.startAction(action)
+ smart.termWidth = 30
+ // Fake a SIGWINCH
+ smartStat.sigwinch <- syscall.SIGWINCH
+ <-smartStat.sigwinchHandled
+ runner.finishAction(result)
+
+ stat.Flush()
+
+ w := "\r\x1b[1m[ 0% 0/2] action with very long descrip\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action with very lo\x1b[0m\x1b[K\n"
+
+ if g := smart.String(); g != w {
+ t.Errorf("want:\n%q\ngot:\n%q", w, g)
+ }
+}
diff --git a/ui/terminal/stdio.go b/ui/terminal/stdio.go
new file mode 100644
index 0000000..dec2963
--- /dev/null
+++ b/ui/terminal/stdio.go
@@ -0,0 +1,55 @@
+// Copyright 2018 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 terminal provides a set of interfaces that can be used to interact
+// with the terminal (including falling back when the terminal is detected to
+// be a redirect or other dumb terminal)
+package terminal
+
+import (
+ "io"
+ "os"
+)
+
+// StdioInterface represents a set of stdin/stdout/stderr Reader/Writers
+type StdioInterface interface {
+ Stdin() io.Reader
+ Stdout() io.Writer
+ Stderr() io.Writer
+}
+
+// StdioImpl uses the OS stdin/stdout/stderr to implement StdioInterface
+type StdioImpl struct{}
+
+func (StdioImpl) Stdin() io.Reader { return os.Stdin }
+func (StdioImpl) Stdout() io.Writer { return os.Stdout }
+func (StdioImpl) Stderr() io.Writer { return os.Stderr }
+
+var _ StdioInterface = StdioImpl{}
+
+type customStdio struct {
+ stdin io.Reader
+ stdout io.Writer
+ stderr io.Writer
+}
+
+func NewCustomStdio(stdin io.Reader, stdout, stderr io.Writer) StdioInterface {
+ return customStdio{stdin, stdout, stderr}
+}
+
+func (c customStdio) Stdin() io.Reader { return c.stdin }
+func (c customStdio) Stdout() io.Writer { return c.stdout }
+func (c customStdio) Stderr() io.Writer { return c.stderr }
+
+var _ StdioInterface = customStdio{}
diff --git a/ui/terminal/util.go b/ui/terminal/util.go
index a85a517..c9377f1 100644
--- a/ui/terminal/util.go
+++ b/ui/terminal/util.go
@@ -22,18 +22,20 @@
"unsafe"
)
-func isTerminal(w io.Writer) bool {
+func isSmartTerminal(w io.Writer) bool {
if f, ok := w.(*os.File); ok {
var termios syscall.Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(),
ioctlGetTermios, uintptr(unsafe.Pointer(&termios)),
0, 0, 0)
return err == 0
+ } else if _, ok := w.(*fakeSmartTerminal); ok {
+ return true
}
return false
}
-func termWidth(w io.Writer) (int, bool) {
+func termSize(w io.Writer) (width int, height int, ok bool) {
if f, ok := w.(*os.File); ok {
var winsize struct {
ws_row, ws_column uint16
@@ -42,9 +44,11 @@
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(),
syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&winsize)),
0, 0, 0)
- return int(winsize.ws_column), err == 0
+ return int(winsize.ws_column), int(winsize.ws_row), err == 0
+ } else if f, ok := w.(*fakeSmartTerminal); ok {
+ return f.termWidth, f.termHeight, true
}
- return 0, false
+ return 0, 0, false
}
// stripAnsiEscapes strips ANSI control codes from a byte array in place.
@@ -99,3 +103,8 @@
return input
}
+
+type fakeSmartTerminal struct {
+ bytes.Buffer
+ termWidth, termHeight int
+}
diff --git a/ui/terminal/writer.go b/ui/terminal/writer.go
deleted file mode 100644
index ebe4b2a..0000000
--- a/ui/terminal/writer.go
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright 2018 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 terminal provides a set of interfaces that can be used to interact
-// with the terminal (including falling back when the terminal is detected to
-// be a redirect or other dumb terminal)
-package terminal
-
-import (
- "fmt"
- "io"
- "os"
- "strings"
- "sync"
-)
-
-// Writer provides an interface to write temporary and permanent messages to
-// the terminal.
-//
-// The terminal is considered to be a dumb terminal if TERM==dumb, or if a
-// terminal isn't detected on stdout/stderr (generally because it's a pipe or
-// file). Dumb terminals will strip out all ANSI escape sequences, including
-// colors.
-type Writer interface {
- // Print prints the string to the terminal, overwriting any current
- // status being displayed.
- //
- // On a dumb terminal, the status messages will be kept.
- Print(str string)
-
- // Status prints the first line of the string to the terminal,
- // overwriting any previous status line. Strings longer than the width
- // of the terminal will be cut off.
- //
- // On a dumb terminal, previous status messages will remain, and the
- // entire first line of the string will be printed.
- StatusLine(str string)
-
- // StatusAndMessage prints the first line of status to the terminal,
- // similarly to StatusLine(), then prints the full msg below that. The
- // status line is retained.
- //
- // There is guaranteed to be no other output in between the status and
- // message.
- StatusAndMessage(status, msg string)
-
- // Finish ensures that the output ends with a newline (preserving any
- // current status line that is current displayed).
- //
- // This does nothing on dumb terminals.
- Finish()
-
- // Write implements the io.Writer interface. This is primarily so that
- // the logger can use this interface to print to stderr without
- // breaking the other semantics of this interface.
- //
- // Try to use any of the other functions if possible.
- Write(p []byte) (n int, err error)
-
- isSmartTerminal() bool
-}
-
-// NewWriter creates a new Writer based on the stdio and the TERM
-// environment variable.
-func NewWriter(stdio StdioInterface) Writer {
- w := &writerImpl{
- stdio: stdio,
-
- haveBlankLine: true,
- }
-
- if term, ok := os.LookupEnv("TERM"); ok && term != "dumb" {
- w.smartTerminal = isTerminal(stdio.Stdout())
- }
- w.stripEscapes = !w.smartTerminal
-
- return w
-}
-
-type writerImpl struct {
- stdio StdioInterface
-
- haveBlankLine bool
-
- // Protecting the above, we assume that smartTerminal and stripEscapes
- // does not change after initial setup.
- lock sync.Mutex
-
- smartTerminal bool
- stripEscapes bool
-}
-
-func (w *writerImpl) isSmartTerminal() bool {
- return w.smartTerminal
-}
-
-func (w *writerImpl) requestLine() {
- if !w.haveBlankLine {
- fmt.Fprintln(w.stdio.Stdout())
- w.haveBlankLine = true
- }
-}
-
-func (w *writerImpl) Print(str string) {
- if w.stripEscapes {
- str = string(stripAnsiEscapes([]byte(str)))
- }
-
- w.lock.Lock()
- defer w.lock.Unlock()
- w.print(str)
-}
-
-func (w *writerImpl) print(str string) {
- if !w.haveBlankLine {
- fmt.Fprint(w.stdio.Stdout(), "\r", "\x1b[K")
- w.haveBlankLine = true
- }
- fmt.Fprint(w.stdio.Stdout(), str)
- if len(str) == 0 || str[len(str)-1] != '\n' {
- fmt.Fprint(w.stdio.Stdout(), "\n")
- }
-}
-
-func (w *writerImpl) StatusLine(str string) {
- w.lock.Lock()
- defer w.lock.Unlock()
-
- w.statusLine(str)
-}
-
-func (w *writerImpl) statusLine(str string) {
- if !w.smartTerminal {
- fmt.Fprintln(w.stdio.Stdout(), str)
- return
- }
-
- idx := strings.IndexRune(str, '\n')
- if idx != -1 {
- str = str[0:idx]
- }
-
- // Limit line width to the terminal width, otherwise we'll wrap onto
- // another line and we won't delete the previous line.
- //
- // Run this on every line in case the window has been resized while
- // we're printing. This could be optimized to only re-run when we get
- // SIGWINCH if it ever becomes too time consuming.
- if max, ok := termWidth(w.stdio.Stdout()); ok {
- if len(str) > max {
- // TODO: Just do a max. Ninja elides the middle, but that's
- // more complicated and these lines aren't that important.
- str = str[:max]
- }
- }
-
- // Move to the beginning on the line, print the output, then clear
- // the rest of the line.
- fmt.Fprint(w.stdio.Stdout(), "\r", str, "\x1b[K")
- w.haveBlankLine = false
-}
-
-func (w *writerImpl) StatusAndMessage(status, msg string) {
- if w.stripEscapes {
- msg = string(stripAnsiEscapes([]byte(msg)))
- }
-
- w.lock.Lock()
- defer w.lock.Unlock()
-
- w.statusLine(status)
- w.requestLine()
- w.print(msg)
-}
-
-func (w *writerImpl) Finish() {
- w.lock.Lock()
- defer w.lock.Unlock()
-
- w.requestLine()
-}
-
-func (w *writerImpl) Write(p []byte) (n int, err error) {
- w.Print(string(p))
- return len(p), nil
-}
-
-// StdioInterface represents a set of stdin/stdout/stderr Reader/Writers
-type StdioInterface interface {
- Stdin() io.Reader
- Stdout() io.Writer
- Stderr() io.Writer
-}
-
-// StdioImpl uses the OS stdin/stdout/stderr to implement StdioInterface
-type StdioImpl struct{}
-
-func (StdioImpl) Stdin() io.Reader { return os.Stdin }
-func (StdioImpl) Stdout() io.Writer { return os.Stdout }
-func (StdioImpl) Stderr() io.Writer { return os.Stderr }
-
-var _ StdioInterface = StdioImpl{}
-
-type customStdio struct {
- stdin io.Reader
- stdout io.Writer
- stderr io.Writer
-}
-
-func NewCustomStdio(stdin io.Reader, stdout, stderr io.Writer) StdioInterface {
- return customStdio{stdin, stdout, stderr}
-}
-
-func (c customStdio) Stdin() io.Reader { return c.stdin }
-func (c customStdio) Stdout() io.Writer { return c.stdout }
-func (c customStdio) Stderr() io.Writer { return c.stderr }
-
-var _ StdioInterface = customStdio{}
diff --git a/ui/tracer/status.go b/ui/tracer/status.go
index af50e2d..c831255 100644
--- a/ui/tracer/status.go
+++ b/ui/tracer/status.go
@@ -85,3 +85,8 @@
func (s *statusOutput) Flush() {}
func (s *statusOutput) Message(level status.MsgLevel, message string) {}
+
+func (s *statusOutput) Write(p []byte) (int, error) {
+ // Discard writes
+ return len(p), nil
+}
diff --git a/zip/cmd/main.go b/zip/cmd/main.go
index 6f40a3e..fba2e4b 100644
--- a/zip/cmd/main.go
+++ b/zip/cmd/main.go
@@ -136,6 +136,7 @@
writeIfChanged := flags.Bool("write_if_changed", false, "only update resultant .zip if it has changed")
ignoreMissingFiles := flags.Bool("ignore_missing_files", false, "continue if a requested file does not exist")
symlinks := flags.Bool("symlinks", true, "store symbolic links in zip instead of following them")
+ srcJar := flags.Bool("srcjar", false, "move .java files to locations that match their package statement")
parallelJobs := flags.Int("parallel", runtime.NumCPU(), "number of parallel threads to use")
cpuProfile := flags.String("cpuprofile", "", "write cpu profile to file")
@@ -191,6 +192,7 @@
FileArgs: fileArgsBuilder.FileArgs(),
OutputFilePath: *out,
EmulateJar: *emulateJar,
+ SrcJar: *srcJar,
AddDirectoryEntriesToZip: *directories,
CompressionLevel: *compLevel,
ManifestSourcePath: *manifest,
diff --git a/zip/zip.go b/zip/zip.go
index 1f5fe43..707c4ef 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -210,6 +210,7 @@
FileArgs []FileArg
OutputFilePath string
EmulateJar bool
+ SrcJar bool
AddDirectoryEntriesToZip bool
CompressionLevel int
ManifestSourcePath string
@@ -364,7 +365,7 @@
}
}
- return z.write(w, pathMappings, args.ManifestSourcePath, args.EmulateJar, args.NumParallelJobs)
+ return z.write(w, pathMappings, args.ManifestSourcePath, args.EmulateJar, args.SrcJar, args.NumParallelJobs)
}
func Zip(args ZipArgs) error {
@@ -446,7 +447,9 @@
sort.SliceStable(mappings, less)
}
-func (z *ZipWriter) write(f io.Writer, pathMappings []pathMapping, manifest string, emulateJar bool, parallelJobs int) error {
+func (z *ZipWriter) write(f io.Writer, pathMappings []pathMapping, manifest string, emulateJar, srcJar bool,
+ parallelJobs int) error {
+
z.errors = make(chan error)
defer close(z.errors)
@@ -489,7 +492,7 @@
if emulateJar && ele.dest == jar.ManifestFile {
err = z.addManifest(ele.dest, ele.src, ele.zipMethod)
} else {
- err = z.addFile(ele.dest, ele.src, ele.zipMethod, emulateJar)
+ err = z.addFile(ele.dest, ele.src, ele.zipMethod, emulateJar, srcJar)
}
if err != nil {
z.errors <- err
@@ -588,7 +591,7 @@
}
// imports (possibly with compression) <src> into the zip at sub-path <dest>
-func (z *ZipWriter) addFile(dest, src string, method uint16, emulateJar bool) error {
+func (z *ZipWriter) addFile(dest, src string, method uint16, emulateJar, srcJar bool) error {
var fileSize int64
var executable bool
@@ -606,12 +609,9 @@
return nil
}
return err
- } else if s.IsDir() {
- if z.directories {
- return z.writeDirectory(dest, src, emulateJar)
- }
- return nil
- } else {
+ }
+
+ createParentDirs := func(dest, src string) error {
if err := z.writeDirectory(filepath.Dir(dest), src, emulateJar); err != nil {
return err
}
@@ -625,32 +625,64 @@
z.createdFiles[dest] = src
- if s.Mode()&os.ModeSymlink != 0 {
- return z.writeSymlink(dest, src)
- } else if !s.Mode().IsRegular() {
- return fmt.Errorf("%s is not a file, directory, or symlink", src)
+ return nil
+ }
+
+ if s.IsDir() {
+ if z.directories {
+ return z.writeDirectory(dest, src, emulateJar)
+ }
+ return nil
+ } else if s.Mode()&os.ModeSymlink != 0 {
+ err = createParentDirs(dest, src)
+ if err != nil {
+ return err
+ }
+
+ return z.writeSymlink(dest, src)
+ } else if s.Mode().IsRegular() {
+ r, err := z.fs.Open(src)
+ if err != nil {
+ return err
+ }
+
+ if srcJar && filepath.Ext(src) == ".java" {
+ // rewrite the destination using the package path if it can be determined
+ pkg, err := jar.JavaPackage(r, src)
+ if err != nil {
+ // ignore errors for now, leaving the file at in its original location in the zip
+ } else {
+ dest = filepath.Join(filepath.Join(strings.Split(pkg, ".")...), filepath.Base(src))
+ }
+
+ _, err = r.Seek(0, io.SeekStart)
+ if err != nil {
+ return err
+ }
}
fileSize = s.Size()
executable = s.Mode()&0100 != 0
- }
- r, err := z.fs.Open(src)
- if err != nil {
- return err
- }
+ header := &zip.FileHeader{
+ Name: dest,
+ Method: method,
+ UncompressedSize64: uint64(fileSize),
+ }
- header := &zip.FileHeader{
- Name: dest,
- Method: method,
- UncompressedSize64: uint64(fileSize),
- }
+ if executable {
+ header.SetMode(0700)
+ }
- if executable {
- header.SetMode(0700)
- }
+ err = createParentDirs(dest, src)
+ if err != nil {
+ return err
+ }
- return z.writeFileContents(header, r)
+ return z.writeFileContents(header, r)
+ } else {
+ return fmt.Errorf("%s is not a file, directory, or symlink", src)
+ }
}
func (z *ZipWriter) addManifest(dest string, src string, method uint16) error {
diff --git a/zip/zip_test.go b/zip/zip_test.go
index 93c5f3d..84317d1 100644
--- a/zip/zip_test.go
+++ b/zip/zip_test.go
@@ -40,14 +40,15 @@
)
var mockFs = pathtools.MockFs(map[string][]byte{
- "a/a/a": fileA,
- "a/a/b": fileB,
- "a/a/c -> ../../c": nil,
- "a/a/d -> b": nil,
- "c": fileC,
- "l": []byte("a/a/a\na/a/b\nc\n"),
- "l2": []byte("missing\n"),
- "manifest.txt": fileCustomManifest,
+ "a/a/a": fileA,
+ "a/a/b": fileB,
+ "a/a/c -> ../../c": nil,
+ "dangling -> missing": nil,
+ "a/a/d -> b": nil,
+ "c": fileC,
+ "l": []byte("a/a/a\na/a/b\nc\n"),
+ "l2": []byte("missing\n"),
+ "manifest.txt": fileCustomManifest,
})
func fh(name string, contents []byte, method uint16) zip.FileHeader {
@@ -210,6 +211,17 @@
},
},
{
+ name: "dangling symlinks",
+ args: fileArgsBuilder().
+ File("dangling"),
+ compressionLevel: 9,
+ storeSymlinks: true,
+
+ files: []zip.FileHeader{
+ fhLink("dangling", "missing"),
+ },
+ },
+ {
name: "list",
args: fileArgsBuilder().
List("l"),
@@ -554,3 +566,70 @@
})
}
}
+
+func TestSrcJar(t *testing.T) {
+ mockFs := pathtools.MockFs(map[string][]byte{
+ "wrong_package.java": []byte("package foo;"),
+ "foo/correct_package.java": []byte("package foo;"),
+ "src/no_package.java": nil,
+ "src2/parse_error.java": []byte("error"),
+ })
+
+ want := []string{
+ "foo/",
+ "foo/wrong_package.java",
+ "foo/correct_package.java",
+ "no_package.java",
+ "src2/",
+ "src2/parse_error.java",
+ }
+
+ args := ZipArgs{}
+ args.FileArgs = NewFileArgsBuilder().File("**/*.java").FileArgs()
+
+ args.SrcJar = true
+ args.AddDirectoryEntriesToZip = true
+ args.Filesystem = mockFs
+ args.Stderr = &bytes.Buffer{}
+
+ buf := &bytes.Buffer{}
+ err := ZipTo(args, buf)
+ if err != nil {
+ t.Fatalf("got error %v", err)
+ }
+
+ br := bytes.NewReader(buf.Bytes())
+ zr, err := zip.NewReader(br, int64(br.Len()))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var got []string
+ for _, f := range zr.File {
+ r, err := f.Open()
+ if err != nil {
+ t.Fatalf("error when opening %s: %s", f.Name, err)
+ }
+
+ crc := crc32.NewIEEE()
+ len, err := io.Copy(crc, r)
+ r.Close()
+ if err != nil {
+ t.Fatalf("error when reading %s: %s", f.Name, err)
+ }
+
+ if uint64(len) != f.UncompressedSize64 {
+ t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len)
+ }
+
+ if crc.Sum32() != f.CRC32 {
+ t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc)
+ }
+
+ got = append(got, f.Name)
+ }
+
+ if !reflect.DeepEqual(want, got) {
+ t.Errorf("want files %q, got %q", want, got)
+ }
+}