diff options
33 files changed, 784 insertions, 154 deletions
diff --git a/android/api_levels.go b/android/api_levels.go index fab5fc7bf..dc17238f3 100644 --- a/android/api_levels.go +++ b/android/api_levels.go @@ -291,6 +291,8 @@ var ApiLevelR = uncheckedFinalApiLevel(30) var ApiLevelUpsideDownCake = uncheckedFinalApiLevel(34) +var ApiLevelVanillaIceCream = uncheckedFinalApiLevel(35) + // ReplaceFinalizedCodenames returns the API level number associated with that API level // if the `raw` input is the codename of an API level has been finalized. // If the input is *not* a finalized codename, the input is returned unmodified. diff --git a/android/buildinfo_prop.go b/android/buildinfo_prop.go index 8288fc555..083f3efcb 100644 --- a/android/buildinfo_prop.go +++ b/android/buildinfo_prop.go @@ -23,7 +23,7 @@ import ( func init() { ctx := InitRegistrationContext - ctx.RegisterParallelSingletonModuleType("buildinfo_prop", buildinfoPropFactory) + ctx.RegisterModuleType("buildinfo_prop", buildinfoPropFactory) } type buildinfoPropProperties struct { @@ -32,7 +32,7 @@ type buildinfoPropProperties struct { } type buildinfoPropModule struct { - SingletonModuleBase + ModuleBase properties buildinfoPropProperties @@ -88,6 +88,10 @@ func shouldAddBuildThumbprint(config Config) bool { } func (p *buildinfoPropModule) GenerateAndroidBuildActions(ctx ModuleContext) { + if ctx.ModuleName() != "buildinfo.prop" || ctx.ModuleDir() != "build/soong" { + ctx.ModuleErrorf("There can only be one buildinfo_prop module in build/soong") + return + } p.outputFilePath = PathForModuleOut(ctx, p.Name()).OutputPath if !ctx.Config().KatiEnabled() { WriteFileRule(ctx, p.outputFilePath, "# no buildinfo.prop if kati is disabled") @@ -111,10 +115,11 @@ func (p *buildinfoPropModule) GenerateAndroidBuildActions(ctx ModuleContext) { cmd.FlagWithArg("--build-id=", config.BuildId()) cmd.FlagWithArg("--build-keys=", config.BuildKeys()) - // shouldn't depend on BuildNumberFile and BuildThumbprintFile to prevent from rebuilding - // on every incremental build. - cmd.FlagWithArg("--build-number-file=", config.BuildNumberFile(ctx).String()) + // Note: depending on BuildNumberFile will cause the build.prop file to be rebuilt + // every build, but that's intentional. + cmd.FlagWithInput("--build-number-file=", config.BuildNumberFile(ctx)) if shouldAddBuildThumbprint(config) { + // In the previous make implementation, a dependency was not added on the thumbprint file cmd.FlagWithArg("--build-thumbprint-file=", config.BuildThumbprintFile(ctx).String()) } @@ -123,8 +128,10 @@ func (p *buildinfoPropModule) GenerateAndroidBuildActions(ctx ModuleContext) { cmd.FlagWithArg("--build-variant=", buildVariant) cmd.FlagForEachArg("--cpu-abis=", config.DeviceAbi()) - // shouldn't depend on BUILD_DATETIME_FILE to prevent from rebuilding on every incremental - // build. + // Technically we should also have a dependency on BUILD_DATETIME_FILE, + // but it can be either an absolute or relative path, which is hard to turn into + // a Path object. So just rely on the BuildNumberFile always changing to cause + // us to rebuild. cmd.FlagWithArg("--date-file=", ctx.Config().Getenv("BUILD_DATETIME_FILE")) if len(config.ProductLocales()) > 0 { @@ -163,12 +170,8 @@ func (p *buildinfoPropModule) GenerateAndroidBuildActions(ctx ModuleContext) { ctx.InstallFile(p.installPath, p.Name(), p.outputFilePath) } -func (f *buildinfoPropModule) GenerateSingletonBuildActions(ctx SingletonContext) { - // does nothing; buildinfo_prop is a singeton because two buildinfo modules don't make sense. -} - func (p *buildinfoPropModule) AndroidMkEntries() []AndroidMkEntries { - return []AndroidMkEntries{AndroidMkEntries{ + return []AndroidMkEntries{{ Class: "ETC", OutputFile: OptionalPathForPath(p.outputFilePath), ExtraEntries: []AndroidMkExtraEntriesFunc{ @@ -184,7 +187,7 @@ func (p *buildinfoPropModule) AndroidMkEntries() []AndroidMkEntries { // buildinfo_prop module generates a build.prop file, which contains a set of common // system/build.prop properties, such as ro.build.version.*. Not all properties are implemented; // currently this module is only for microdroid. -func buildinfoPropFactory() SingletonModule { +func buildinfoPropFactory() Module { module := &buildinfoPropModule{} module.AddProperties(&module.properties) InitAndroidModule(module) diff --git a/android/config.go b/android/config.go index 5a6d40f31..a18cb8be8 100644 --- a/android/config.go +++ b/android/config.go @@ -370,6 +370,7 @@ func loadFromConfigFile(configurable *ProductVariables, filename string) error { } else { // Make a decoder for it jsonDecoder := json.NewDecoder(configFileReader) + jsonDecoder.DisallowUnknownFields() err = jsonDecoder.Decode(configurable) if err != nil { return fmt.Errorf("config file: %s did not parse correctly: %s", filename, err.Error()) @@ -1333,10 +1334,6 @@ func (c *config) SourceRootDirs() []string { return c.productVariables.SourceRootDirs } -func (c *config) IncludeTags() []string { - return c.productVariables.IncludeTags -} - func (c *config) HostStaticBinaries() bool { return Bool(c.productVariables.HostStaticBinaries) } @@ -1912,10 +1909,10 @@ func (c *deviceConfig) HostFakeSnapshotEnabled() bool { } func (c *deviceConfig) ShippingApiLevel() ApiLevel { - if c.config.productVariables.ShippingApiLevel == nil { + if c.config.productVariables.Shipping_api_level == nil { return NoneApiLevel } - apiLevel, _ := strconv.Atoi(*c.config.productVariables.ShippingApiLevel) + apiLevel, _ := strconv.Atoi(*c.config.productVariables.Shipping_api_level) return uncheckedFinalApiLevel(apiLevel) } diff --git a/android/gen_notice.go b/android/gen_notice.go index 6815f6467..9adde9e9b 100644 --- a/android/gen_notice.go +++ b/android/gen_notice.go @@ -176,6 +176,7 @@ func (m *genNoticeModule) GenerateAndroidBuildActions(ctx ModuleContext) { } out := m.getStem() + m.getSuffix() m.output = PathForModuleOut(ctx, out).OutputPath + ctx.SetOutputFiles(Paths{m.output}, "") } func GenNoticeFactory() Module { @@ -193,16 +194,6 @@ func GenNoticeFactory() Module { return module } -var _ OutputFileProducer = (*genNoticeModule)(nil) - -// Implements OutputFileProducer -func (m *genNoticeModule) OutputFiles(tag string) (Paths, error) { - if tag == "" { - return Paths{m.output}, nil - } - return nil, fmt.Errorf("unrecognized tag %q", tag) -} - var _ AndroidMkEntriesProvider = (*genNoticeModule)(nil) // Implements AndroidMkEntriesProvider diff --git a/android/module_context.go b/android/module_context.go index bc089114f..591e270f0 100644 --- a/android/module_context.go +++ b/android/module_context.go @@ -718,6 +718,9 @@ func (m *moduleContext) SetOutputFiles(outputFiles Paths, tag string) { } m.module.base().outputFiles.DefaultOutputFiles = outputFiles } else { + if m.module.base().outputFiles.TaggedOutputFiles == nil { + m.module.base().outputFiles.TaggedOutputFiles = make(map[string]Paths) + } if _, exists := m.module.base().outputFiles.TaggedOutputFiles[tag]; exists { m.ModuleErrorf("Module %s OutputFiles at tag %s cannot be overwritten", m.ModuleName(), tag) } else { diff --git a/android/register.go b/android/register.go index aeb3b4c1b..eb6a35e05 100644 --- a/android/register.go +++ b/android/register.go @@ -156,7 +156,6 @@ type Context struct { func NewContext(config Config) *Context { ctx := &Context{blueprint.NewContext(), config} ctx.SetSrcDir(absSrcDir) - ctx.AddIncludeTags(config.IncludeTags()...) ctx.AddSourceRootDirs(config.SourceRootDirs()...) return ctx } diff --git a/android/test_config.go b/android/test_config.go index a15343adb..f2510387f 100644 --- a/android/test_config.go +++ b/android/test_config.go @@ -50,7 +50,7 @@ func TestConfig(buildDir string, env map[string]string, bp string, fs map[string AAPTCharacteristics: stringPtr("nosdcard"), AAPTPrebuiltDPI: []string{"xhdpi", "xxhdpi"}, UncompressPrivAppDex: boolPtr(true), - ShippingApiLevel: stringPtr("30"), + Shipping_api_level: stringPtr("30"), }, outDir: buildDir, diff --git a/android/variable.go b/android/variable.go index 9a9556314..16338166b 100644 --- a/android/variable.go +++ b/android/variable.go @@ -55,6 +55,10 @@ type variableProperties struct { Base_dir *string } + Shipping_api_level struct { + Cflags []string + } + // unbundled_build is a catch-all property to annotate modules that don't build in one or // more unbundled branches, usually due to dependencies missing from the manifest. Unbundled_build struct { @@ -440,7 +444,7 @@ type ProductVariables struct { PrebuiltHiddenApiDir *string `json:",omitempty"` - ShippingApiLevel *string `json:",omitempty"` + Shipping_api_level *string `json:",omitempty"` BuildBrokenPluginValidation []string `json:",omitempty"` BuildBrokenClangAsFlags bool `json:",omitempty"` @@ -472,7 +476,6 @@ type ProductVariables struct { IgnorePrefer32OnDevice bool `json:",omitempty"` - IncludeTags []string `json:",omitempty"` SourceRootDirs []string `json:",omitempty"` AfdoProfiles []string `json:",omitempty"` diff --git a/apex/bootclasspath_fragment_test.go b/apex/bootclasspath_fragment_test.go index 778c20a56..af9123e70 100644 --- a/apex/bootclasspath_fragment_test.go +++ b/apex/bootclasspath_fragment_test.go @@ -197,6 +197,12 @@ func TestBootclasspathFragmentInArtApex(t *testing.T) { updatable: false, } + override_apex { + name: "com.mycompany.android.art", + base: "com.android.art", + min_sdk_version: "33", // mycompany overrides the min_sdk_version + } + apex_key { name: "com.android.art.key", public_key: "testkey.avbpubkey", @@ -325,6 +331,26 @@ func TestBootclasspathFragmentInArtApex(t *testing.T) { checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo") }) + t.Run("boot image files from source of override apex", func(t *testing.T) { + result := android.GroupFixturePreparers( + commonPreparer, + + // Configure some libraries in the art bootclasspath_fragment that match the source + // bootclasspath_fragment's contents property. + java.FixtureConfigureBootJars("com.android.art:foo", "com.android.art:bar"), + dexpreopt.FixtureSetTestOnlyArtBootImageJars("com.android.art:foo", "com.android.art:bar"), + addSource("foo", "bar"), + java.FixtureSetBootImageInstallDirOnDevice("art", "apex/com.android.art/javalib"), + ).RunTest(t) + + ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.mycompany.android.art_com.mycompany.android.art", []string{ + "etc/boot-image.prof", + "etc/classpaths/bootclasspath.pb", + "javalib/bar.jar", + "javalib/foo.jar", + }) + }) + t.Run("generate boot image profile even if dexpreopt is disabled", func(t *testing.T) { result := android.GroupFixturePreparers( commonPreparer, diff --git a/bin/Android.bp b/bin/Android.bp new file mode 100644 index 000000000..4d6d91102 --- /dev/null +++ b/bin/Android.bp @@ -0,0 +1,26 @@ +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], + default_team: "trendy_team_build", +} + +filegroup { + name: "run_tool_with_logging_script", + visibility: [ + "//build/soong/tests:__subpackages__", + ], + srcs: ["run_tool_with_logging"], +} diff --git a/bin/run_tool_with_logging b/bin/run_tool_with_logging new file mode 100755 index 000000000..2b2c8d81c --- /dev/null +++ b/bin/run_tool_with_logging @@ -0,0 +1,55 @@ +#!/bin/bash + +# Copyright (C) 2024 The Android Open Source Project +# +# 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. + +# Run commands in a subshell for us to handle forced terminations with a trap +# handler. +( +tool_tag="$1" +shift +tool_binary="$1" +shift + +# If the logger is not configured, run the original command and return. +if [[ -z "${ANDROID_TOOL_LOGGER}" ]]; then + "${tool_binary}" "${@}" + exit $? +fi + +# Otherwise, run the original command and call the logger when done. +start_time=$(date +%s.%N) +logger=${ANDROID_TOOL_LOGGER} + +# Install a trap to call the logger even when the process terminates abnormally. +# The logger is run in the background and its output suppressed to avoid +# interference with the user flow. +trap ' +exit_code=$?; +# Remove the trap to prevent duplicate log. +trap - EXIT; +"${logger}" \ + --tool_tag="${tool_tag}" \ + --start_timestamp="${start_time}" \ + --end_timestamp="$(date +%s.%N)" \ + --tool_args="$*" \ + --exit_code="${exit_code}" \ + ${ANDROID_TOOL_LOGGER_EXTRA_ARGS} \ + > /dev/null 2>&1 & +exit ${exit_code} +' SIGINT SIGTERM SIGQUIT EXIT + +# Run the original command. +"${tool_binary}" "${@}" +) diff --git a/cc/compiler.go b/cc/compiler.go index 83080fd72..ede6a5d0a 100644 --- a/cc/compiler.go +++ b/cc/compiler.go @@ -101,7 +101,7 @@ type BaseCompilerProperties struct { Generated_headers []string `android:"arch_variant,variant_prepend"` // pass -frtti instead of -fno-rtti - Rtti *bool + Rtti *bool `android:"arch_variant"` // C standard version to use. Can be a specific version (such as "gnu11"), // "experimental" (which will use draft versions like C1x when available), diff --git a/cmd/release_config/release_config_lib/release_configs.go b/cmd/release_config/release_config_lib/release_configs.go index 02eedc822..052cde898 100644 --- a/cmd/release_config/release_config_lib/release_configs.go +++ b/cmd/release_config/release_config_lib/release_configs.go @@ -87,16 +87,15 @@ func (configs *ReleaseConfigs) WriteInheritanceGraph(outFile string) error { data := []string{} usedAliases := make(map[string]bool) priorStages := make(map[string][]string) - rankedStageNames := make(map[string]bool) for _, config := range configs.ReleaseConfigs { + if config.Name == "root" { + continue + } var fillColor string inherits := []string{} for _, inherit := range config.InheritNames { if inherit == "root" { - // Only show "root" if we have no other inheritance. - if len(config.InheritNames) > 1 { - continue - } + continue } data = append(data, fmt.Sprintf(`"%s" -> "%s"`, config.Name, inherit)) inherits = append(inherits, inherit) @@ -113,14 +112,9 @@ func (configs *ReleaseConfigs) WriteInheritanceGraph(outFile string) error { } // Add links for all of the advancement progressions. for priorStage := range config.PriorStagesMap { - stageName := config.Name - if len(config.OtherNames) > 0 { - stageName = config.OtherNames[0] - } data = append(data, fmt.Sprintf(`"%s" -> "%s" [ style=dashed color="#81c995" ]`, - priorStage, stageName)) - priorStages[stageName] = append(priorStages[stageName], priorStage) - rankedStageNames[stageName] = true + priorStage, config.Name)) + priorStages[config.Name] = append(priorStages[config.Name], priorStage) } label := config.Name if len(inherits) > 0 { @@ -129,16 +123,24 @@ func (configs *ReleaseConfigs) WriteInheritanceGraph(outFile string) error { if len(config.OtherNames) > 0 { label += "\\nother names: " + strings.Join(config.OtherNames, " ") } - // The active release config has a light blue fill. - if config.Name == *configs.Artifact.ReleaseConfig.Name { + switch config.Name { + case *configs.Artifact.ReleaseConfig.Name: + // The active release config has a light blue fill. fillColor = `fillcolor="#d2e3fc" ` + case "trunk", "trunk_staging": + // Certain workflow stages have a light green fill. + fillColor = `fillcolor="#ceead6" ` + default: + // Look for "next" and "*_next", make them light green as well. + for _, n := range config.OtherNames { + if n == "next" || strings.HasSuffix(n, "_next") { + fillColor = `fillcolor="#ceead6" ` + } + } } data = append(data, fmt.Sprintf(`"%s" [ label="%s" %s]`, config.Name, label, fillColor)) } - if len(rankedStageNames) > 0 { - data = append(data, fmt.Sprintf("subgraph {rank=same %s}", strings.Join(SortedMapKeys(rankedStageNames), " "))) - } slices.Sort(data) data = append([]string{ "digraph {", diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go index 4490dd2af..3dac8bdae 100644 --- a/cmd/soong_build/main.go +++ b/cmd/soong_build/main.go @@ -98,7 +98,6 @@ func newContext(configuration android.Config) *android.Context { ctx := android.NewContext(configuration) ctx.SetNameInterface(newNameResolver(configuration)) ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies()) - ctx.AddIncludeTags(configuration.IncludeTags()...) ctx.AddSourceRootDirs(configuration.SourceRootDirs()...) return ctx } diff --git a/filesystem/avb_gen_vbmeta_image.go b/filesystem/avb_gen_vbmeta_image.go index 985f0eac2..a7fd7829e 100644 --- a/filesystem/avb_gen_vbmeta_image.go +++ b/filesystem/avb_gen_vbmeta_image.go @@ -81,6 +81,8 @@ func (a *avbGenVbmetaImage) GenerateAndroidBuildActions(ctx android.ModuleContex a.output = android.PathForModuleOut(ctx, a.installFileName()).OutputPath cmd.FlagWithOutput("--output_vbmeta_image ", a.output) builder.Build("avbGenVbmetaImage", fmt.Sprintf("avbGenVbmetaImage %s", ctx.ModuleName())) + + ctx.SetOutputFiles([]android.Path{a.output}, "") } var _ android.AndroidMkEntriesProvider = (*avbGenVbmetaImage)(nil) @@ -99,16 +101,6 @@ func (a *avbGenVbmetaImage) AndroidMkEntries() []android.AndroidMkEntries { }} } -var _ android.OutputFileProducer = (*avbGenVbmetaImage)(nil) - -// Implements android.OutputFileProducer -func (a *avbGenVbmetaImage) OutputFiles(tag string) (android.Paths, error) { - if tag == "" { - return []android.Path{a.output}, nil - } - return nil, fmt.Errorf("unsupported module reference tag %q", tag) -} - type avbGenVbmetaImageDefaults struct { android.ModuleBase android.DefaultsModuleBase diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go index 352b45178..e796ab9b3 100644 --- a/filesystem/bootimg.go +++ b/filesystem/bootimg.go @@ -123,6 +123,8 @@ func (b *bootimg) GenerateAndroidBuildActions(ctx android.ModuleContext) { b.installDir = android.PathForModuleInstall(ctx, "etc") ctx.InstallFile(b.installDir, b.installFileName(), b.output) + + ctx.SetOutputFiles([]android.Path{b.output}, "") } func (b *bootimg) buildBootImage(ctx android.ModuleContext, vendor bool) android.OutputPath { @@ -292,13 +294,3 @@ func (b *bootimg) SignedOutputPath() android.Path { } return nil } - -var _ android.OutputFileProducer = (*bootimg)(nil) - -// Implements android.OutputFileProducer -func (b *bootimg) OutputFiles(tag string) (android.Paths, error) { - if tag == "" { - return []android.Path{b.output}, nil - } - return nil, fmt.Errorf("unsupported module reference tag %q", tag) -} diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go index d2572c269..c889dd61c 100644 --- a/filesystem/filesystem.go +++ b/filesystem/filesystem.go @@ -221,6 +221,8 @@ func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) { f.installDir = android.PathForModuleInstall(ctx, "etc") ctx.InstallFile(f.installDir, f.installFileName(), f.output) + + ctx.SetOutputFiles([]android.Path{f.output}, "") } func validatePartitionType(ctx android.ModuleContext, p partition) { @@ -561,16 +563,6 @@ func (f *filesystem) AndroidMkEntries() []android.AndroidMkEntries { }} } -var _ android.OutputFileProducer = (*filesystem)(nil) - -// Implements android.OutputFileProducer -func (f *filesystem) OutputFiles(tag string) (android.Paths, error) { - if tag == "" { - return []android.Path{f.output}, nil - } - return nil, fmt.Errorf("unsupported module reference tag %q", tag) -} - // Filesystem is the public interface for the filesystem struct. Currently, it's only for the apex // package to have access to the output file. type Filesystem interface { diff --git a/filesystem/logical_partition.go b/filesystem/logical_partition.go index e2f7d7bdf..e483fe472 100644 --- a/filesystem/logical_partition.go +++ b/filesystem/logical_partition.go @@ -185,6 +185,8 @@ func (l *logicalPartition) GenerateAndroidBuildActions(ctx android.ModuleContext l.installDir = android.PathForModuleInstall(ctx, "etc") ctx.InstallFile(l.installDir, l.installFileName(), l.output) + + ctx.SetOutputFiles([]android.Path{l.output}, "") } // Add a rule that converts the filesystem for the given partition to the given rule builder. The @@ -231,13 +233,3 @@ func (l *logicalPartition) OutputPath() android.Path { func (l *logicalPartition) SignedOutputPath() android.Path { return nil // logical partition is not signed by itself } - -var _ android.OutputFileProducer = (*logicalPartition)(nil) - -// Implements android.OutputFileProducer -func (l *logicalPartition) OutputFiles(tag string) (android.Paths, error) { - if tag == "" { - return []android.Path{l.output}, nil - } - return nil, fmt.Errorf("unsupported module reference tag %q", tag) -} diff --git a/filesystem/raw_binary.go b/filesystem/raw_binary.go index 1544ea788..ad36c2935 100644 --- a/filesystem/raw_binary.go +++ b/filesystem/raw_binary.go @@ -15,8 +15,6 @@ package filesystem import ( - "fmt" - "github.com/google/blueprint" "github.com/google/blueprint/proptools" @@ -88,6 +86,8 @@ func (r *rawBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) { r.output = outputFile r.installDir = android.PathForModuleInstall(ctx, "etc") ctx.InstallFile(r.installDir, r.installFileName(), r.output) + + ctx.SetOutputFiles([]android.Path{r.output}, "") } var _ android.AndroidMkEntriesProvider = (*rawBinary)(nil) @@ -109,13 +109,3 @@ func (r *rawBinary) OutputPath() android.Path { func (r *rawBinary) SignedOutputPath() android.Path { return nil } - -var _ android.OutputFileProducer = (*rawBinary)(nil) - -// Implements android.OutputFileProducer -func (r *rawBinary) OutputFiles(tag string) (android.Paths, error) { - if tag == "" { - return []android.Path{r.output}, nil - } - return nil, fmt.Errorf("unsupported module reference tag %q", tag) -} diff --git a/filesystem/vbmeta.go b/filesystem/vbmeta.go index 43a2f3712..0c6e7f428 100644 --- a/filesystem/vbmeta.go +++ b/filesystem/vbmeta.go @@ -211,6 +211,8 @@ func (v *vbmeta) GenerateAndroidBuildActions(ctx android.ModuleContext) { v.installDir = android.PathForModuleInstall(ctx, "etc") ctx.InstallFile(v.installDir, v.installFileName(), v.output) + + ctx.SetOutputFiles([]android.Path{v.output}, "") } // Returns the embedded shell command that prints the rollback index @@ -288,13 +290,3 @@ func (v *vbmeta) OutputPath() android.Path { func (v *vbmeta) SignedOutputPath() android.Path { return v.OutputPath() // vbmeta is always signed } - -var _ android.OutputFileProducer = (*vbmeta)(nil) - -// Implements android.OutputFileProducer -func (v *vbmeta) OutputFiles(tag string) (android.Paths, error) { - if tag == "" { - return []android.Path{v.output}, nil - } - return nil, fmt.Errorf("unsupported module reference tag %q", tag) -} diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go index 4d3d794d8..16209b72e 100644 --- a/java/bootclasspath_fragment.go +++ b/java/bootclasspath_fragment.go @@ -524,10 +524,16 @@ func (b *BootclasspathFragmentModule) getProfileProviderApex(ctx android.BaseMod } // Bootclasspath fragment modules that are for the platform do not produce boot related files. - apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider) - for _, apex := range apexInfo.InApexVariants { - if isProfileProviderApex(ctx, apex) { - return apex + apexInfos, _ := android.ModuleProvider(ctx, android.AllApexInfoProvider) + if apexInfos == nil { + return "" + } + + for _, apexInfo := range apexInfos.ApexInfos { + for _, apex := range apexInfo.InApexVariants { + if isProfileProviderApex(ctx, apex) { + return apex + } } } diff --git a/java/droidstubs.go b/java/droidstubs.go index 5ca6c256f..b32b754d3 100644 --- a/java/droidstubs.go +++ b/java/droidstubs.go @@ -1054,8 +1054,7 @@ func (d *Droidstubs) everythingOptionalCmd(ctx android.ModuleContext, cmd *andro } if !treatDocumentationIssuesAsErrors { - // Treat documentation issues as warnings, but error when new. - cmd.Flag("--error-when-new-category").Flag("Documentation") + treatDocumentationIssuesAsWarningErrorWhenNew(cmd) } // Add "check released" options. (Detect incompatible API changes from the last public release) @@ -1083,6 +1082,22 @@ func (d *Droidstubs) everythingOptionalCmd(ctx android.ModuleContext, cmd *andro } } +// HIDDEN_DOCUMENTATION_ISSUES is the set of documentation related issues that should always be +// hidden as they are very noisy and provide little value. +var HIDDEN_DOCUMENTATION_ISSUES = []string{ + "Deprecated", + "IntDef", + "Nullable", +} + +func treatDocumentationIssuesAsWarningErrorWhenNew(cmd *android.RuleBuilderCommand) { + // Treat documentation issues as warnings, but error when new. + cmd.Flag("--error-when-new-category").Flag("Documentation") + + // Hide some documentation issues that generated a lot of noise for little benefit. + cmd.FlagForEachArg("--hide ", HIDDEN_DOCUMENTATION_ISSUES) +} + // Sandbox rule for generating exportable stubs and other artifacts func (d *Droidstubs) exportableStubCmd(ctx android.ModuleContext, params stubsCommandConfigParams) { optionalCmdParams := stubsCommandParams{ @@ -1154,7 +1169,7 @@ func (d *Droidstubs) optionalStubCmd(ctx android.ModuleContext, params stubsComm } // Treat documentation issues as warnings, but error when new. - cmd.Flag("--error-when-new-category").Flag("Documentation") + treatDocumentationIssuesAsWarningErrorWhenNew(cmd) if params.stubConfig.generateStubs { rule.Command(). diff --git a/java/java.go b/java/java.go index ccccbacdb..08fb6782c 100644 --- a/java/java.go +++ b/java/java.go @@ -2180,7 +2180,7 @@ func (al *ApiLibrary) DepsMutator(ctx android.BottomUpMutatorContext) { // Map where key is the api scope name and value is the int value // representing the order of the api scope, narrowest to the widest -var scopeOrderMap = allApiScopes.MapToIndex( +var scopeOrderMap = AllApiScopes.MapToIndex( func(s *apiScope) string { return s.name }) func (al *ApiLibrary) sortApiFilesByApiScope(ctx android.ModuleContext, srcFilesInfo []JavaApiImportInfo) []JavaApiImportInfo { diff --git a/java/sdk_library.go b/java/sdk_library.go index 72eb6e346..6c1a38d7d 100644 --- a/java/sdk_library.go +++ b/java/sdk_library.go @@ -324,6 +324,16 @@ func (scopes apiScopes) MapToIndex(accessor func(*apiScope) string) map[string]i return ret } +func (scopes apiScopes) ConvertStubsLibraryExportableToEverything(name string) string { + for _, scope := range scopes { + if strings.HasSuffix(name, scope.exportableStubsLibraryModuleNameSuffix()) { + return strings.TrimSuffix(name, scope.exportableStubsLibraryModuleNameSuffix()) + + scope.stubsLibraryModuleNameSuffix() + } + } + return name +} + var ( scopeByName = make(map[string]*apiScope) allScopeNames []string @@ -418,7 +428,7 @@ var ( }, kind: android.SdkSystemServer, }) - allApiScopes = apiScopes{ + AllApiScopes = apiScopes{ apiScopePublic, apiScopeSystem, apiScopeTest, @@ -1204,7 +1214,7 @@ func (c *commonToSdkLibraryAndImport) selectScopePaths(ctx android.BaseModuleCon paths := c.findClosestScopePath(apiScope) if paths == nil { var scopes []string - for _, s := range allApiScopes { + for _, s := range AllApiScopes { if c.findScopePaths(s) != nil { scopes = append(scopes, s.name) } @@ -1421,7 +1431,7 @@ func (module *SdkLibrary) getGeneratedApiScopes(ctx android.EarlyModuleContext) // Check to see if any scopes have been explicitly enabled. If any have then all // must be. anyScopesExplicitlyEnabled := false - for _, scope := range allApiScopes { + for _, scope := range AllApiScopes { scopeProperties := module.scopeToProperties[scope] if scopeProperties.Enabled != nil { anyScopesExplicitlyEnabled = true @@ -1431,7 +1441,7 @@ func (module *SdkLibrary) getGeneratedApiScopes(ctx android.EarlyModuleContext) var generatedScopes apiScopes enabledScopes := make(map[*apiScope]struct{}) - for _, scope := range allApiScopes { + for _, scope := range AllApiScopes { scopeProperties := module.scopeToProperties[scope] // If any scopes are explicitly enabled then ignore the legacy enabled status. // This is to ensure that any new usages of this module type do not rely on legacy @@ -1451,7 +1461,7 @@ func (module *SdkLibrary) getGeneratedApiScopes(ctx android.EarlyModuleContext) // Now check to make sure that any scope that is extended by an enabled scope is also // enabled. - for _, scope := range allApiScopes { + for _, scope := range AllApiScopes { if _, ok := enabledScopes[scope]; ok { extends := scope.extends if extends != nil { @@ -2580,7 +2590,7 @@ func SdkLibraryFactory() android.Module { // Initialize the map from scope to scope specific properties. scopeToProperties := make(map[*apiScope]*ApiScopeProperties) - for _, scope := range allApiScopes { + for _, scope := range AllApiScopes { scopeToProperties[scope] = scope.scopeSpecificProperties(module) } module.scopeToProperties = scopeToProperties @@ -2697,7 +2707,7 @@ var allScopeStructType = createAllScopePropertiesStructType() // Dynamically create a structure type for each apiscope in allApiScopes. func createAllScopePropertiesStructType() reflect.Type { var fields []reflect.StructField - for _, apiScope := range allApiScopes { + for _, apiScope := range AllApiScopes { field := reflect.StructField{ Name: apiScope.fieldName, Type: reflect.TypeOf(sdkLibraryScopeProperties{}), @@ -2715,7 +2725,7 @@ func createPropertiesInstance() (interface{}, map[*apiScope]*sdkLibraryScopeProp allScopePropertiesStruct := allScopePropertiesPtr.Elem() scopeProperties := make(map[*apiScope]*sdkLibraryScopeProperties) - for _, apiScope := range allApiScopes { + for _, apiScope := range AllApiScopes { field := allScopePropertiesStruct.FieldByName(apiScope.fieldName) scopeProperties[apiScope] = field.Addr().Interface().(*sdkLibraryScopeProperties) } @@ -3597,7 +3607,7 @@ func (s *sdkLibrarySdkMemberProperties) PopulateFromVariant(ctx android.SdkMembe s.Stem = sdk.distStem() s.Scopes = make(map[*apiScope]*scopeProperties) - for _, apiScope := range allApiScopes { + for _, apiScope := range AllApiScopes { paths := sdk.findScopePaths(apiScope) if paths == nil { continue @@ -3659,7 +3669,7 @@ func (s *sdkLibrarySdkMemberProperties) AddToPropertySet(ctx android.SdkMemberCo stem := s.Stem - for _, apiScope := range allApiScopes { + for _, apiScope := range AllApiScopes { if properties, ok := s.Scopes[apiScope]; ok { scopeSet := propertySet.AddPropertySet(apiScope.propertyName) diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go index f9d49d98d..4894210d4 100644 --- a/sdk/sdk_test.go +++ b/sdk/sdk_test.go @@ -519,4 +519,140 @@ java_sdk_library_import { ) }) + t.Run("test replacing exportable module", func(t *testing.T) { + result := android.GroupFixturePreparers( + prepareForSdkTestWithJava, + java.PrepareForTestWithJavaDefaultModules, + java.PrepareForTestWithJavaSdkLibraryFiles, + java.FixtureWithLastReleaseApis("mysdklibrary", "anothersdklibrary"), + android.FixtureWithRootAndroidBp(` + sdk { + name: "mysdk", + bootclasspath_fragments: ["mybootclasspathfragment"], + } + + bootclasspath_fragment { + name: "mybootclasspathfragment", + apex_available: ["myapex"], + contents: ["mysdklibrary"], + hidden_api: { + split_packages: ["*"], + }, + core_platform_api: { + stub_libs: [ + "anothersdklibrary.stubs.exportable", + ], + }, + api: { + stub_libs: [ + "anothersdklibrary", + ], + }, + } + + java_sdk_library { + name: "mysdklibrary", + srcs: ["Test.java"], + compile_dex: true, + min_sdk_version: "S", + public: {enabled: true}, + permitted_packages: ["mysdklibrary"], + } + + java_sdk_library { + name: "anothersdklibrary", + srcs: ["Test.java"], + compile_dex: true, + min_sdk_version: "S", + public: {enabled: true}, + system: {enabled: true}, + module_lib: {enabled: true}, + } + `), + android.FixtureMergeEnv(map[string]string{ + "SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE": "S", + }), + android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { + variables.BuildFlags = map[string]string{ + "RELEASE_HIDDEN_API_EXPORTABLE_STUBS": "true", + } + variables.Platform_version_active_codenames = []string{"UpsideDownCake", "Tiramisu", "S-V2"} + }), + ).RunTest(t) + + CheckSnapshot(t, result, "mysdk", "", + checkAndroidBpContents(` +// This is auto-generated. DO NOT EDIT. + +prebuilt_bootclasspath_fragment { + name: "mybootclasspathfragment", + prefer: false, + visibility: ["//visibility:public"], + apex_available: ["myapex"], + contents: ["mysdklibrary"], + api: { + stub_libs: ["anothersdklibrary"], + }, + core_platform_api: { + stub_libs: ["anothersdklibrary.stubs"], + }, + hidden_api: { + annotation_flags: "hiddenapi/annotation-flags.csv", + metadata: "hiddenapi/metadata.csv", + index: "hiddenapi/index.csv", + stub_flags: "hiddenapi/stub-flags.csv", + all_flags: "hiddenapi/all-flags.csv", + }, +} + +java_sdk_library_import { + name: "mysdklibrary", + prefer: false, + visibility: ["//visibility:public"], + apex_available: ["//apex_available:platform"], + shared_library: true, + compile_dex: true, + permitted_packages: ["mysdklibrary"], + public: { + jars: ["sdk_library/public/mysdklibrary-stubs.jar"], + stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"], + current_api: "sdk_library/public/mysdklibrary.txt", + removed_api: "sdk_library/public/mysdklibrary-removed.txt", + sdk_version: "current", + }, +} + +java_sdk_library_import { + name: "anothersdklibrary", + prefer: false, + visibility: ["//visibility:public"], + apex_available: ["//apex_available:platform"], + shared_library: true, + compile_dex: true, + public: { + jars: ["sdk_library/public/anothersdklibrary-stubs.jar"], + stub_srcs: ["sdk_library/public/anothersdklibrary_stub_sources"], + current_api: "sdk_library/public/anothersdklibrary.txt", + removed_api: "sdk_library/public/anothersdklibrary-removed.txt", + sdk_version: "current", + }, + system: { + jars: ["sdk_library/system/anothersdklibrary-stubs.jar"], + stub_srcs: ["sdk_library/system/anothersdklibrary_stub_sources"], + current_api: "sdk_library/system/anothersdklibrary.txt", + removed_api: "sdk_library/system/anothersdklibrary-removed.txt", + sdk_version: "system_current", + }, + module_lib: { + jars: ["sdk_library/module-lib/anothersdklibrary-stubs.jar"], + stub_srcs: ["sdk_library/module-lib/anothersdklibrary_stub_sources"], + current_api: "sdk_library/module-lib/anothersdklibrary.txt", + removed_api: "sdk_library/module-lib/anothersdklibrary-removed.txt", + sdk_version: "module_current", + }, +} +`), + ) + }) + } diff --git a/sdk/update.go b/sdk/update.go index afecf9fe2..0a97fd9ee 100644 --- a/sdk/update.go +++ b/sdk/update.go @@ -480,6 +480,12 @@ be unnecessary as every module in the sdk already has its own licenses property. // Transform the module module to make it suitable for use in the snapshot. module = transformModule(module, snapshotTransformer) module = transformModule(module, emptyClasspathContentsTransformation{}) + + targetApiLevel, err := android.ApiLevelFromUserWithConfig(ctx.Config(), s.targetBuildRelease(ctx).name) + if err == nil && targetApiLevel.LessThan(android.ApiLevelVanillaIceCream) { + module = transformModule(module, replaceExportablePropertiesTransformer{}) + } + if module != nil { bpFile.AddModule(module) } @@ -804,6 +810,50 @@ func (t pruneEmptySetTransformer) transformPropertySetAfterContents(_ string, pr } } +type replaceExportablePropertiesTransformer struct { + identityTransformation +} + +var _ bpTransformer = (*replaceExportablePropertiesTransformer)(nil) + +func handleExportableProperties[T any](value T) any { + switch v := any(value).(type) { + case string: + return java.AllApiScopes.ConvertStubsLibraryExportableToEverything(v) + case *bpPropertySet: + v.properties = handleExportableProperties(v.properties).(map[string]interface{}) + return v + case []string: + result := make([]string, len(v)) + for i, elem := range v { + result[i] = handleExportableProperties(elem).(string) + } + return result + case []any: + result := make([]any, len(v)) + for i, elem := range v { + result[i] = handleExportableProperties(elem) + } + return result + case map[string]any: + result := make(map[string]any) + for k, val := range v { + result[k] = handleExportableProperties(val) + } + return result + default: + return value + } +} + +func (t replaceExportablePropertiesTransformer) transformPropertySetAfterContents(name string, propertySet *bpPropertySet, tag android.BpPropertyTag) (*bpPropertySet, android.BpPropertyTag) { + if name == "name" { + return propertySet, tag + } + propertySet.properties = handleExportableProperties(propertySet.properties).(map[string]interface{}) + return propertySet, tag +} + func generateBpContents(bpFile *bpFile) string { contents := &generatedContents{} contents.IndentedPrintf("// This is auto-generated. DO NOT EDIT.\n") diff --git a/sh/sh_binary.go b/sh/sh_binary.go index 3cbbc4547..3a4adc6f3 100644 --- a/sh/sh_binary.go +++ b/sh/sh_binary.go @@ -15,7 +15,6 @@ package sh import ( - "fmt" "path/filepath" "strings" @@ -188,15 +187,6 @@ func (s *ShBinary) OutputFile() android.OutputPath { return s.outputFilePath } -func (s *ShBinary) OutputFiles(tag string) (android.Paths, error) { - switch tag { - case "": - return android.Paths{s.outputFilePath}, nil - default: - return nil, fmt.Errorf("unsupported module reference tag %q", tag) - } -} - func (s *ShBinary) SubDir() string { return proptools.String(s.properties.Sub_dir) } @@ -271,6 +261,8 @@ func (s *ShBinary) generateAndroidBuildActions(ctx android.ModuleContext) { Input: s.sourceFilePath, }) android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: []string{s.sourceFilePath.String()}}) + + ctx.SetOutputFiles(android.Paths{s.outputFilePath}, "") } func (s *ShBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) { diff --git a/tests/Android.bp b/tests/Android.bp new file mode 100644 index 000000000..458cf4b0c --- /dev/null +++ b/tests/Android.bp @@ -0,0 +1,34 @@ +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], + default_team: "trendy_team_build", +} + +python_test_host { + name: "run_tool_with_logging_test", + main: "run_tool_with_logging_test.py", + pkg_path: "testdata", + srcs: [ + "run_tool_with_logging_test.py", + ], + test_options: { + unit_test: true, + }, + data: [ + ":run_tool_with_logging_script", + ":tool_event_logger", + ], +} diff --git a/tests/run_tool_with_logging_test.py b/tests/run_tool_with_logging_test.py new file mode 100644 index 000000000..57a6d6296 --- /dev/null +++ b/tests/run_tool_with_logging_test.py @@ -0,0 +1,337 @@ +# Copyright 2024 The Android Open Source Project +# +# 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. + +import dataclasses +import glob +from importlib import resources +import logging +import os +from pathlib import Path +import re +import shutil +import signal +import stat +import subprocess +import sys +import tempfile +import textwrap +import time +import unittest + +EXII_RETURN_CODE = 0 +INTERRUPTED_RETURN_CODE = 130 + + +class RunToolWithLoggingTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + # Configure to print logging to stdout. + logging.basicConfig(filename=None, level=logging.DEBUG) + console = logging.StreamHandler(sys.stdout) + logging.getLogger("").addHandler(console) + + def setUp(self): + super().setUp() + self.working_dir = tempfile.TemporaryDirectory() + # Run all the tests from working_dir which is our temp Android build top. + os.chdir(self.working_dir.name) + + self.logging_script_path = self._import_executable("run_tool_with_logging") + + def tearDown(self): + self.working_dir.cleanup() + super().tearDown() + + def test_does_not_log_when_logger_var_empty(self): + test_tool = TestScript.create(self.working_dir) + + self._run_script_and_wait(f""" + export ANDROID_TOOL_LOGGER="" + {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + test_tool.assert_called_once_with_args("arg1 arg2") + + def test_does_not_log_with_logger_unset(self): + test_tool = TestScript.create(self.working_dir) + + self._run_script_and_wait(f""" + unset ANDROID_TOOL_LOGGER + {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + test_tool.assert_called_once_with_args("arg1 arg2") + + def test_log_success_with_logger_enabled(self): + test_tool = TestScript.create(self.working_dir) + test_logger = TestScript.create(self.working_dir) + + self._run_script_and_wait(f""" + export ANDROID_TOOL_LOGGER="{test_logger.executable}" + {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + test_tool.assert_called_once_with_args("arg1 arg2") + expected_logger_args = ( + "--tool_tag=FAKE_TOOL --start_timestamp=\d+\.\d+ --end_timestamp=" + "\d+\.\d+ --tool_args=arg1 arg2 --exit_code=0" + ) + test_logger.assert_called_once_with_args(expected_logger_args) + + def test_run_tool_output_is_same_with_and_without_logging(self): + test_tool = TestScript.create(self.working_dir, "echo 'tool called'") + test_logger = TestScript.create(self.working_dir) + + run_tool_with_logging_stdout, run_tool_with_logging_stderr = ( + self._run_script_and_wait(f""" + export ANDROID_TOOL_LOGGER="{test_logger.executable}" + {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + ) + + run_tool_without_logging_stdout, run_tool_without_logging_stderr = ( + self._run_script_and_wait(f""" + export ANDROID_TOOL_LOGGER="{test_logger.executable}" + {test_tool.executable} arg1 arg2 + """) + ) + + self.assertEqual( + run_tool_with_logging_stdout, run_tool_without_logging_stdout + ) + self.assertEqual( + run_tool_with_logging_stderr, run_tool_without_logging_stderr + ) + + def test_logger_output_is_suppressed(self): + test_tool = TestScript.create(self.working_dir) + test_logger = TestScript.create(self.working_dir, "echo 'logger called'") + + run_tool_with_logging_output, _ = self._run_script_and_wait(f""" + export ANDROID_TOOL_LOGGER="{test_logger.executable}" + {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + self.assertNotIn("logger called", run_tool_with_logging_output) + + def test_logger_error_is_suppressed(self): + test_tool = TestScript.create(self.working_dir) + test_logger = TestScript.create( + self.working_dir, "echo 'logger failed' > /dev/stderr; exit 1" + ) + + _, err = self._run_script_and_wait(f""" + export ANDROID_TOOL_LOGGER="{test_logger.executable}" + {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + self.assertNotIn("logger failed", err) + + def test_log_success_when_tool_interrupted(self): + test_tool = TestScript.create(self.working_dir, script_body="sleep 100") + test_logger = TestScript.create(self.working_dir) + + process = self._run_script(f""" + export ANDROID_TOOL_LOGGER="{test_logger.executable}" + {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + pgid = os.getpgid(process.pid) + # Give sometime for the subprocess to start. + time.sleep(1) + # Kill the subprocess and any processes created in the same group. + os.killpg(pgid, signal.SIGINT) + + returncode, _, _ = self._wait_for_process(process) + self.assertEqual(returncode, INTERRUPTED_RETURN_CODE) + + expected_logger_args = ( + "--tool_tag=FAKE_TOOL --start_timestamp=\d+\.\d+ --end_timestamp=" + "\d+\.\d+ --tool_args=arg1 arg2 --exit_code=130" + ) + test_logger.assert_called_once_with_args(expected_logger_args) + + def test_logger_can_be_toggled_on(self): + test_tool = TestScript.create(self.working_dir) + test_logger = TestScript.create(self.working_dir) + + self._run_script_and_wait(f""" + export ANDROID_TOOL_LOGGER="" + export ANDROID_TOOL_LOGGER="{test_logger.executable}" + {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + test_logger.assert_called_with_times(1) + + def test_logger_can_be_toggled_off(self): + test_tool = TestScript.create(self.working_dir) + test_logger = TestScript.create(self.working_dir) + + self._run_script_and_wait(f""" + export ANDROID_TOOL_LOGGER="{test_logger.executable}" + export ANDROID_TOOL_LOGGER="" + {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + test_logger.assert_not_called() + + def test_integration_tool_event_logger_dry_run(self): + test_tool = TestScript.create(self.working_dir) + logger_path = self._import_executable("tool_event_logger") + + self._run_script_and_wait(f""" + TMPDIR="{self.working_dir.name}" + export ANDROID_TOOL_LOGGER="{logger_path}" + export ANDROID_TOOL_LOGGER_EXTRA_ARGS="--dry_run" + {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + self._assert_logger_dry_run() + + def test_tool_args_do_not_fail_logger(self): + test_tool = TestScript.create(self.working_dir) + logger_path = self._import_executable("tool_event_logger") + + self._run_script_and_wait(f""" + TMPDIR="{self.working_dir.name}" + export ANDROID_TOOL_LOGGER="{logger_path}" + export ANDROID_TOOL_LOGGER_EXTRA_ARGS="--dry_run" + {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} --tool-arg1 + """) + + self._assert_logger_dry_run() + + def _import_executable(self, executable_name: str) -> Path: + # logger = "tool_event_logger" + executable_path = Path(self.working_dir.name).joinpath(executable_name) + with resources.as_file( + resources.files("testdata").joinpath(executable_name) + ) as p: + shutil.copy(p, executable_path) + Path.chmod(executable_path, 0o755) + return executable_path + + def _assert_logger_dry_run(self): + log_files = glob.glob(self.working_dir.name + "/tool_event_logger_*/*.log") + self.assertEqual(len(log_files), 1) + + with open(log_files[0], "r") as f: + lines = f.readlines() + self.assertEqual(len(lines), 1) + self.assertIn("dry run", lines[0]) + + def _run_script_and_wait(self, test_script: str) -> tuple[str, str]: + process = self._run_script(test_script) + returncode, out, err = self._wait_for_process(process) + logging.debug("script stdout: %s", out) + logging.debug("script stderr: %s", err) + self.assertEqual(returncode, EXII_RETURN_CODE) + return out, err + + def _run_script(self, test_script: str) -> subprocess.Popen: + return subprocess.Popen( + test_script, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + start_new_session=True, + executable="/bin/bash", + ) + + def _wait_for_process( + self, process: subprocess.Popen + ) -> tuple[int, str, str]: + pgid = os.getpgid(process.pid) + out, err = process.communicate() + # Wait for all process in the same group to complete since the logger runs + # as a separate detached process. + self._wait_for_process_group(pgid) + return (process.returncode, out, err) + + def _wait_for_process_group(self, pgid: int, timeout: int = 5): + """Waits for all subprocesses within the process group to complete.""" + start_time = time.time() + while True: + if time.time() - start_time > timeout: + raise TimeoutError( + f"Process group did not complete after {timeout} seconds" + ) + for pid in os.listdir("/proc"): + if pid.isdigit(): + try: + if os.getpgid(int(pid)) == pgid: + time.sleep(0.1) + break + except (FileNotFoundError, PermissionError, ProcessLookupError): + pass + else: + # All processes have completed. + break + + +@dataclasses.dataclass +class TestScript: + executable: Path + output_file: Path + + def create(temp_dir: Path, script_body: str = ""): + with tempfile.NamedTemporaryFile(dir=temp_dir.name, delete=False) as f: + output_file = f.name + + with tempfile.NamedTemporaryFile(dir=temp_dir.name, delete=False) as f: + executable = f.name + executable_contents = textwrap.dedent(f""" + #!/bin/bash + + echo "${{@}}" >> {output_file} + {script_body} + """) + f.write(executable_contents.encode("utf-8")) + + Path.chmod(f.name, os.stat(f.name).st_mode | stat.S_IEXEC) + + return TestScript(executable, output_file) + + def assert_called_with_times(self, expected_call_times: int): + lines = self._read_contents_from_output_file() + assert len(lines) == expected_call_times, ( + f"Expect to call {expected_call_times} times, but actually called" + f" {len(lines)} times." + ) + + def assert_called_with_args(self, expected_args: str): + lines = self._read_contents_from_output_file() + assert len(lines) > 0 + assert re.search(expected_args, lines[0]), ( + f"Expect to call with args {expected_args}, but actually called with" + f" args {lines[0]}." + ) + + def assert_not_called(self): + self.assert_called_with_times(0) + + def assert_called_once_with_args(self, expected_args: str): + self.assert_called_with_times(1) + self.assert_called_with_args(expected_args) + + def _read_contents_from_output_file(self) -> list[str]: + with open(self.output_file, "r") as f: + return f.readlines() + + +if __name__ == "__main__": + unittest.main() diff --git a/ui/build/config.go b/ui/build/config.go index 7426a78ee..feded1c85 100644 --- a/ui/build/config.go +++ b/ui/build/config.go @@ -1164,14 +1164,6 @@ func (c *configImpl) SetSourceRootDirs(i []string) { c.sourceRootDirs = i } -func (c *configImpl) GetIncludeTags() []string { - return c.includeTags -} - -func (c *configImpl) SetIncludeTags(i []string) { - c.includeTags = i -} - func (c *configImpl) GetLogsPrefix() string { return c.logsPrefix } diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go index e17bd5402..eba86a0fc 100644 --- a/ui/build/dumpvars.go +++ b/ui/build/dumpvars.go @@ -147,7 +147,6 @@ func dumpMakeVars(ctx Context, config Config, goals, vars []string, write_soong_ var BannerVars = []string{ "PLATFORM_VERSION_CODENAME", "PLATFORM_VERSION", - "PRODUCT_INCLUDE_TAGS", "PRODUCT_SOURCE_ROOT_DIRS", "TARGET_PRODUCT", "TARGET_BUILD_VARIANT", @@ -301,6 +300,5 @@ func runMakeProductConfig(ctx Context, config Config) { config.SetBuildBrokenDupRules(makeVars["BUILD_BROKEN_DUP_RULES"] == "true") config.SetBuildBrokenUsesNetwork(makeVars["BUILD_BROKEN_USES_NETWORK"] == "true") config.SetBuildBrokenNinjaUsesEnvVars(strings.Fields(makeVars["BUILD_BROKEN_NINJA_USES_ENV_VARS"])) - config.SetIncludeTags(strings.Fields(makeVars["PRODUCT_INCLUDE_TAGS"])) config.SetSourceRootDirs(strings.Fields(makeVars["PRODUCT_SOURCE_ROOT_DIRS"])) } diff --git a/ui/build/soong.go b/ui/build/soong.go index 9955b1fdc..2f3150d03 100644 --- a/ui/build/soong.go +++ b/ui/build/soong.go @@ -401,7 +401,6 @@ func bootstrapBlueprint(ctx Context, config Config) { } blueprintCtx := blueprint.NewContext() - blueprintCtx.AddIncludeTags(config.GetIncludeTags()...) blueprintCtx.AddSourceRootDirs(config.GetSourceRootDirs()...) blueprintCtx.SetIgnoreUnknownModuleTypes(true) blueprintConfig := BlueprintConfig{ diff --git a/ui/build/test_build.go b/ui/build/test_build.go index 309513919..24ad08284 100644 --- a/ui/build/test_build.go +++ b/ui/build/test_build.go @@ -79,6 +79,10 @@ func testForDanglingRules(ctx Context, config Config) { // bpglob is built explicitly using Microfactory bpglob := filepath.Join(config.SoongOutDir(), "bpglob") + // release-config files are generated from the initial lunch or Kati phase + // before running soong and ninja. + releaseConfigDir := filepath.Join(outDir, "soong", "release-config") + danglingRules := make(map[string]bool) scanner := bufio.NewScanner(stdout) @@ -93,7 +97,8 @@ func testForDanglingRules(ctx Context, config Config) { line == variablesFilePath || line == dexpreoptConfigFilePath || line == buildDatetimeFilePath || - line == bpglob { + line == bpglob || + strings.HasPrefix(line, releaseConfigDir) { // Leaf node is in one of Soong's bootstrap directories, which do not have // full build rules in the primary build.ninja file. continue |