Merge "Populate java_api_libray's IDEInfo" into main
diff --git a/aconfig/init.go b/aconfig/init.go
index 256b213..de155ab 100644
--- a/aconfig/init.go
+++ b/aconfig/init.go
@@ -47,7 +47,7 @@
// For create-device-config-sysprops: Generate aconfig flag value map text file
aconfigTextRule = pctx.AndroidStaticRule("aconfig_text",
blueprint.RuleParams{
- Command: `${aconfig} dump-cache --dedup --format='{fully_qualified_name}={state:bool}'` +
+ Command: `${aconfig} dump-cache --dedup --format='{fully_qualified_name}:{permission}={state:bool}'` +
` --cache ${in}` +
` --out ${out}.tmp` +
` && ( if cmp -s ${out}.tmp ${out} ; then rm ${out}.tmp ; else mv ${out}.tmp ${out} ; fi )`,
diff --git a/android/arch.go b/android/arch.go
index e0c6908..6d896e5 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -19,6 +19,7 @@
"fmt"
"reflect"
"runtime"
+ "slices"
"strings"
"github.com/google/blueprint"
@@ -587,19 +588,21 @@
}
osTargets := mctx.Config().Targets[os]
+
image := base.commonProperties.ImageVariation
// Filter NativeBridge targets unless they are explicitly supported.
// Skip creating native bridge variants for non-core modules.
if os == Android && !(base.IsNativeBridgeSupported() && image == CoreVariation) {
+ osTargets = slices.DeleteFunc(slices.Clone(osTargets), func(t Target) bool {
+ return bool(t.NativeBridge)
+ })
+ }
- var targets []Target
- for _, t := range osTargets {
- if !t.NativeBridge {
- targets = append(targets, t)
- }
- }
-
- osTargets = targets
+ // Filter HostCross targets if disabled.
+ if base.HostSupported() && !base.HostCrossSupported() {
+ osTargets = slices.DeleteFunc(slices.Clone(osTargets), func(t Target) bool {
+ return t.HostCross
+ })
}
// only the primary arch in the ramdisk / vendor_ramdisk / recovery partition
diff --git a/android/arch_test.go b/android/arch_test.go
index f0a58a9..6134a06 100644
--- a/android/arch_test.go
+++ b/android/arch_test.go
@@ -332,6 +332,12 @@
}
module {
+ name: "nohostcross",
+ host_supported: true,
+ host_cross_supported: false,
+ }
+
+ module {
name: "baz",
device_supported: false,
}
@@ -355,13 +361,14 @@
`
testCases := []struct {
- name string
- preparer FixturePreparer
- fooVariants []string
- barVariants []string
- bazVariants []string
- quxVariants []string
- firstVariants []string
+ name string
+ preparer FixturePreparer
+ fooVariants []string
+ barVariants []string
+ noHostCrossVariants []string
+ bazVariants []string
+ quxVariants []string
+ firstVariants []string
multiTargetVariants []string
multiTargetVariantsMap map[string][]string
@@ -373,6 +380,7 @@
preparer: nil,
fooVariants: []string{"android_arm64_armv8-a", "android_arm_armv7-a-neon"},
barVariants: append(buildOSVariants, "android_arm64_armv8-a", "android_arm_armv7-a-neon"),
+ noHostCrossVariants: append(buildOSVariants, "android_arm64_armv8-a", "android_arm_armv7-a-neon"),
bazVariants: nil,
quxVariants: append(buildOS32Variants, "android_arm_armv7-a-neon"),
firstVariants: append(buildOS64Variants, "android_arm64_armv8-a"),
@@ -390,6 +398,7 @@
}),
fooVariants: nil,
barVariants: buildOSVariants,
+ noHostCrossVariants: buildOSVariants,
bazVariants: nil,
quxVariants: buildOS32Variants,
firstVariants: buildOS64Variants,
@@ -406,6 +415,7 @@
}),
fooVariants: []string{"android_arm64_armv8-a", "android_arm_armv7-a-neon"},
barVariants: []string{"linux_musl_x86_64", "linux_musl_arm64", "linux_musl_x86", "android_arm64_armv8-a", "android_arm_armv7-a-neon"},
+ noHostCrossVariants: []string{"linux_musl_x86_64", "linux_musl_x86", "android_arm64_armv8-a", "android_arm_armv7-a-neon"},
bazVariants: nil,
quxVariants: []string{"linux_musl_x86", "android_arm_armv7-a-neon"},
firstVariants: []string{"linux_musl_x86_64", "linux_musl_arm64", "android_arm64_armv8-a"},
@@ -461,6 +471,10 @@
t.Errorf("want bar variants:\n%q\ngot:\n%q\n", w, g)
}
+ if g, w := enabledVariants(ctx, "nohostcross"), tt.noHostCrossVariants; !reflect.DeepEqual(w, g) {
+ t.Errorf("want nohostcross variants:\n%q\ngot:\n%q\n", w, g)
+ }
+
if g, w := enabledVariants(ctx, "baz"), tt.bazVariants; !reflect.DeepEqual(w, g) {
t.Errorf("want baz variants:\n%q\ngot:\n%q\n", w, g)
}
diff --git a/android/module.go b/android/module.go
index dd56031..91f2056 100644
--- a/android/module.go
+++ b/android/module.go
@@ -603,6 +603,11 @@
Device_supported *bool
}
+type hostCrossProperties struct {
+ // If set to true, build a variant of the module for the host cross. Defaults to true.
+ Host_cross_supported *bool
+}
+
type Multilib string
const (
@@ -718,6 +723,10 @@
m.AddProperties(&base.hostAndDeviceProperties)
}
+ if hod&hostCrossSupported != 0 {
+ m.AddProperties(&base.hostCrossProperties)
+ }
+
initArchModule(m)
}
@@ -803,6 +812,7 @@
distProperties distProperties
variableProperties interface{}
hostAndDeviceProperties hostAndDeviceProperties
+ hostCrossProperties hostCrossProperties
// Arch specific versions of structs in GetProperties() prior to
// initialization in InitAndroidArchModule, lets call it `generalProperties`.
@@ -1299,7 +1309,11 @@
// hostEnabled is true if the host_supported property is true or the HostOrDeviceSupported
// value has the hostDefault bit set.
hostEnabled := proptools.BoolDefault(m.hostAndDeviceProperties.Host_supported, hod&hostDefault != 0)
- return hod&hostCrossSupported != 0 && hostEnabled
+
+ // Default true for the Host_cross_supported property
+ hostCrossEnabled := proptools.BoolDefault(m.hostCrossProperties.Host_cross_supported, true)
+
+ return hod&hostCrossSupported != 0 && hostEnabled && hostCrossEnabled
}
func (m *ModuleBase) Platform() bool {
@@ -2504,8 +2518,9 @@
} else if cta, isCta := ctx.(*singletonContextAdaptor); isCta {
providerData, _ := cta.moduleProvider(module, OutputFilesProvider)
outputFiles, _ = providerData.(OutputFilesInfo)
+ } else {
+ return nil, fmt.Errorf("unsupported context %q in method outputFilesForModuleFromProvider", reflect.TypeOf(ctx))
}
- // TODO: Add a check for skipped context
if outputFiles.isEmpty() {
return nil, OutputFilesProviderNotSet
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 85e29bd..8b03124 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -58,6 +58,7 @@
sboxInputs bool
sboxManifestPath WritablePath
missingDeps []string
+ args map[string]string
}
// NewRuleBuilder returns a newly created RuleBuilder.
@@ -78,6 +79,17 @@
return rb
}
+// Set the phony_output argument.
+// This causes the output files to be ignored.
+// If the output isn't created, it's not treated as an error.
+// The build rule is run every time whether or not the output is created.
+func (rb *RuleBuilder) SetPhonyOutput() {
+ if rb.args == nil {
+ rb.args = make(map[string]string)
+ }
+ rb.args["phony_output"] = "true"
+}
+
// RuleBuilderInstall is a tuple of install from and to locations.
type RuleBuilderInstall struct {
From Path
@@ -726,6 +738,12 @@
commandString = proptools.NinjaEscape(commandString)
}
+ args_vars := make([]string, len(r.args))
+ i := 0
+ for k, _ := range r.args {
+ args_vars[i] = k
+ i++
+ }
r.ctx.Build(r.pctx, BuildParams{
Rule: r.ctx.Rule(r.pctx, name, blueprint.RuleParams{
Command: commandString,
@@ -734,7 +752,7 @@
Rspfile: proptools.NinjaEscape(rspFile),
RspfileContent: rspFileContent,
Pool: pool,
- }),
+ }, args_vars...),
Inputs: rspFileInputs,
Implicits: inputs,
OrderOnly: r.OrderOnlys(),
@@ -744,6 +762,7 @@
Depfile: depFile,
Deps: depFormat,
Description: desc,
+ Args: r.args,
})
}
diff --git a/android/variable.go b/android/variable.go
index d144f7d..3b02bc7 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -132,9 +132,11 @@
Keep_symbols *bool
Keep_symbols_and_debug_frame *bool
}
- Static_libs []string
- Whole_static_libs []string
- Shared_libs []string
+ Static_libs []string
+ Exclude_static_libs []string
+ Whole_static_libs []string
+ Shared_libs []string
+ Jni_libs []string
Cmdline []string
diff --git a/androidmk/parser/parser_test.go b/androidmk/parser/parser_test.go
index db3313d..fb03c23 100644
--- a/androidmk/parser/parser_test.go
+++ b/androidmk/parser/parser_test.go
@@ -86,20 +86,19 @@
},
{
name: "Blank line in rule's command",
- in: `all:
+ in: `all:
echo first line
echo second line`,
out: []Node{
&Rule{
- Target: SimpleMakeString("all", NoPos),
- RecipePos: NoPos,
- Recipe: "echo first line\necho second line",
+ Target: SimpleMakeString("all", NoPos),
+ RecipePos: NoPos,
+ Recipe: "echo first line\necho second line",
Prerequisites: SimpleMakeString("", NoPos),
},
},
},
-
}
func TestParse(t *testing.T) {
diff --git a/apex/apex.go b/apex/apex.go
index f9b30d4..5a75d3e 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -703,7 +703,6 @@
rustLibVariations := append(
target.Variations(), []blueprint.Variation{
{Mutator: "rust_libraries", Variation: "dylib"},
- {Mutator: "link", Variation: ""},
}...,
)
diff --git a/cc/cc.go b/cc/cc.go
index 740be3a..64b2465 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -48,10 +48,10 @@
ctx.RegisterModuleType("cc_defaults", defaultsFactory)
ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
- ctx.BottomUp("sdk", sdkMutator).Parallel()
+ ctx.Transition("sdk", &sdkTransitionMutator{})
ctx.BottomUp("llndk", llndkMutator).Parallel()
- ctx.BottomUp("link", LinkageMutator).Parallel()
- ctx.BottomUp("version", versionMutator).Parallel()
+ ctx.Transition("link", &linkageTransitionMutator{})
+ ctx.Transition("version", &versionTransitionMutator{})
ctx.BottomUp("begin", BeginMutator).Parallel()
})
@@ -1867,8 +1867,10 @@
"libdl": true,
"libz": true,
// art apex
+ // TODO(b/234351700): Remove this when com.android.art.debug is gone.
"libandroidio": true,
"libdexfile": true,
+ "libdexfiled": true, // com.android.art.debug only
"libnativebridge": true,
"libnativehelper": true,
"libnativeloader": true,
diff --git a/cc/ccdeps.go b/cc/ccdeps.go
index d30abba..469fe31 100644
--- a/cc/ccdeps.go
+++ b/cc/ccdeps.go
@@ -85,9 +85,8 @@
moduleDeps := ccDeps{}
moduleInfos := map[string]ccIdeInfo{}
- // Track which projects have already had CMakeLists.txt generated to keep the first
- // variant for each project.
- seenProjects := map[string]bool{}
+ // Track if best variant (device arch match) has been found.
+ bestVariantFound := map[string]bool{}
pathToCC, _ := evalVariable(ctx, "${config.ClangBin}/")
moduleDeps.C_clang = fmt.Sprintf("%s%s", buildCMakePath(pathToCC), cClang)
@@ -96,7 +95,7 @@
ctx.VisitAllModules(func(module android.Module) {
if ccModule, ok := module.(*Module); ok {
if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok {
- generateCLionProjectData(ctx, compiledModule, ccModule, seenProjects, moduleInfos)
+ generateCLionProjectData(ctx, compiledModule, ccModule, bestVariantFound, moduleInfos)
}
}
})
@@ -180,26 +179,30 @@
}
func generateCLionProjectData(ctx android.SingletonContext, compiledModule CompiledInterface,
- ccModule *Module, seenProjects map[string]bool, moduleInfos map[string]ccIdeInfo) {
+ ccModule *Module, bestVariantFound map[string]bool, moduleInfos map[string]ccIdeInfo) {
+ moduleName := ccModule.ModuleBase.Name()
srcs := compiledModule.Srcs()
+
+ // Skip if best variant has already been found.
+ if bestVariantFound[moduleName] {
+ return
+ }
+
+ // Skip if sources are empty.
if len(srcs) == 0 {
return
}
- // Only keep the DeviceArch variant module.
- if ctx.DeviceConfig().DeviceArch() != ccModule.ModuleBase.Arch().ArchType.Name {
+ // Check if device arch matches, in which case this is the best variant and takes precedence.
+ if ccModule.Device() && ccModule.ModuleBase.Arch().ArchType.Name == ctx.DeviceConfig().DeviceArch() {
+ bestVariantFound[moduleName] = true
+ } else if _, ok := moduleInfos[moduleName]; ok {
+ // Skip because this isn't the best variant and a previous one has already been added.
+ // Heuristically, ones that appear first are likely to be more relevant.
return
}
- clionProjectLocation := getCMakeListsForModule(ccModule, ctx)
- if seenProjects[clionProjectLocation] {
- return
- }
-
- seenProjects[clionProjectLocation] = true
-
- name := ccModule.ModuleBase.Name()
- dpInfo := moduleInfos[name]
+ dpInfo := ccIdeInfo{}
dpInfo.Path = append(dpInfo.Path, path.Dir(ctx.BlueprintFile(ccModule)))
dpInfo.Srcs = append(dpInfo.Srcs, srcs.Strings()...)
@@ -216,9 +219,9 @@
dpInfo.Local_Cpp_flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.CppFlags)
dpInfo.System_include_flags = parseCompilerCCParameters(ctx, ccModule.flags.SystemIncludeFlags)
- dpInfo.Module_name = name
+ dpInfo.Module_name = moduleName
- moduleInfos[name] = dpInfo
+ moduleInfos[moduleName] = dpInfo
}
type Deal struct {
diff --git a/cc/config/global.go b/cc/config/global.go
index 62a4765..bf2502f 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -397,8 +397,8 @@
// prebuilts/clang default settings.
ClangDefaultBase = "prebuilts/clang/host"
- ClangDefaultVersion = "clang-r522817"
- ClangDefaultShortVersion = "18"
+ ClangDefaultVersion = "clang-r530567"
+ ClangDefaultShortVersion = "19"
// Directories with warnings from Android.bp files.
WarningAllowedProjects = []string{
diff --git a/cc/library.go b/cc/library.go
index 560b1ae..6dd5b56 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -19,6 +19,7 @@
"io"
"path/filepath"
"regexp"
+ "slices"
"strconv"
"strings"
"sync"
@@ -711,7 +712,7 @@
setStubsVersion(string)
stubsVersion() string
- stubsVersions(ctx android.BaseMutatorContext) []string
+ stubsVersions(ctx android.BaseModuleContext) []string
setAllStubsVersions([]string)
allStubsVersions() []string
@@ -1903,7 +1904,7 @@
return BoolDefault(library.Properties.Stubs.Implementation_installable, true)
}
-func (library *libraryDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
+func (library *libraryDecorator) stubsVersions(ctx android.BaseModuleContext) []string {
if !library.hasStubsVariants() {
return nil
}
@@ -2064,26 +2065,26 @@
// connects a shared library to a static library in order to reuse its .o files to avoid
// compiling source files twice.
-func reuseStaticLibrary(mctx android.BottomUpMutatorContext, static, shared *Module) {
- if staticCompiler, ok := static.compiler.(*libraryDecorator); ok {
- sharedCompiler := shared.compiler.(*libraryDecorator)
+func reuseStaticLibrary(ctx android.BottomUpMutatorContext, shared *Module) {
+ if sharedCompiler, ok := shared.compiler.(*libraryDecorator); ok {
// Check libraries in addition to cflags, since libraries may be exporting different
// include directories.
- if len(staticCompiler.StaticProperties.Static.Cflags.GetOrDefault(mctx, nil)) == 0 &&
- len(sharedCompiler.SharedProperties.Shared.Cflags.GetOrDefault(mctx, nil)) == 0 &&
- len(staticCompiler.StaticProperties.Static.Whole_static_libs) == 0 &&
+ if len(sharedCompiler.StaticProperties.Static.Cflags.GetOrDefault(ctx, nil)) == 0 &&
+ len(sharedCompiler.SharedProperties.Shared.Cflags.GetOrDefault(ctx, nil)) == 0 &&
+ len(sharedCompiler.StaticProperties.Static.Whole_static_libs) == 0 &&
len(sharedCompiler.SharedProperties.Shared.Whole_static_libs) == 0 &&
- len(staticCompiler.StaticProperties.Static.Static_libs) == 0 &&
+ len(sharedCompiler.StaticProperties.Static.Static_libs) == 0 &&
len(sharedCompiler.SharedProperties.Shared.Static_libs) == 0 &&
- len(staticCompiler.StaticProperties.Static.Shared_libs) == 0 &&
+ len(sharedCompiler.StaticProperties.Static.Shared_libs) == 0 &&
len(sharedCompiler.SharedProperties.Shared.Shared_libs) == 0 &&
// Compare System_shared_libs properties with nil because empty lists are
// semantically significant for them.
- staticCompiler.StaticProperties.Static.System_shared_libs == nil &&
+ sharedCompiler.StaticProperties.Static.System_shared_libs == nil &&
sharedCompiler.SharedProperties.Shared.System_shared_libs == nil {
- mctx.AddInterVariantDependency(reuseObjTag, shared, static)
+ // TODO: namespaces?
+ ctx.AddVariationDependencies([]blueprint.Variation{{"link", "static"}}, reuseObjTag, ctx.ModuleName())
sharedCompiler.baseCompiler.Properties.OriginalSrcs =
sharedCompiler.baseCompiler.Properties.Srcs
sharedCompiler.baseCompiler.Properties.Srcs = nil
@@ -2091,19 +2092,21 @@
}
// This dep is just to reference static variant from shared variant
- mctx.AddInterVariantDependency(staticVariantTag, shared, static)
+ ctx.AddVariationDependencies([]blueprint.Variation{{"link", "static"}}, staticVariantTag, ctx.ModuleName())
}
}
-// LinkageMutator adds "static" or "shared" variants for modules depending
+// linkageTransitionMutator adds "static" or "shared" variants for modules depending
// on whether the module can be built as a static library or a shared library.
-func LinkageMutator(mctx android.BottomUpMutatorContext) {
+type linkageTransitionMutator struct{}
+
+func (linkageTransitionMutator) Split(ctx android.BaseModuleContext) []string {
ccPrebuilt := false
- if m, ok := mctx.Module().(*Module); ok && m.linker != nil {
+ if m, ok := ctx.Module().(*Module); ok && m.linker != nil {
_, ccPrebuilt = m.linker.(prebuiltLibraryInterface)
}
if ccPrebuilt {
- library := mctx.Module().(*Module).linker.(prebuiltLibraryInterface)
+ library := ctx.Module().(*Module).linker.(prebuiltLibraryInterface)
// Differentiate between header only and building an actual static/shared library
buildStatic := library.buildStatic()
@@ -2112,75 +2115,118 @@
// Always create both the static and shared variants for prebuilt libraries, and then disable the one
// that is not being used. This allows them to share the name of a cc_library module, which requires that
// all the variants of the cc_library also exist on the prebuilt.
- modules := mctx.CreateLocalVariations("static", "shared")
- static := modules[0].(*Module)
- shared := modules[1].(*Module)
-
- static.linker.(prebuiltLibraryInterface).setStatic()
- shared.linker.(prebuiltLibraryInterface).setShared()
-
- if buildShared {
- mctx.AliasVariation("shared")
- } else if buildStatic {
- mctx.AliasVariation("static")
- }
-
- if !buildStatic {
- static.linker.(prebuiltLibraryInterface).disablePrebuilt()
- }
- if !buildShared {
- shared.linker.(prebuiltLibraryInterface).disablePrebuilt()
- }
+ return []string{"static", "shared"}
} else {
// Header only
}
-
- } else if library, ok := mctx.Module().(LinkableInterface); ok && (library.CcLibraryInterface() || library.RustLibraryInterface()) {
+ } else if library, ok := ctx.Module().(LinkableInterface); ok && (library.CcLibraryInterface() || library.RustLibraryInterface()) {
// Non-cc.Modules may need an empty variant for their mutators.
variations := []string{}
if library.NonCcVariants() {
variations = append(variations, "")
}
isLLNDK := false
- if m, ok := mctx.Module().(*Module); ok {
+ if m, ok := ctx.Module().(*Module); ok {
isLLNDK = m.IsLlndk()
}
buildStatic := library.BuildStaticVariant() && !isLLNDK
buildShared := library.BuildSharedVariant()
if buildStatic && buildShared {
- variations := append([]string{"static", "shared"}, variations...)
-
- modules := mctx.CreateLocalVariations(variations...)
- static := modules[0].(LinkableInterface)
- shared := modules[1].(LinkableInterface)
- static.SetStatic()
- shared.SetShared()
-
- if _, ok := library.(*Module); ok {
- reuseStaticLibrary(mctx, static.(*Module), shared.(*Module))
- }
- mctx.AliasVariation("shared")
+ variations = append([]string{"static", "shared"}, variations...)
+ return variations
} else if buildStatic {
- variations := append([]string{"static"}, variations...)
-
- modules := mctx.CreateLocalVariations(variations...)
- modules[0].(LinkableInterface).SetStatic()
- mctx.AliasVariation("static")
+ variations = append([]string{"static"}, variations...)
} else if buildShared {
- variations := append([]string{"shared"}, variations...)
-
- modules := mctx.CreateLocalVariations(variations...)
- modules[0].(LinkableInterface).SetShared()
- mctx.AliasVariation("shared")
- } else if len(variations) > 0 {
- mctx.CreateLocalVariations(variations...)
- mctx.AliasVariation(variations[0])
+ variations = append([]string{"shared"}, variations...)
}
- if library.BuildRlibVariant() && library.IsRustFFI() && !buildStatic {
+
+ if len(variations) > 0 {
+ return variations
+ }
+ }
+ return []string{""}
+}
+
+func (linkageTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+ return ""
+}
+
+func (linkageTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+ ccPrebuilt := false
+ if m, ok := ctx.Module().(*Module); ok && m.linker != nil {
+ _, ccPrebuilt = m.linker.(prebuiltLibraryInterface)
+ }
+ if ccPrebuilt {
+ if incomingVariation != "" {
+ return incomingVariation
+ }
+ library := ctx.Module().(*Module).linker.(prebuiltLibraryInterface)
+ if library.buildShared() {
+ return "shared"
+ } else if library.buildStatic() {
+ return "static"
+ }
+ return ""
+ } else if library, ok := ctx.Module().(LinkableInterface); ok && library.CcLibraryInterface() {
+ isLLNDK := false
+ if m, ok := ctx.Module().(*Module); ok {
+ isLLNDK = m.IsLlndk()
+ }
+ buildStatic := library.BuildStaticVariant() && !isLLNDK
+ buildShared := library.BuildSharedVariant()
+ if library.BuildRlibVariant() && library.IsRustFFI() && !buildStatic && (incomingVariation == "static" || incomingVariation == "") {
// Rust modules do not build static libs, but rlibs are used as if they
// were via `static_libs`. Thus we need to alias the BuildRlibVariant
// to "static" for Rust FFI libraries.
- mctx.CreateAliasVariation("static", "")
+ return ""
+ }
+ if incomingVariation != "" {
+ return incomingVariation
+ }
+ if buildShared {
+ return "shared"
+ } else if buildStatic {
+ return "static"
+ }
+ return ""
+ }
+ return ""
+}
+
+func (linkageTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+ ccPrebuilt := false
+ if m, ok := ctx.Module().(*Module); ok && m.linker != nil {
+ _, ccPrebuilt = m.linker.(prebuiltLibraryInterface)
+ }
+ if ccPrebuilt {
+ library := ctx.Module().(*Module).linker.(prebuiltLibraryInterface)
+ if variation == "static" {
+ library.setStatic()
+ if !library.buildStatic() {
+ library.disablePrebuilt()
+ }
+ } else if variation == "shared" {
+ library.setShared()
+ if !library.buildShared() {
+ library.disablePrebuilt()
+ }
+ }
+ } else if library, ok := ctx.Module().(LinkableInterface); ok && library.CcLibraryInterface() {
+ if variation == "static" {
+ library.SetStatic()
+ } else if variation == "shared" {
+ library.SetShared()
+ var isLLNDK bool
+ if m, ok := ctx.Module().(*Module); ok {
+ isLLNDK = m.IsLlndk()
+ }
+ buildStatic := library.BuildStaticVariant() && !isLLNDK
+ buildShared := library.BuildSharedVariant()
+ if buildStatic && buildShared {
+ if _, ok := library.(*Module); ok {
+ reuseStaticLibrary(ctx, library.(*Module))
+ }
+ }
}
}
}
@@ -2204,64 +2250,14 @@
}
}
-func createVersionVariations(mctx android.BottomUpMutatorContext, versions []string) {
- // "" is for the non-stubs (implementation) variant for system modules, or the LLNDK variant
- // for LLNDK modules.
- variants := append(android.CopyOf(versions), "")
-
- m := mctx.Module().(*Module)
- isLLNDK := m.IsLlndk()
- isVendorPublicLibrary := m.IsVendorPublicLibrary()
- isImportedApiLibrary := m.isImportedApiLibrary()
-
- modules := mctx.CreateLocalVariations(variants...)
- for i, m := range modules {
-
- if variants[i] != "" || isLLNDK || isVendorPublicLibrary || isImportedApiLibrary {
- // A stubs or LLNDK stubs variant.
- c := m.(*Module)
- if c.sanitize != nil {
- c.sanitize.Properties.ForceDisable = true
- }
- if c.stl != nil {
- c.stl.Properties.Stl = StringPtr("none")
- }
- c.Properties.PreventInstall = true
- lib := moduleLibraryInterface(m)
- isLatest := i == (len(versions) - 1)
- lib.setBuildStubs(isLatest)
-
- if variants[i] != "" {
- // A non-LLNDK stubs module is hidden from make and has a dependency from the
- // implementation module to the stubs module.
- c.Properties.HideFromMake = true
- lib.setStubsVersion(variants[i])
- mctx.AddInterVariantDependency(stubImplDepTag, modules[len(modules)-1], modules[i])
- }
- }
- }
- mctx.AliasVariation("")
- latestVersion := ""
- if len(versions) > 0 {
- latestVersion = versions[len(versions)-1]
- }
- mctx.CreateAliasVariation("latest", latestVersion)
-}
-
-func createPerApiVersionVariations(mctx android.BottomUpMutatorContext, minSdkVersion string) {
+func perApiVersionVariations(mctx android.BaseModuleContext, minSdkVersion string) []string {
from, err := nativeApiLevelFromUser(mctx, minSdkVersion)
if err != nil {
mctx.PropertyErrorf("min_sdk_version", err.Error())
- return
+ return []string{""}
}
- versionStrs := ndkLibraryVersions(mctx, from)
- modules := mctx.CreateLocalVariations(versionStrs...)
-
- for i, module := range modules {
- module.(*Module).Properties.Sdk_version = StringPtr(versionStrs[i])
- module.(*Module).Properties.Min_sdk_version = StringPtr(versionStrs[i])
- }
+ return ndkLibraryVersions(mctx, from)
}
func canBeOrLinkAgainstVersionVariants(module interface {
@@ -2291,7 +2287,7 @@
}
// setStubsVersions normalizes the versions in the Stubs.Versions property into MutatedProperties.AllStubsVersions.
-func setStubsVersions(mctx android.BottomUpMutatorContext, library libraryInterface, module *Module) {
+func setStubsVersions(mctx android.BaseModuleContext, library libraryInterface, module *Module) {
if !library.buildShared() || !canBeVersionVariant(module) {
return
}
@@ -2304,25 +2300,98 @@
library.setAllStubsVersions(versions)
}
-// versionMutator splits a module into the mandatory non-stubs variant
+// versionTransitionMutator splits a module into the mandatory non-stubs variant
// (which is unnamed) and zero or more stubs variants.
-func versionMutator(mctx android.BottomUpMutatorContext) {
- if mctx.Os() != android.Android {
- return
+type versionTransitionMutator struct{}
+
+func (versionTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+ if ctx.Os() != android.Android {
+ return []string{""}
}
- m, ok := mctx.Module().(*Module)
- if library := moduleLibraryInterface(mctx.Module()); library != nil && canBeVersionVariant(m) {
- setStubsVersions(mctx, library, m)
+ m, ok := ctx.Module().(*Module)
+ if library := moduleLibraryInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
+ setStubsVersions(ctx, library, m)
- createVersionVariations(mctx, library.allStubsVersions())
- return
+ return append(slices.Clone(library.allStubsVersions()), "")
+ } else if ok && m.SplitPerApiLevel() && m.IsSdkVariant() {
+ return perApiVersionVariations(ctx, m.MinSdkVersion())
}
- if ok {
- if m.SplitPerApiLevel() && m.IsSdkVariant() {
- createPerApiVersionVariations(mctx, m.MinSdkVersion())
+ return []string{""}
+}
+
+func (versionTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+ return ""
+}
+
+func (versionTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+ if ctx.Os() != android.Android {
+ return ""
+ }
+ m, ok := ctx.Module().(*Module)
+ if library := moduleLibraryInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
+ if incomingVariation == "latest" {
+ latestVersion := ""
+ versions := library.allStubsVersions()
+ if len(versions) > 0 {
+ latestVersion = versions[len(versions)-1]
+ }
+ return latestVersion
}
+ return incomingVariation
+ } else if ok && m.SplitPerApiLevel() && m.IsSdkVariant() {
+ // If this module only has variants with versions and the incoming dependency doesn't specify which one
+ // is needed then assume the latest version.
+ if incomingVariation == "" {
+ return android.FutureApiLevel.String()
+ }
+ return incomingVariation
+ }
+
+ return ""
+}
+
+func (versionTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+ // Optimization: return early if this module can't be affected.
+ if ctx.Os() != android.Android {
+ return
+ }
+
+ m, ok := ctx.Module().(*Module)
+ if library := moduleLibraryInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
+ isLLNDK := m.IsLlndk()
+ isVendorPublicLibrary := m.IsVendorPublicLibrary()
+ isImportedApiLibrary := m.isImportedApiLibrary()
+
+ if variation != "" || isLLNDK || isVendorPublicLibrary || isImportedApiLibrary {
+ // A stubs or LLNDK stubs variant.
+ if m.sanitize != nil {
+ m.sanitize.Properties.ForceDisable = true
+ }
+ if m.stl != nil {
+ m.stl.Properties.Stl = StringPtr("none")
+ }
+ m.Properties.PreventInstall = true
+ lib := moduleLibraryInterface(m)
+ allStubsVersions := library.allStubsVersions()
+ isLatest := len(allStubsVersions) > 0 && variation == allStubsVersions[len(allStubsVersions)-1]
+ lib.setBuildStubs(isLatest)
+ }
+ if variation != "" {
+ // A non-LLNDK stubs module is hidden from make
+ library.setStubsVersion(variation)
+ m.Properties.HideFromMake = true
+ } else {
+ // A non-LLNDK implementation module has a dependency to all stubs versions
+ for _, version := range library.allStubsVersions() {
+ ctx.AddVariationDependencies([]blueprint.Variation{{"version", version}},
+ stubImplDepTag, ctx.ModuleName())
+ }
+ }
+ } else if ok && m.SplitPerApiLevel() && m.IsSdkVariant() {
+ m.Properties.Sdk_version = StringPtr(variation)
+ m.Properties.Min_sdk_version = StringPtr(variation)
}
}
diff --git a/cc/library_stub.go b/cc/library_stub.go
index 6f06333..6367825 100644
--- a/cc/library_stub.go
+++ b/cc/library_stub.go
@@ -303,7 +303,7 @@
return d.hasApexStubs()
}
-func (d *apiLibraryDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
+func (d *apiLibraryDecorator) stubsVersions(ctx android.BaseModuleContext) []string {
m, ok := ctx.Module().(*Module)
if !ok {
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index f326068..b822e5c 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -133,7 +133,7 @@
return strings.TrimSuffix(name, ndkLibrarySuffix)
}
-func ndkLibraryVersions(ctx android.BaseMutatorContext, from android.ApiLevel) []string {
+func ndkLibraryVersions(ctx android.BaseModuleContext, from android.ApiLevel) []string {
var versions []android.ApiLevel
versionStrs := []string{}
for _, version := range ctx.Config().AllSupportedApiLevels() {
@@ -147,7 +147,7 @@
return versionStrs
}
-func (this *stubDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
+func (this *stubDecorator) stubsVersions(ctx android.BaseModuleContext) []string {
if !ctx.Module().Enabled(ctx) {
return nil
}
diff --git a/cc/sdk.go b/cc/sdk.go
index 4925ce1..dc1261d 100644
--- a/cc/sdk.go
+++ b/cc/sdk.go
@@ -19,12 +19,86 @@
"android/soong/genrule"
)
-// sdkMutator sets a creates a platform and an SDK variant for modules
+// sdkTransitionMutator creates a platform and an SDK variant for modules
// that set sdk_version, and ignores sdk_version for the platform
// variant. The SDK variant will be used for embedding in APKs
// that may be installed on older platforms. Apexes use their own
// variants that enforce backwards compatibility.
-func sdkMutator(ctx android.BottomUpMutatorContext) {
+type sdkTransitionMutator struct{}
+
+func (sdkTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+ if ctx.Os() != android.Android {
+ return []string{""}
+ }
+
+ switch m := ctx.Module().(type) {
+ case LinkableInterface:
+ if m.AlwaysSdk() {
+ if !m.UseSdk() && !m.SplitPerApiLevel() {
+ ctx.ModuleErrorf("UseSdk() must return true when AlwaysSdk is set, did the factory forget to set Sdk_version?")
+ }
+ return []string{"sdk"}
+ } else if m.UseSdk() || m.SplitPerApiLevel() {
+ return []string{"", "sdk"}
+ } else {
+ return []string{""}
+ }
+ case *genrule.Module:
+ if p, ok := m.Extra.(*GenruleExtraProperties); ok {
+ if String(p.Sdk_version) != "" {
+ return []string{"", "sdk"}
+ } else {
+ return []string{""}
+ }
+ }
+ case *CcApiVariant:
+ ccApiVariant, _ := ctx.Module().(*CcApiVariant)
+ if String(ccApiVariant.properties.Variant) == "ndk" {
+ return []string{"sdk"}
+ } else {
+ return []string{""}
+ }
+ }
+
+ return []string{""}
+}
+
+func (sdkTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+ return sourceVariation
+}
+
+func (sdkTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+ if ctx.Os() != android.Android {
+ return ""
+ }
+ switch m := ctx.Module().(type) {
+ case LinkableInterface:
+ if m.AlwaysSdk() {
+ return "sdk"
+ } else if m.UseSdk() || m.SplitPerApiLevel() {
+ return incomingVariation
+ }
+ case *genrule.Module:
+ if p, ok := m.Extra.(*GenruleExtraProperties); ok {
+ if String(p.Sdk_version) != "" {
+ return incomingVariation
+ }
+ }
+ case *CcApiVariant:
+ ccApiVariant, _ := ctx.Module().(*CcApiVariant)
+ if String(ccApiVariant.properties.Variant) == "ndk" {
+ return "sdk"
+ }
+ }
+
+ if ctx.IsAddingDependency() {
+ return incomingVariation
+ } else {
+ return ""
+ }
+}
+
+func (sdkTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
if ctx.Os() != android.Android {
return
}
@@ -33,59 +107,45 @@
case LinkableInterface:
ccModule, isCcModule := ctx.Module().(*Module)
if m.AlwaysSdk() {
- if !m.UseSdk() && !m.SplitPerApiLevel() {
- ctx.ModuleErrorf("UseSdk() must return true when AlwaysSdk is set, did the factory forget to set Sdk_version?")
+ if variation != "sdk" {
+ ctx.ModuleErrorf("tried to create variation %q for module with AlwaysSdk set, expected \"sdk\"", variation)
}
- modules := ctx.CreateVariations("sdk")
- modules[0].(*Module).Properties.IsSdkVariant = true
+
+ ccModule.Properties.IsSdkVariant = true
} else if m.UseSdk() || m.SplitPerApiLevel() {
- modules := ctx.CreateVariations("", "sdk")
+ if variation == "" {
+ // Clear the sdk_version property for the platform (non-SDK) variant so later code
+ // doesn't get confused by it.
+ ccModule.Properties.Sdk_version = nil
+ } else {
+ // Mark the SDK variant.
+ ccModule.Properties.IsSdkVariant = true
- // Clear the sdk_version property for the platform (non-SDK) variant so later code
- // doesn't get confused by it.
- modules[0].(*Module).Properties.Sdk_version = nil
-
- // Mark the SDK variant.
- modules[1].(*Module).Properties.IsSdkVariant = true
+ // SDK variant never gets installed because the variant is to be embedded in
+ // APKs, not to be installed to the platform.
+ ccModule.Properties.PreventInstall = true
+ }
if ctx.Config().UnbundledBuildApps() {
- // For an unbundled apps build, hide the platform variant from Make
- // so that other Make modules don't link against it, but against the
- // SDK variant.
- modules[0].(*Module).Properties.HideFromMake = true
+ if variation == "" {
+ // For an unbundled apps build, hide the platform variant from Make
+ // so that other Make modules don't link against it, but against the
+ // SDK variant.
+ ccModule.Properties.HideFromMake = true
+ }
} else {
- // For a platform build, mark the SDK variant so that it gets a ".sdk" suffix when
- // exposed to Make.
- modules[1].(*Module).Properties.SdkAndPlatformVariantVisibleToMake = true
+ if variation == "sdk" {
+ // For a platform build, mark the SDK variant so that it gets a ".sdk" suffix when
+ // exposed to Make.
+ ccModule.Properties.SdkAndPlatformVariantVisibleToMake = true
+ }
}
- // SDK variant never gets installed because the variant is to be embedded in
- // APKs, not to be installed to the platform.
- modules[1].(*Module).Properties.PreventInstall = true
- ctx.AliasVariation("")
} else {
if isCcModule {
// Clear the sdk_version property for modules that don't have an SDK variant so
// later code doesn't get confused by it.
ccModule.Properties.Sdk_version = nil
}
- ctx.CreateVariations("")
- ctx.AliasVariation("")
- }
- case *genrule.Module:
- if p, ok := m.Extra.(*GenruleExtraProperties); ok {
- if String(p.Sdk_version) != "" {
- ctx.CreateVariations("", "sdk")
- } else {
- ctx.CreateVariations("")
- }
- ctx.AliasVariation("")
- }
- case *CcApiVariant:
- ccApiVariant, _ := ctx.Module().(*CcApiVariant)
- if String(ccApiVariant.properties.Variant) == "ndk" {
- ctx.CreateVariations("sdk")
- } else {
- ctx.CreateVariations("")
}
}
}
diff --git a/cmd/release_config/release_config_lib/flag_declaration.go b/cmd/release_config/release_config_lib/flag_declaration.go
index 97d4d4c..22001bf 100644
--- a/cmd/release_config/release_config_lib/flag_declaration.go
+++ b/cmd/release_config/release_config_lib/flag_declaration.go
@@ -18,10 +18,21 @@
rc_proto "android/soong/cmd/release_config/release_config_proto"
)
+var (
+ // Allowlist: these flags may have duplicate (identical) declarations
+ // without generating an error. This will be removed once all such
+ // declarations have been fixed.
+ DuplicateDeclarationAllowlist = map[string]bool{}
+)
+
func FlagDeclarationFactory(protoPath string) (fd *rc_proto.FlagDeclaration) {
fd = &rc_proto.FlagDeclaration{}
if protoPath != "" {
LoadMessage(protoPath, fd)
}
+ // If the input didn't specify a value, create one (== UnspecifiedValue).
+ if fd.Value == nil {
+ fd.Value = &rc_proto.Value{Val: &rc_proto.Value_UnspecifiedValue{false}}
+ }
return fd
}
diff --git a/cmd/release_config/release_config_lib/release_configs.go b/cmd/release_config/release_config_lib/release_configs.go
index f2e1388..97eb8f1 100644
--- a/cmd/release_config/release_config_lib/release_configs.go
+++ b/cmd/release_config/release_config_lib/release_configs.go
@@ -42,6 +42,10 @@
// Flags declared this directory's flag_declarations/*.textproto
FlagDeclarations []rc_proto.FlagDeclaration
+
+ // Potential aconfig and build flag contributions in this map directory.
+ // This is used to detect errors.
+ FlagValueDirs map[string][]string
}
type ReleaseConfigDirMap map[string]int
@@ -272,6 +276,20 @@
configs.Aliases[name] = alias.Target
}
var err error
+ // Temporarily allowlist duplicate flag declaration files to prevent
+ // more from entering the tree while we work to clean up the duplicates
+ // that already exist.
+ dupFlagFile := filepath.Join(dir, "duplicate_allowlist.txt")
+ data, err := os.ReadFile(dupFlagFile)
+ if err == nil {
+ for _, flag := range strings.Split(string(data), "\n") {
+ flag = strings.TrimSpace(flag)
+ if strings.HasPrefix(flag, "//") || strings.HasPrefix(flag, "#") {
+ continue
+ }
+ DuplicateDeclarationAllowlist[flag] = true
+ }
+ }
err = WalkTextprotoFiles(dir, "flag_declarations", func(path string, d fs.DirEntry, err error) error {
flagDeclaration := FlagDeclarationFactory(path)
// Container must be specified.
@@ -285,14 +303,6 @@
}
}
- // TODO: once we have namespaces initialized, we can throw an error here.
- if flagDeclaration.Namespace == nil {
- flagDeclaration.Namespace = proto.String("android_UNKNOWN")
- }
- // If the input didn't specify a value, create one (== UnspecifiedValue).
- if flagDeclaration.Value == nil {
- flagDeclaration.Value = &rc_proto.Value{Val: &rc_proto.Value_UnspecifiedValue{false}}
- }
m.FlagDeclarations = append(m.FlagDeclarations, *flagDeclaration)
name := *flagDeclaration.Name
if name == "RELEASE_ACONFIG_VALUE_SETS" {
@@ -300,8 +310,8 @@
}
if def, ok := configs.FlagArtifacts[name]; !ok {
configs.FlagArtifacts[name] = &FlagArtifact{FlagDeclaration: flagDeclaration, DeclarationIndex: ConfigDirIndex}
- } else if !proto.Equal(def.FlagDeclaration, flagDeclaration) {
- return fmt.Errorf("Duplicate definition of %s", *flagDeclaration.Name)
+ } else if !proto.Equal(def.FlagDeclaration, flagDeclaration) || !DuplicateDeclarationAllowlist[name] {
+ return fmt.Errorf("Duplicate definition of %s in %s", *flagDeclaration.Name, path)
}
// Set the initial value in the flag artifact.
configs.FilesUsedMap[path] = true
@@ -317,6 +327,21 @@
return err
}
+ subDirs := func(subdir string) (ret []string) {
+ if flagVersions, err := os.ReadDir(filepath.Join(dir, subdir)); err == nil {
+ for _, e := range flagVersions {
+ if e.IsDir() && validReleaseConfigName(e.Name()) {
+ ret = append(ret, e.Name())
+ }
+ }
+ }
+ return
+ }
+ m.FlagValueDirs = map[string][]string{
+ "aconfig": subDirs("aconfig"),
+ "flag_values": subDirs("flag_values"),
+ }
+
err = WalkTextprotoFiles(dir, "release_configs", func(path string, d fs.DirEntry, err error) error {
releaseConfigContribution := &ReleaseConfigContribution{path: path, DeclarationIndex: ConfigDirIndex}
LoadMessage(path, &releaseConfigContribution.proto)
@@ -424,6 +449,27 @@
}
}
+ // Look for ignored flagging values. Gather the entire list to make it easier to fix them.
+ errors := []string{}
+ for _, contrib := range configs.ReleaseConfigMaps {
+ dirName := filepath.Dir(contrib.path)
+ for k, names := range contrib.FlagValueDirs {
+ for _, rcName := range names {
+ if config, err := configs.GetReleaseConfig(rcName); err == nil {
+ rcPath := filepath.Join(dirName, "release_configs", fmt.Sprintf("%s.textproto", config.Name))
+ if _, err := os.Stat(rcPath); err != nil {
+ errors = append(errors, fmt.Sprintf("%s exists but %s does not contribute to %s",
+ filepath.Join(dirName, k, rcName), dirName, config.Name))
+ }
+ }
+
+ }
+ }
+ }
+ if len(errors) > 0 {
+ return fmt.Errorf("%s", strings.Join(errors, "\n"))
+ }
+
releaseConfig, err := configs.GetReleaseConfig(targetRelease)
if err != nil {
return err
diff --git a/cmd/release_config/release_config_lib/util.go b/cmd/release_config/release_config_lib/util.go
index 9919c70..b149293 100644
--- a/cmd/release_config/release_config_lib/util.go
+++ b/cmd/release_config/release_config_lib/util.go
@@ -31,8 +31,9 @@
)
var (
- disableWarnings bool
- containerRegexp, _ = regexp.Compile("^[a-z][a-z0-9]*([._][a-z][a-z0-9]*)*$")
+ disableWarnings bool
+ containerRegexp, _ = regexp.Compile("^[a-z][a-z0-9]*([._][a-z][a-z0-9]*)*$")
+ releaseConfigRegexp, _ = regexp.Compile("^[a-z][a-z0-9]*([._][a-z0-9]*)*$")
)
type StringList []string
@@ -179,6 +180,10 @@
return containerRegexp.MatchString(container)
}
+func validReleaseConfigName(name string) bool {
+ return releaseConfigRegexp.MatchString(name)
+}
+
// Returns the default value for release config artifacts.
func GetDefaultOutDir() string {
outEnv := os.Getenv("OUT_DIR")
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 201515f..b69a76f 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -532,7 +532,7 @@
}
for _, f := range global.PatternsOnSystemOther {
- if makefileMatch("/" + f, dexLocation) || makefileMatch(filepath.Join(SystemPartition, f), dexLocation) {
+ if makefileMatch("/"+f, dexLocation) || makefileMatch(filepath.Join(SystemPartition, f), dexLocation) {
return true
}
}
diff --git a/docs/resources.md b/docs/resources.md
new file mode 100644
index 0000000..c7cb0cf
--- /dev/null
+++ b/docs/resources.md
@@ -0,0 +1,89 @@
+## Soong Android Resource Compilation
+
+The Android build process involves several steps to compile resources into a format that the Android app can use
+efficiently in android_library, android_app and android_test modules. See the
+[resources documentation](https://developer.android.com/guide/topics/resources/providing-resources) for general
+information on resources (with a focus on building with Gradle).
+
+For all modules, AAPT2 compiles resources provided by directories listed in the resource_dirs directory (which is
+implicitly set to `["res"]` if unset, but can be overridden by setting the `resource_dirs` property).
+
+## android_library with resource processor
+For an android_library with resource processor enabled (currently by setting `use_resource_processor: true`, but will be
+enabled by default in the future):
+- AAPT2 generates the `package-res.apk` file with a resource table that contains all resources from the current
+android_library module. `package-res.apk` files from transitive dependencies are passed to AAPT2 with the `-I` flag to
+resolve references to resources from dependencies.
+- AAPT2 generates an R.txt file that lists all the resources provided by the current android_library module.
+- ResourceProcessorBusyBox reads the `R.txt` file for the current android_library and produces an `R.jar` with an
+`R.class` in the package listed in the android_library's `AndroidManifest.xml` file that contains java fields for each
+resource ID. The resource IDs are non-final, as the final IDs will not be known until the resource table of the final
+android_app or android_test module is built.
+- The android_library's java and/or kotlin code is compiled with the generated `R.jar` in the classpath, along with the
+`R.jar` files from all transitive android_library dependencies.
+
+## android_app or android_test with resource processor
+For an android_app or android_test with resource processor enabled (currently by setting `use_resource_processor: true`,
+but will be enabled by default in the future):
+- AAPT2 generates the `package-res.apk` file with a resource table that contains all resources from the current
+android_app or android_test, as well as all transitive android_library modules referenced via `static_libs`. The
+current module is overlaid on dependencies so that resources from the current module replace resources from dependencies
+in the case of conflicts.
+- AAPT2 generates an R.txt file that lists all the resources provided by the current android_app or android_test, as
+well as all transitive android_library modules referenced via `static_libs`. The R.txt file contains the final resource
+ID for each resource.
+- ResourceProcessorBusyBox reads the `R.txt` file for the current android_app or android_test, as well as all transitive
+android_library modules referenced via `static_libs`, and produces an `R.jar` with an `R.class` in the package listed in
+the android_app or android_test's `AndroidManifest.xml` file that contains java fields for all local or transitive
+resource IDs. In addition, it creates an `R.class` in the package listed in each android_library dependency's
+`AndroidManifest.xml` file that contains final resource IDs for the resources that were found in that library.
+- The android_app or android_test's java and/or kotlin code is compiled with the current module's `R.jar` in the
+classpath, but not the `R.jar` files from transitive android_library dependencies. The `R.jar` file is also merged into
+the program classes that are dexed and placed in the final APK.
+
+## android_app, android_test or android_library without resource processor
+For an android_app, android_test or android_library without resource processor enabled (current the default, or
+explicitly set with `use_resource_processor: false`):
+- AAPT2 generates the `package-res.apk` file with a resource table that contains all resources from the current
+android_app, android_test or android_library module, as well as all transitive android_library modules referenced via
+`static_libs`. The current module is overlaid on dependencies so that resources from the current module replace
+resources from dependencies in the case of conflicts.
+- AAPT2 generates an `R.java` file in the package listed in each the current module's `AndroidManifest.xml` file that
+contains resource IDs for all resources from the current module as well as all transitive android_library modules
+referenced via `static_libs`. The same `R.java` containing all local and transitive resources is also duplicated into
+every package listed in an `AndroidManifest.xml` file in any static `android_library` dependency.
+- The module's java and/or kotlin code is compiled along with all the generated `R.java` files.
+
+
+## Downsides of legacy resource compilation without resource processor
+
+Compiling resources without using the resource processor results in a generated R.java source file for every transitive
+package that contains every transitive resource. For modules with large transitive dependency trees this can be tens of
+thousands of resource IDs duplicated in tens to a hundred java sources. These java sources all have to be compiled in
+every successive module in the dependency tree, and then the final R8 step has to drop hundreds of thousands of
+unreferenced fields. This results in significant build time and disk usage increases over building with resource
+processor.
+
+## Converting to compilation with resource processor
+
+### Reference resources using the package name of the module that includes them.
+Converting an android_library module to build with resource processor requires fixing any references to resources
+provided by android_library dependencies to reference the R classes using the package name found in the
+`AndroidManifest.xml` file of the dependency. For example, when referencing an androidx resource:
+```java
+View.inflate(mContext, R.layout.preference, null));
+```
+must be replaced with:
+```java
+View.inflate(mContext, androidx.preference.R.layout.preference, null));
+```
+
+### Use unique package names for each module in `AndroidManifest.xml`
+
+Each module will produce an `R.jar` containing an `R.class` in the package specified in it's `AndroidManifest.xml`.
+If multiple modules use the same package name they will produce conflicting `R.class` files, which can cause some
+resource IDs to appear to be missing.
+
+If existing code has multiple modules that contribute resources to the same package, one option is to move all the
+resources into a single resources-only `android_library` module with no code, and then depend on that from all the other
+modules.
\ No newline at end of file
diff --git a/elf/Android.bp b/elf/Android.bp
index 6450be1..6d3f4f0 100644
--- a/elf/Android.bp
+++ b/elf/Android.bp
@@ -20,6 +20,7 @@
name: "soong-elf",
pkgPath: "android/soong/elf",
srcs: [
+ "build_id_dir.go",
"elf.go",
],
testSrcs: [
diff --git a/elf/build_id_dir.go b/elf/build_id_dir.go
new file mode 100644
index 0000000..5fb7dda
--- /dev/null
+++ b/elf/build_id_dir.go
@@ -0,0 +1,172 @@
+// Copyright 2024 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 elf
+
+import (
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+ "time"
+)
+
+func UpdateBuildIdDir(path string) error {
+ path = filepath.Clean(path)
+ buildIdPath := path + "/.build-id"
+
+ // Collect the list of files and build-id symlinks. If the symlinks are
+ // up to date (newer than the symbol files), there is nothing to do.
+ var buildIdFiles, symbolFiles []string
+ var buildIdMtime, symbolsMtime time.Time
+ filepath.WalkDir(path, func(path string, entry fs.DirEntry, err error) error {
+ if entry == nil || entry.IsDir() {
+ return nil
+ }
+ info, err := entry.Info()
+ if err != nil {
+ return err
+ }
+ mtime := info.ModTime()
+ if strings.HasPrefix(path, buildIdPath) {
+ if buildIdMtime.Compare(mtime) < 0 {
+ buildIdMtime = mtime
+ }
+ buildIdFiles = append(buildIdFiles, path)
+ } else {
+ if symbolsMtime.Compare(mtime) < 0 {
+ symbolsMtime = mtime
+ }
+ symbolFiles = append(symbolFiles, path)
+ }
+ return nil
+ })
+ if symbolsMtime.Compare(buildIdMtime) < 0 {
+ return nil
+ }
+
+ // Collect build-id -> file mapping from ELF files in the symbols directory.
+ concurrency := 8
+ done := make(chan error)
+ buildIdToFile := make(map[string]string)
+ var mu sync.Mutex
+ for i := 0; i != concurrency; i++ {
+ go func(paths []string) {
+ for _, path := range paths {
+ id, err := Identifier(path, true)
+ if err != nil {
+ done <- err
+ return
+ }
+ if id == "" {
+ continue
+ }
+ mu.Lock()
+ oldPath := buildIdToFile[id]
+ if oldPath == "" || oldPath > path {
+ buildIdToFile[id] = path
+ }
+ mu.Unlock()
+ }
+ done <- nil
+ }(symbolFiles[len(symbolFiles)*i/concurrency : len(symbolFiles)*(i+1)/concurrency])
+ }
+
+ // Collect previously generated build-id -> file mapping from the .build-id directory.
+ // We will use this for incremental updates. If we see anything in the .build-id
+ // directory that we did not expect, we'll delete it and start over.
+ prevBuildIdToFile := make(map[string]string)
+out:
+ for _, buildIdFile := range buildIdFiles {
+ if !strings.HasSuffix(buildIdFile, ".debug") {
+ prevBuildIdToFile = nil
+ break
+ }
+ buildId := buildIdFile[len(buildIdPath)+1 : len(buildIdFile)-6]
+ for i, ch := range buildId {
+ if i == 2 {
+ if ch != '/' {
+ prevBuildIdToFile = nil
+ break out
+ }
+ } else {
+ if (ch < '0' || ch > '9') && (ch < 'a' || ch > 'f') {
+ prevBuildIdToFile = nil
+ break out
+ }
+ }
+ }
+ target, err := os.Readlink(buildIdFile)
+ if err != nil || !strings.HasPrefix(target, "../../") {
+ prevBuildIdToFile = nil
+ break
+ }
+ prevBuildIdToFile[buildId[0:2]+buildId[3:]] = path + target[5:]
+ }
+ if prevBuildIdToFile == nil {
+ err := os.RemoveAll(buildIdPath)
+ if err != nil {
+ return err
+ }
+ prevBuildIdToFile = make(map[string]string)
+ }
+
+ // Wait for build-id collection from ELF files to finish.
+ for i := 0; i != concurrency; i++ {
+ err := <-done
+ if err != nil {
+ return err
+ }
+ }
+
+ // Delete old symlinks.
+ for id, _ := range prevBuildIdToFile {
+ if buildIdToFile[id] == "" {
+ symlinkDir := buildIdPath + "/" + id[:2]
+ symlinkPath := symlinkDir + "/" + id[2:] + ".debug"
+ if err := os.Remove(symlinkPath); err != nil {
+ return err
+ }
+ }
+ }
+
+ // Add new symlinks and update changed symlinks.
+ for id, path := range buildIdToFile {
+ prevPath := prevBuildIdToFile[id]
+ if prevPath == path {
+ continue
+ }
+ symlinkDir := buildIdPath + "/" + id[:2]
+ symlinkPath := symlinkDir + "/" + id[2:] + ".debug"
+ if prevPath == "" {
+ if err := os.MkdirAll(symlinkDir, 0755); err != nil {
+ return err
+ }
+ } else {
+ if err := os.Remove(symlinkPath); err != nil {
+ return err
+ }
+ }
+
+ target, err := filepath.Rel(symlinkDir, path)
+ if err != nil {
+ return err
+ }
+ if err := os.Symlink(target, symlinkPath); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/java/aapt2.go b/java/aapt2.go
index f704fc6..61cf373 100644
--- a/java/aapt2.go
+++ b/java/aapt2.go
@@ -69,7 +69,7 @@
// aapt2Compile compiles resources and puts the results in the requested directory.
func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Paths,
- flags []string, productToFilter string) android.WritablePaths {
+ flags []string, productToFilter string, featureFlagsPaths android.Paths) android.WritablePaths {
if productToFilter != "" && productToFilter != "default" {
// --filter-product leaves only product-specific resources. Product-specific resources only exist
// in value resources (values/*.xml), so filter value resource files only. Ignore other types of
@@ -85,6 +85,10 @@
flags = append([]string{"--filter-product " + productToFilter}, flags...)
}
+ for _, featureFlagsPath := range android.SortedUniquePaths(featureFlagsPaths) {
+ flags = append(flags, "--feature-flags", "@"+featureFlagsPath.String())
+ }
+
// Shard the input paths so that they can be processed in parallel. If we shard them into too
// small chunks, the additional cost of spinning up aapt2 outweighs the performance gain. The
// current shard size, 100, seems to be a good balance between the added cost and the gain.
@@ -112,6 +116,7 @@
ctx.Build(pctx, android.BuildParams{
Rule: aapt2CompileRule,
Description: "aapt2 compile " + dir.String() + shardDesc,
+ Implicits: featureFlagsPaths,
Inputs: shard,
Outputs: outPaths,
Args: map[string]string{
diff --git a/java/aar.go b/java/aar.go
index 2f49a95..b69b7c2 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -440,7 +440,8 @@
var compiledResDirs []android.Paths
for _, dir := range resDirs {
a.resourceFiles = append(a.resourceFiles, dir.files...)
- compiledResDirs = append(compiledResDirs, aapt2Compile(ctx, dir.dir, dir.files, compileFlags, a.filterProduct()).Paths())
+ compiledResDirs = append(compiledResDirs, aapt2Compile(ctx, dir.dir, dir.files,
+ compileFlags, a.filterProduct(), opts.aconfigTextFiles).Paths())
}
for i, zip := range resZips {
@@ -499,7 +500,8 @@
}
for _, dir := range overlayDirs {
- compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files, compileFlags, a.filterProduct()).Paths()...)
+ compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files,
+ compileFlags, a.filterProduct(), opts.aconfigTextFiles).Paths()...)
}
var splitPackages android.WritablePaths
diff --git a/java/app_test.go b/java/app_test.go
index e878ccf..6b7d522 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -4364,7 +4364,16 @@
}
func TestAppFlagsPackages(t *testing.T) {
- ctx := testApp(t, `
+ ctx := android.GroupFixturePreparers(
+ prepareForJavaTest,
+ android.FixtureMergeMockFs(
+ map[string][]byte{
+ "res/layout/layout.xml": nil,
+ "res/values/strings.xml": nil,
+ "res/values-en-rUS/strings.xml": nil,
+ },
+ ),
+ ).RunTestWithBp(t, `
android_app {
name: "foo",
srcs: ["a.java"],
@@ -4396,10 +4405,10 @@
// android_app module depends on aconfig_declarations listed in flags_packages
android.AssertBoolEquals(t, "foo expected to depend on bar", true,
- CheckModuleHasDependency(t, ctx, "foo", "android_common", "bar"))
+ CheckModuleHasDependency(t, ctx.TestContext, "foo", "android_common", "bar"))
android.AssertBoolEquals(t, "foo expected to depend on baz", true,
- CheckModuleHasDependency(t, ctx, "foo", "android_common", "baz"))
+ CheckModuleHasDependency(t, ctx.TestContext, "foo", "android_common", "baz"))
aapt2LinkRule := foo.Rule("android/soong/java.aapt2Link")
linkInFlags := aapt2LinkRule.Args["inFlags"]
@@ -4408,6 +4417,14 @@
linkInFlags,
"--feature-flags @out/soong/.intermediates/bar/intermediate.txt --feature-flags @out/soong/.intermediates/baz/intermediate.txt",
)
+
+ aapt2CompileRule := foo.Rule("android/soong/java.aapt2Compile")
+ compileFlags := aapt2CompileRule.Args["cFlags"]
+ android.AssertStringDoesContain(t,
+ "aapt2 compile command expected to pass feature flags arguments",
+ compileFlags,
+ "--feature-flags @out/soong/.intermediates/bar/intermediate.txt --feature-flags @out/soong/.intermediates/baz/intermediate.txt",
+ )
}
func TestAppFlagsPackagesPropagation(t *testing.T) {
diff --git a/python/python.go b/python/python.go
index 1ee533f..8726f02 100644
--- a/python/python.go
+++ b/python/python.go
@@ -38,7 +38,7 @@
// Exported to support other packages using Python modules in tests.
func RegisterPythonPreDepsMutators(ctx android.RegisterMutatorsContext) {
- ctx.BottomUp("python_version", versionSplitMutator()).Parallel()
+ ctx.Transition("python_version", &versionSplitTransitionMutator{})
}
// the version-specific properties that apply to python modules.
@@ -245,7 +245,6 @@
protoExt = ".proto"
pyVersion2 = "PY2"
pyVersion3 = "PY3"
- pyVersion2And3 = "PY2ANDPY3"
internalPath = "internal"
)
@@ -253,46 +252,68 @@
getBaseProperties() *BaseProperties
}
-// versionSplitMutator creates version variants for modules and appends the version-specific
-// properties for a given variant to the properties in the variant module
-func versionSplitMutator() func(android.BottomUpMutatorContext) {
- return func(mctx android.BottomUpMutatorContext) {
- if base, ok := mctx.Module().(basePropertiesProvider); ok {
- props := base.getBaseProperties()
- var versionNames []string
- // collect version specific properties, so that we can merge version-specific properties
- // into the module's overall properties
- var versionProps []VersionProperties
- // PY3 is first so that we alias the PY3 variant rather than PY2 if both
- // are available
- if proptools.BoolDefault(props.Version.Py3.Enabled, true) {
- versionNames = append(versionNames, pyVersion3)
- versionProps = append(versionProps, props.Version.Py3)
+type versionSplitTransitionMutator struct{}
+
+func (versionSplitTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+ if base, ok := ctx.Module().(basePropertiesProvider); ok {
+ props := base.getBaseProperties()
+ var variants []string
+ // PY3 is first so that we alias the PY3 variant rather than PY2 if both
+ // are available
+ if proptools.BoolDefault(props.Version.Py3.Enabled, true) {
+ variants = append(variants, pyVersion3)
+ }
+ if proptools.BoolDefault(props.Version.Py2.Enabled, false) {
+ if !ctx.DeviceConfig().BuildBrokenUsesSoongPython2Modules() &&
+ ctx.ModuleName() != "py2-cmd" &&
+ ctx.ModuleName() != "py2-stdlib" {
+ ctx.PropertyErrorf("version.py2.enabled", "Python 2 is no longer supported, please convert to python 3. This error can be temporarily overridden by setting BUILD_BROKEN_USES_SOONG_PYTHON2_MODULES := true in the product configuration")
}
- if proptools.BoolDefault(props.Version.Py2.Enabled, false) {
- if !mctx.DeviceConfig().BuildBrokenUsesSoongPython2Modules() &&
- mctx.ModuleName() != "py2-cmd" &&
- mctx.ModuleName() != "py2-stdlib" {
- mctx.PropertyErrorf("version.py2.enabled", "Python 2 is no longer supported, please convert to python 3. This error can be temporarily overridden by setting BUILD_BROKEN_USES_SOONG_PYTHON2_MODULES := true in the product configuration")
- }
- versionNames = append(versionNames, pyVersion2)
- versionProps = append(versionProps, props.Version.Py2)
- }
- modules := mctx.CreateLocalVariations(versionNames...)
- // Alias module to the first variant
- if len(versionNames) > 0 {
- mctx.AliasVariation(versionNames[0])
- }
- for i, v := range versionNames {
- // set the actual version for Python module.
- newProps := modules[i].(basePropertiesProvider).getBaseProperties()
- newProps.Actual_version = v
- // append versioned properties for the Python module to the overall properties
- err := proptools.AppendMatchingProperties([]interface{}{newProps}, &versionProps[i], nil)
- if err != nil {
- panic(err)
- }
- }
+ variants = append(variants, pyVersion2)
+ }
+ return variants
+ }
+ return []string{""}
+}
+
+func (versionSplitTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+ return ""
+}
+
+func (versionSplitTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+ if incomingVariation != "" {
+ return incomingVariation
+ }
+ if base, ok := ctx.Module().(basePropertiesProvider); ok {
+ props := base.getBaseProperties()
+ if proptools.BoolDefault(props.Version.Py3.Enabled, true) {
+ return pyVersion3
+ } else {
+ return pyVersion2
+ }
+ }
+
+ return ""
+}
+
+func (versionSplitTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+ if variation == "" {
+ return
+ }
+ if base, ok := ctx.Module().(basePropertiesProvider); ok {
+ props := base.getBaseProperties()
+ props.Actual_version = variation
+
+ var versionProps *VersionProperties
+ if variation == pyVersion3 {
+ versionProps = &props.Version.Py3
+ } else if variation == pyVersion2 {
+ versionProps = &props.Version.Py2
+ }
+
+ err := proptools.AppendMatchingProperties([]interface{}{props}, versionProps, nil)
+ if err != nil {
+ panic(err)
}
}
}
diff --git a/rust/coverage.go b/rust/coverage.go
index e0e919c..91a7806 100644
--- a/rust/coverage.go
+++ b/rust/coverage.go
@@ -47,7 +47,7 @@
// no_std modules are missing libprofiler_builtins which provides coverage, so we need to add it as a dependency.
if rustModule, ok := ctx.Module().(*Module); ok && rustModule.compiler.noStdlibs() {
- ctx.AddVariationDependencies([]blueprint.Variation{{Mutator: "rust_libraries", Variation: "rlib"}, {Mutator: "link", Variation: ""}}, rlibDepTag, ProfilerBuiltins)
+ ctx.AddVariationDependencies([]blueprint.Variation{{Mutator: "rust_libraries", Variation: "rlib"}}, rlibDepTag, ProfilerBuiltins)
}
}
diff --git a/rust/library.go b/rust/library.go
index ba73f27..50d5a72 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -20,6 +20,8 @@
"regexp"
"strings"
+ "github.com/google/blueprint"
+
"android/soong/android"
"android/soong/cc"
)
@@ -692,31 +694,28 @@
}
}
-// LibraryMutator mutates the libraries into variants according to the
-// build{Rlib,Dylib} attributes.
-func LibraryMutator(mctx android.BottomUpMutatorContext) {
- // Only mutate on Rust libraries.
- m, ok := mctx.Module().(*Module)
+type libraryTransitionMutator struct{}
+
+func (libraryTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+ m, ok := ctx.Module().(*Module)
if !ok || m.compiler == nil {
- return
+ return []string{""}
}
library, ok := m.compiler.(libraryInterface)
if !ok {
- return
+ return []string{""}
}
// Don't produce rlib/dylib/source variants for shared or static variants
if library.shared() || library.static() {
- return
+ return []string{""}
}
var variants []string
// The source variant is used for SourceProvider modules. The other variants (i.e. rlib and dylib)
// depend on this variant. It must be the first variant to be declared.
- sourceVariant := false
if m.sourceProvider != nil {
- variants = append(variants, "source")
- sourceVariant = true
+ variants = append(variants, sourceVariation)
}
if library.buildRlib() {
variants = append(variants, rlibVariation)
@@ -726,92 +725,134 @@
}
if len(variants) == 0 {
- return
+ return []string{""}
}
- modules := mctx.CreateLocalVariations(variants...)
- // The order of the variations (modules) matches the variant names provided. Iterate
- // through the new variation modules and set their mutated properties.
- var emptyVariant = false
- var rlibVariant = false
- for i, v := range modules {
- switch variants[i] {
- case rlibVariation:
- v.(*Module).compiler.(libraryInterface).setRlib()
- rlibVariant = true
- case dylibVariation:
- v.(*Module).compiler.(libraryInterface).setDylib()
- if v.(*Module).ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
- // TODO(b/165791368)
- // Disable dylib Vendor Ramdisk variations until we support these.
- v.(*Module).Disable()
- }
+ return variants
+}
- case "source":
- v.(*Module).compiler.(libraryInterface).setSource()
- // The source variant does not produce any library.
- // Disable the compilation steps.
- v.(*Module).compiler.SetDisabled()
- case "":
- emptyVariant = true
+func (libraryTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+ return ""
+}
+
+func (libraryTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+ m, ok := ctx.Module().(*Module)
+ if !ok || m.compiler == nil {
+ return ""
+ }
+ library, ok := m.compiler.(libraryInterface)
+ if !ok {
+ return ""
+ }
+
+ if incomingVariation == "" {
+ if m.sourceProvider != nil {
+ return sourceVariation
+ }
+ if library.shared() {
+ return ""
+ }
+ if library.buildRlib() {
+ return rlibVariation
+ }
+ if library.buildDylib() {
+ return dylibVariation
}
}
+ return incomingVariation
+}
- if rlibVariant && library.isFFILibrary() {
- // If an rlib variant is set and this is an FFI library, make it the
- // default variant so CC can link against it appropriately.
- mctx.AliasVariation(rlibVariation)
- } else if emptyVariant {
- // If there's an empty variant, alias it so it is the default variant
- mctx.AliasVariation("")
+func (libraryTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+ m, ok := ctx.Module().(*Module)
+ if !ok || m.compiler == nil {
+ return
+ }
+ library, ok := m.compiler.(libraryInterface)
+ if !ok {
+ return
+ }
+
+ switch variation {
+ case rlibVariation:
+ library.setRlib()
+ case dylibVariation:
+ library.setDylib()
+ if m.ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
+ // TODO(b/165791368)
+ // Disable dylib Vendor Ramdisk variations until we support these.
+ m.Disable()
+ }
+
+ case sourceVariation:
+ library.setSource()
+ // The source variant does not produce any library.
+ // Disable the compilation steps.
+ m.compiler.SetDisabled()
}
// If a source variant is created, add an inter-variant dependency
// between the other variants and the source variant.
- if sourceVariant {
- sv := modules[0]
- for _, v := range modules[1:] {
- if !v.Enabled(mctx) {
- continue
- }
- mctx.AddInterVariantDependency(sourceDepTag, v, sv)
- }
- // Alias the source variation so it can be named directly in "srcs" properties.
- mctx.AliasVariation("source")
+ if m.sourceProvider != nil && variation != sourceVariation {
+ ctx.AddVariationDependencies(
+ []blueprint.Variation{
+ {"rust_libraries", sourceVariation},
+ },
+ sourceDepTag, ctx.ModuleName())
}
}
-func LibstdMutator(mctx android.BottomUpMutatorContext) {
- if m, ok := mctx.Module().(*Module); ok && m.compiler != nil && !m.compiler.Disabled() {
- switch library := m.compiler.(type) {
- case libraryInterface:
- // Only create a variant if a library is actually being built.
- if library.rlib() && !library.sysroot() {
- // If this is a rust_ffi variant it only needs rlib-std
- if library.isFFILibrary() {
- variants := []string{"rlib-std"}
- modules := mctx.CreateLocalVariations(variants...)
- rlib := modules[0].(*Module)
- rlib.compiler.(libraryInterface).setRlibStd()
- rlib.Properties.RustSubName += RlibStdlibSuffix
- mctx.AliasVariation("rlib-std")
- } else {
- variants := []string{"rlib-std", "dylib-std"}
- modules := mctx.CreateLocalVariations(variants...)
+type libstdTransitionMutator struct{}
- rlib := modules[0].(*Module)
- dylib := modules[1].(*Module)
- rlib.compiler.(libraryInterface).setRlibStd()
- dylib.compiler.(libraryInterface).setDylibStd()
- if dylib.ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
- // TODO(b/165791368)
- // Disable rlibs that link against dylib-std on vendor ramdisk variations until those dylib
- // variants are properly supported.
- dylib.Disable()
- }
- rlib.Properties.RustSubName += RlibStdlibSuffix
+func (libstdTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+ if m, ok := ctx.Module().(*Module); ok && m.compiler != nil && !m.compiler.Disabled() {
+ // Only create a variant if a library is actually being built.
+ if library, ok := m.compiler.(libraryInterface); ok {
+ if library.rlib() && !library.sysroot() {
+ if library.isFFILibrary() {
+ return []string{"rlib-std"}
+ } else {
+ return []string{"rlib-std", "dylib-std"}
}
}
}
}
+ return []string{""}
+}
+
+func (libstdTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+ return ""
+}
+
+func (libstdTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+ if m, ok := ctx.Module().(*Module); ok && m.compiler != nil && !m.compiler.Disabled() {
+ if library, ok := m.compiler.(libraryInterface); ok {
+ if library.shared() {
+ return ""
+ }
+ if library.rlib() && !library.sysroot() {
+ if incomingVariation != "" {
+ return incomingVariation
+ }
+ return "rlib-std"
+ }
+ }
+ }
+ return ""
+}
+
+func (libstdTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+ if variation == "rlib-std" {
+ rlib := ctx.Module().(*Module)
+ rlib.compiler.(libraryInterface).setRlibStd()
+ rlib.Properties.RustSubName += RlibStdlibSuffix
+ } else if variation == "dylib-std" {
+ dylib := ctx.Module().(*Module)
+ dylib.compiler.(libraryInterface).setDylibStd()
+ if dylib.ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
+ // TODO(b/165791368)
+ // Disable rlibs that link against dylib-std on vendor ramdisk variations until those dylib
+ // variants are properly supported.
+ dylib.Disable()
+ }
+ }
}
diff --git a/rust/rust.go b/rust/rust.go
index 9dae75e..3402adc 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -37,20 +37,24 @@
func init() {
android.RegisterModuleType("rust_defaults", defaultsFactory)
- android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
- ctx.BottomUp("rust_libraries", LibraryMutator).Parallel()
- ctx.BottomUp("rust_stdlinkage", LibstdMutator).Parallel()
- ctx.BottomUp("rust_begin", BeginMutator).Parallel()
- })
- android.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
- ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
- })
+ android.PreDepsMutators(registerPreDepsMutators)
+ android.PostDepsMutators(registerPostDepsMutators)
pctx.Import("android/soong/android")
pctx.Import("android/soong/rust/config")
pctx.ImportAs("cc_config", "android/soong/cc/config")
android.InitRegistrationContext.RegisterParallelSingletonType("kythe_rust_extract", kytheExtractRustFactory)
}
+func registerPreDepsMutators(ctx android.RegisterMutatorsContext) {
+ ctx.Transition("rust_libraries", &libraryTransitionMutator{})
+ ctx.Transition("rust_stdlinkage", &libstdTransitionMutator{})
+ ctx.BottomUp("rust_begin", BeginMutator).Parallel()
+}
+
+func registerPostDepsMutators(ctx android.RegisterMutatorsContext) {
+ ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
+}
+
type Flags struct {
GlobalRustFlags []string // Flags that apply globally to rust
GlobalLinkFlags []string // Flags that apply globally to linker
@@ -1128,10 +1132,11 @@
}
var (
- rlibVariation = "rlib"
- dylibVariation = "dylib"
- rlibAutoDep = autoDep{variation: rlibVariation, depTag: rlibDepTag}
- dylibAutoDep = autoDep{variation: dylibVariation, depTag: dylibDepTag}
+ sourceVariation = "source"
+ rlibVariation = "rlib"
+ dylibVariation = "dylib"
+ rlibAutoDep = autoDep{variation: rlibVariation, depTag: rlibDepTag}
+ dylibAutoDep = autoDep{variation: dylibVariation, depTag: dylibDepTag}
)
type autoDeppable interface {
@@ -1613,7 +1618,6 @@
}
rlibDepVariations := commonDepVariations
- rlibDepVariations = append(rlibDepVariations, blueprint.Variation{Mutator: "link", Variation: ""})
if lib, ok := mod.compiler.(libraryInterface); !ok || !lib.sysroot() {
rlibDepVariations = append(rlibDepVariations,
@@ -1629,7 +1633,6 @@
// dylibs
dylibDepVariations := append(commonDepVariations, blueprint.Variation{Mutator: "rust_libraries", Variation: dylibVariation})
- dylibDepVariations = append(dylibDepVariations, blueprint.Variation{Mutator: "link", Variation: ""})
for _, lib := range deps.Dylibs {
actx.AddVariationDependencies(dylibDepVariations, dylibDepTag, lib)
@@ -1650,7 +1653,6 @@
// otherwise select the rlib variant.
autoDepVariations := append(commonDepVariations,
blueprint.Variation{Mutator: "rust_libraries", Variation: autoDep.variation})
- autoDepVariations = append(autoDepVariations, blueprint.Variation{Mutator: "link", Variation: ""})
if actx.OtherModuleDependencyVariantExists(autoDepVariations, lib) {
actx.AddVariationDependencies(autoDepVariations, autoDep.depTag, lib)
@@ -1664,8 +1666,7 @@
} else if _, ok := mod.sourceProvider.(*protobufDecorator); ok {
for _, lib := range deps.Rustlibs {
srcProviderVariations := append(commonDepVariations,
- blueprint.Variation{Mutator: "rust_libraries", Variation: "source"})
- srcProviderVariations = append(srcProviderVariations, blueprint.Variation{Mutator: "link", Variation: ""})
+ blueprint.Variation{Mutator: "rust_libraries", Variation: sourceVariation})
// Only add rustlib dependencies if they're source providers themselves.
// This is used to track which crate names need to be added to the source generated
@@ -1681,7 +1682,7 @@
if deps.Stdlibs != nil {
if mod.compiler.stdLinkage(ctx) == RlibLinkage {
for _, lib := range deps.Stdlibs {
- actx.AddVariationDependencies(append(commonDepVariations, []blueprint.Variation{{Mutator: "rust_libraries", Variation: "rlib"}, {Mutator: "link", Variation: ""}}...),
+ actx.AddVariationDependencies(append(commonDepVariations, []blueprint.Variation{{Mutator: "rust_libraries", Variation: "rlib"}}...),
rlibDepTag, lib)
}
} else {
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 0d005d0..eeedf3f 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -60,6 +60,7 @@
// testRust returns a TestContext in which a basic environment has been setup.
// This environment contains a few mocked files. See rustMockedFiles for the list of these files.
func testRust(t *testing.T, bp string) *android.TestContext {
+ t.Helper()
skipTestIfOsNotSupported(t)
result := android.GroupFixturePreparers(
prepareForRustTest,
diff --git a/rust/test.go b/rust/test.go
index 3087d8d..b7ddd06 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -116,7 +116,8 @@
}
func (test *testDecorator) install(ctx ModuleContext) {
- testInstallBase := "/data/local/tests/unrestricted"
+ // TODO: (b/167308193) Switch to /data/local/tests/unrestricted as the default install base.
+ testInstallBase := "/data/local/tmp"
if ctx.RustModule().InVendorOrProduct() {
testInstallBase = "/data/local/tests/vendor"
}
diff --git a/rust/testing.go b/rust/testing.go
index 6ee49a9..32cc823 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -200,15 +200,8 @@
ctx.RegisterModuleType("rust_prebuilt_library", PrebuiltLibraryFactory)
ctx.RegisterModuleType("rust_prebuilt_dylib", PrebuiltDylibFactory)
ctx.RegisterModuleType("rust_prebuilt_rlib", PrebuiltRlibFactory)
- ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
- // rust mutators
- ctx.BottomUp("rust_libraries", LibraryMutator).Parallel()
- ctx.BottomUp("rust_stdlinkage", LibstdMutator).Parallel()
- ctx.BottomUp("rust_begin", BeginMutator).Parallel()
- })
+ ctx.PreDepsMutators(registerPreDepsMutators)
ctx.RegisterParallelSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
ctx.RegisterParallelSingletonType("kythe_rust_extract", kytheExtractRustFactory)
- ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
- ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
- })
+ ctx.PostDepsMutators(registerPostDepsMutators)
}
diff --git a/scripts/run-ckati.sh b/scripts/run-ckati.sh
index b670c8a..70f5a7a 100755
--- a/scripts/run-ckati.sh
+++ b/scripts/run-ckati.sh
@@ -73,12 +73,12 @@
--writable out/ \
-f build/make/core/main.mk \
"${tracing[@]}" \
- ANDROID_JAVA_HOME=prebuilts/jdk/jdk17/linux-x86 \
+ ANDROID_JAVA_HOME=prebuilts/jdk/jdk21/linux-x86 \
ASAN_SYMBOLIZER_PATH=$PWD/prebuilts/clang/host/linux-x86/llvm-binutils-stable/llvm-symbolizer \
BUILD_DATETIME_FILE="$timestamp_file" \
BUILD_HOSTNAME=$(hostname) \
BUILD_USERNAME="$USER" \
- JAVA_HOME=$PWD/prebuilts/jdk/jdk17/linux-x86 \
+ JAVA_HOME=$PWD/prebuilts/jdk/jdk21/linux-x86 \
KATI_PACKAGE_MK_DIR="{$out}/target/product/${target_device}/CONFIG/kati_packaging" \
OUT_DIR="$out" \
PATH="$PWD/prebuilts/build-tools/path/linux-x86:$PWD/${out}/.path" \
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index ee286f6..fcf29c5 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -36,6 +36,7 @@
"blueprint-bootstrap",
"blueprint-microfactory",
"soong-android",
+ "soong-elf",
"soong-finder",
"soong-remoteexec",
"soong-shared",
diff --git a/ui/build/build.go b/ui/build/build.go
index 03d8392..c7319ed 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -22,6 +22,7 @@
"sync"
"text/template"
+ "android/soong/elf"
"android/soong/ui/metrics"
)
@@ -344,6 +345,7 @@
installCleanIfNecessary(ctx, config)
}
runNinjaForBuild(ctx, config)
+ updateBuildIdDir(ctx, config)
}
if what&RunDistActions != 0 {
@@ -351,6 +353,16 @@
}
}
+func updateBuildIdDir(ctx Context, config Config) {
+ ctx.BeginTrace(metrics.RunShutdownTool, "update_build_id_dir")
+ defer ctx.EndTrace()
+
+ symbolsDir := filepath.Join(config.ProductOut(), "symbols")
+ if err := elf.UpdateBuildIdDir(symbolsDir); err != nil {
+ ctx.Printf("failed to update %s/.build-id: %v", symbolsDir, err)
+ }
+}
+
func evaluateWhatToRun(config Config, verboseln func(v ...interface{})) int {
//evaluate what to run
what := 0
diff --git a/ui/build/paths/config.go b/ui/build/paths/config.go
index 81c678d..6c9a1eb 100644
--- a/ui/build/paths/config.go
+++ b/ui/build/paths/config.go
@@ -86,28 +86,28 @@
// This list specifies whether a particular binary from $PATH is allowed to be
// run during the build. For more documentation, see path_interposer.go .
var Configuration = map[string]PathConfig{
- "bash": Allowed,
- "diff": Allowed,
- "dlv": Allowed,
- "expr": Allowed,
- "fuser": Allowed,
- "gcert": Allowed,
- "gcertstatus": Allowed,
- "gcloud": Allowed,
- "git": Allowed,
- "hexdump": Allowed,
- "jar": Allowed,
- "java": Allowed,
- "javap": Allowed,
- "lsof": Allowed,
- "openssl": Allowed,
- "pstree": Allowed,
- "rsync": Allowed,
- "sh": Allowed,
- "stubby": Allowed,
- "tr": Allowed,
- "unzip": Allowed,
- "zip": Allowed,
+ "bash": Allowed,
+ "diff": Allowed,
+ "dlv": Allowed,
+ "expr": Allowed,
+ "fuser": Allowed,
+ "gcert": Allowed,
+ "gcertstatus": Allowed,
+ "gcloud": Allowed,
+ "git": Allowed,
+ "hexdump": Allowed,
+ "jar": Allowed,
+ "java": Allowed,
+ "javap": Allowed,
+ "lsof": Allowed,
+ "openssl": Allowed,
+ "pstree": Allowed,
+ "rsync": Allowed,
+ "sh": Allowed,
+ "stubby": Allowed,
+ "tr": Allowed,
+ "unzip": Allowed,
+ "zip": Allowed,
// Host toolchain is removed. In-tree toolchain should be used instead.
// GCC also can't find cc1 with this implementation.
diff --git a/ui/terminal/format.go b/ui/terminal/format.go
index 241a1dd..01f8b0d 100644
--- a/ui/terminal/format.go
+++ b/ui/terminal/format.go
@@ -23,26 +23,28 @@
)
type formatter struct {
- format string
- quiet bool
- start time.Time
+ colorize bool
+ 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 {
+func newFormatter(colorize bool, format string, quiet bool) formatter {
return formatter{
- format: format,
- quiet: quiet,
- start: time.Now(),
+ colorize: colorize,
+ 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)
+ return fmt.Sprintf("%s %s", s.failedString(), message)
} else if level > status.StatusLvl {
return fmt.Sprintf("%s%s", level.Prefix(), message)
} else if level == status.StatusLvl {
@@ -127,9 +129,9 @@
if result.Error != nil {
targets := strings.Join(result.Outputs, " ")
if s.quiet || result.Command == "" {
- ret = fmt.Sprintf("FAILED: %s\n%s", targets, result.Output)
+ ret = fmt.Sprintf("%s %s\n%s", s.failedString(), targets, result.Output)
} else {
- ret = fmt.Sprintf("FAILED: %s\n%s\n%s", targets, result.Command, result.Output)
+ ret = fmt.Sprintf("%s %s\n%s\n%s", s.failedString(), targets, result.Command, result.Output)
}
} else if result.Output != "" {
ret = result.Output
@@ -141,3 +143,11 @@
return ret
}
+
+func (s formatter) failedString() string {
+ failed := "FAILED:"
+ if s.colorize {
+ failed = ansi.red() + ansi.bold() + failed + ansi.regular()
+ }
+ return failed
+}
diff --git a/ui/terminal/status.go b/ui/terminal/status.go
index 2ad174f..92f2994 100644
--- a/ui/terminal/status.go
+++ b/ui/terminal/status.go
@@ -27,9 +27,10 @@
// statusFormat takes nearly all the same options as NINJA_STATUS.
// %c is currently unsupported.
func NewStatusOutput(w io.Writer, statusFormat string, forceSimpleOutput, quietBuild, forceKeepANSI bool) status.StatusOutput {
- formatter := newFormatter(statusFormat, quietBuild)
+ canUseSmartFormatting := !forceSimpleOutput && isSmartTerminal(w)
+ formatter := newFormatter(canUseSmartFormatting, statusFormat, quietBuild)
- if !forceSimpleOutput && isSmartTerminal(w) {
+ if canUseSmartFormatting {
return NewSmartStatusOutput(w, formatter)
} else {
return NewSimpleStatusOutput(w, formatter, forceKeepANSI)
diff --git a/ui/terminal/status_test.go b/ui/terminal/status_test.go
index 8dd1809..991eca0 100644
--- a/ui/terminal/status_test.go
+++ b/ui/terminal/status_test.go
@@ -58,7 +58,7 @@
{
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",
+ 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\n\x1b[31m\x1b[1mFAILED:\x1b[0m 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",
simple: "[ 33% 1/3] action1\n[ 66% 2/3] action2\nFAILED: f1 f2\ntouch f1 f2\nerror1\nerror2\n[100% 3/3] action3\n",
},
{
@@ -70,7 +70,7 @@
{
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",
+ 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\n\x1b[31m\x1b[1mFAILED:\x1b[0m 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",
simple: "[ 50% 1/2] action1\nstatus\nprint\nFAILED: error\n[100% 2/2] action2\n",
},
{
@@ -362,7 +362,7 @@
stat.Flush()
- w := "\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\nFAILED: \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\nThere was 1 action that completed after the action that failed. See verbose.log.gz for its output.\n"
+ w := "\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\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\nThere was 1 action that completed after the action that failed. See verbose.log.gz for its output.\n"
if g := smart.String(); g != w {
t.Errorf("want:\n%q\ngot:\n%q", w, g)
@@ -407,7 +407,7 @@
stat.Flush()
- w := "\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[ 0% 0/2] action3\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\nFAILED: \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\r\x1b[1m[150% 3/2] action3\x1b[0m\x1b[K\nThere were 2 actions that completed after the action that failed. See verbose.log.gz for their output.\n"
+ w := "\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[ 0% 0/2] action3\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\r\x1b[1m[150% 3/2] action3\x1b[0m\x1b[K\nThere were 2 actions that completed after the action that failed. See verbose.log.gz for their output.\n"
if g := smart.String(); g != w {
t.Errorf("want:\n%q\ngot:\n%q", w, g)
@@ -445,7 +445,7 @@
stat.Flush()
- w := "\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\nFAILED: \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\nFAILED: \nOutput2\n"
+ w := "\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\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput2\n"
if g := smart.String(); g != w {
t.Errorf("want:\n%q\ngot:\n%q", w, g)