diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go index a99fa1ff0..9d0c3deb9 100644 --- a/sdk/java_sdk_test.go +++ b/sdk/java_sdk_test.go @@ -795,6 +795,44 @@ sdk_snapshot { .intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar .intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt .intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt +`), + checkInfoContents(` +[ + { + "@type": "sdk", + "@name": "mysdk", + "java_header_libs": [ + "exported-system-module", + "system-module" + ], + "java_sdk_libs": [ + "myjavalib" + ], + "java_system_modules": [ + "my-system-modules" + ] + }, + { + "@type": "java_library", + "@name": "exported-system-module" + }, + { + "@type": "java_system_modules", + "@name": "my-system-modules", + "@deps": [ + "exported-system-module", + "system-module" + ] + }, + { + "@type": "java_sdk_library", + "@name": "myjavalib" + }, + { + "@type": "java_library", + "@name": "system-module" + } +] `), ) } diff --git a/sdk/sdk.go b/sdk/sdk.go index c8c7b79df..b37eaad69 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -75,6 +75,8 @@ type sdk struct { snapshotFile android.OptionalPath + infoFile android.OptionalPath + // The builder, preserved for testing. builderForTests *snapshotBuilder } @@ -191,27 +193,32 @@ func (s *sdk) GenerateAndroidBuildActions(ctx android.ModuleContext) { } // Generate the snapshot from the member info. - p := s.buildSnapshot(ctx, sdkVariants) - zip := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), p.Base(), p) - s.snapshotFile = android.OptionalPathForPath(zip) + s.buildSnapshot(ctx, sdkVariants) } } func (s *sdk) AndroidMkEntries() []android.AndroidMkEntries { - if !s.snapshotFile.Valid() { + if !s.snapshotFile.Valid() != !s.infoFile.Valid() { + panic("Snapshot (%q) and info file (%q) should both be set or neither should be set.") + } else if !s.snapshotFile.Valid() { return []android.AndroidMkEntries{} } return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "FAKE", OutputFile: s.snapshotFile, - DistFiles: android.MakeDefaultDistFiles(s.snapshotFile.Path()), + DistFiles: android.MakeDefaultDistFiles(s.snapshotFile.Path(), s.infoFile.Path()), Include: "$(BUILD_PHONY_PACKAGE)", ExtraFooters: []android.AndroidMkExtraFootersFunc{ func(w io.Writer, name, prefix, moduleDir string) { // Allow the sdk to be built by simply passing its name on the command line. fmt.Fprintln(w, ".PHONY:", s.Name()) fmt.Fprintln(w, s.Name()+":", s.snapshotFile.String()) + + // Allow the sdk info to be built by simply passing its name on the command line. + infoTarget := s.Name() + ".info" + fmt.Fprintln(w, ".PHONY:", infoTarget) + fmt.Fprintln(w, infoTarget+":", s.infoFile.String()) }, }, }} diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go index 40de15027..ccbeb8d9a 100644 --- a/sdk/sdk_test.go +++ b/sdk/sdk_test.go @@ -263,7 +263,10 @@ func TestSdkInstall(t *testing.T) { result := testSdkWithFs(t, sdk, nil) CheckSnapshot(t, result, "mysdk", "", - checkAllOtherCopyRules(`.intermediates/mysdk/common_os/mysdk-current.zip -> mysdk-current.zip`)) + checkAllOtherCopyRules(` +.intermediates/mysdk/common_os/mysdk-current.info -> mysdk-current.info +.intermediates/mysdk/common_os/mysdk-current.zip -> mysdk-current.zip +`)) } type EmbeddedPropertiesStruct struct { diff --git a/sdk/testing.go b/sdk/testing.go index 062f2000e..b0f5fdc62 100644 --- a/sdk/testing.go +++ b/sdk/testing.go @@ -142,6 +142,7 @@ func getSdkSnapshotBuildInfo(t *testing.T, result *android.TestResult, sdk *sdk) androidBpContents: sdk.GetAndroidBpContentsForTests(), androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(), androidVersionedBpContents: sdk.GetVersionedAndroidBpContentsForTests(), + infoContents: sdk.GetInfoContentsForTests(), snapshotTestCustomizations: map[snapshotTest]*snapshotTestCustomization{}, targetBuildRelease: sdk.builderForTests.targetBuildRelease, } @@ -402,6 +403,17 @@ func checkMergeZips(expected ...string) snapshotBuildInfoChecker { } } +// Check that the snapshot's info contents are ciorrect. +// +// Both the expected and actual string are both trimmed before comparing. +func checkInfoContents(expected string) snapshotBuildInfoChecker { + return func(info *snapshotBuildInfo) { + info.t.Helper() + android.AssertTrimmedStringEquals(info.t, "info contents do not match", + expected, info.infoContents) + } +} + type resultChecker func(t *testing.T, result *android.TestResult) // snapshotTestPreparer registers a preparer that will be used to customize the specified @@ -479,6 +491,9 @@ type snapshotBuildInfo struct { // The contents of the versioned Android.bp file androidVersionedBpContents string + // The contents of the info file. + infoContents string + // The paths, relative to the snapshot root, of all files and directories copied into the // snapshot. snapshotContents []string diff --git a/sdk/update.go b/sdk/update.go index 5db604b7c..71bd042a6 100644 --- a/sdk/update.go +++ b/sdk/update.go @@ -15,6 +15,8 @@ package sdk import ( + "bytes" + "encoding/json" "fmt" "reflect" "sort" @@ -219,9 +221,19 @@ func (s *sdk) collectMembers(ctx android.ModuleContext) { exportedComponentsInfo = ctx.OtherModuleProvider(child, android.ExportedComponentsInfoProvider).(android.ExportedComponentsInfo) } + var container android.SdkAware + if parent != ctx.Module() { + container = parent.(android.SdkAware) + } + export := memberTag.ExportMember() s.memberVariantDeps = append(s.memberVariantDeps, sdkMemberVariantDep{ - s, memberType, child.(android.SdkAware), export, exportedComponentsInfo, + sdkVariant: s, + memberType: memberType, + variant: child.(android.SdkAware), + container: container, + export: export, + exportedComponentsInfo: exportedComponentsInfo, }) // Recurse down into the member's dependencies as it may have dependencies that need to be @@ -311,7 +323,7 @@ func versionedSdkMemberName(ctx android.ModuleContext, memberName string, versio // buildSnapshot is the main function in this source file. It creates rules to copy // the contents (header files, stub libraries, etc) into the zip file. -func (s *sdk) buildSnapshot(ctx android.ModuleContext, sdkVariants []*sdk) android.OutputPath { +func (s *sdk) buildSnapshot(ctx android.ModuleContext, sdkVariants []*sdk) { // Aggregate all the sdkMemberVariantDep instances from all the sdk variants. hasLicenses := false @@ -370,9 +382,9 @@ func (s *sdk) buildSnapshot(ctx android.ModuleContext, sdkVariants []*sdk) andro // Unversioned modules are not required in that case because the numbered version will be a // finalized version of the snapshot that is intended to be kept separate from the generateUnversioned := version == soongSdkSnapshotVersionUnversioned || version == soongSdkSnapshotVersionCurrent - snapshotZipFileSuffix := "" + snapshotFileSuffix := "" if generateVersioned { - snapshotZipFileSuffix = "-" + version + snapshotFileSuffix = "-" + version } currentBuildRelease := latestBuildRelease() @@ -489,7 +501,7 @@ be unnecessary as every module in the sdk already has its own licenses property. filesToZip := builder.filesToZip // zip them all - zipPath := fmt.Sprintf("%s%s.zip", ctx.ModuleName(), snapshotZipFileSuffix) + zipPath := fmt.Sprintf("%s%s.zip", ctx.ModuleName(), snapshotFileSuffix) outputZipFile := android.PathForModuleOut(ctx, zipPath).OutputPath outputDesc := "Building snapshot for " + ctx.ModuleName() @@ -502,7 +514,7 @@ be unnecessary as every module in the sdk already has its own licenses property. zipFile = outputZipFile desc = outputDesc } else { - intermediatePath := fmt.Sprintf("%s%s.unmerged.zip", ctx.ModuleName(), snapshotZipFileSuffix) + intermediatePath := fmt.Sprintf("%s%s.unmerged.zip", ctx.ModuleName(), snapshotFileSuffix) zipFile = android.PathForModuleOut(ctx, intermediatePath).OutputPath desc = "Building intermediate snapshot for " + ctx.ModuleName() } @@ -527,7 +539,120 @@ be unnecessary as every module in the sdk already has its own licenses property. }) } - return outputZipFile + modules := s.generateInfoData(ctx, memberVariantDeps) + + // Output the modules information as pretty printed JSON. + info := newGeneratedFile(ctx, fmt.Sprintf("%s%s.info", ctx.ModuleName(), snapshotFileSuffix)) + output, err := json.MarshalIndent(modules, "", " ") + if err != nil { + ctx.ModuleErrorf("error generating %q: %s", info, err) + } + builder.infoContents = string(output) + info.generatedContents.UnindentedPrintf("%s", output) + info.build(pctx, ctx, nil) + infoPath := info.path + installedInfo := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), infoPath.Base(), infoPath) + s.infoFile = android.OptionalPathForPath(installedInfo) + + // Install the zip, making sure that the info file has been installed as well. + installedZip := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), outputZipFile.Base(), outputZipFile, installedInfo) + s.snapshotFile = android.OptionalPathForPath(installedZip) +} + +type moduleInfo struct { + // The type of the module, e.g. java_sdk_library + moduleType string + // The name of the module. + name string + // A list of additional dependencies of the module. + deps []string + // Additional dynamic properties. + dynamic map[string]interface{} +} + +func (m *moduleInfo) MarshalJSON() ([]byte, error) { + buffer := bytes.Buffer{} + + separator := "" + writeObjectPair := func(key string, value interface{}) { + buffer.WriteString(fmt.Sprintf("%s%q: ", separator, key)) + b, err := json.Marshal(value) + if err != nil { + panic(err) + } + buffer.Write(b) + separator = "," + } + + buffer.WriteString("{") + writeObjectPair("@type", m.moduleType) + writeObjectPair("@name", m.name) + if m.deps != nil { + writeObjectPair("@deps", m.deps) + } + for _, k := range android.SortedStringKeys(m.dynamic) { + v := m.dynamic[k] + writeObjectPair(k, v) + } + buffer.WriteString("}") + return buffer.Bytes(), nil +} + +var _ json.Marshaler = (*moduleInfo)(nil) + +// generateInfoData creates a list of moduleInfo structures that will be marshalled into JSON. +func (s *sdk) generateInfoData(ctx android.ModuleContext, memberVariantDeps []sdkMemberVariantDep) interface{} { + modules := []*moduleInfo{} + sdkInfo := moduleInfo{ + moduleType: "sdk", + name: ctx.ModuleName(), + dynamic: map[string]interface{}{}, + } + modules = append(modules, &sdkInfo) + + name2Info := map[string]*moduleInfo{} + getModuleInfo := func(module android.Module) *moduleInfo { + name := module.Name() + info := name2Info[name] + if info == nil { + moduleType := ctx.OtherModuleType(module) + // Remove any suffix added when creating modules dynamically. + moduleType = strings.Split(moduleType, "__")[0] + info = &moduleInfo{ + moduleType: moduleType, + name: name, + } + name2Info[name] = info + } + return info + } + + for _, memberVariantDep := range memberVariantDeps { + propertyName := memberVariantDep.memberType.SdkPropertyName() + var list []string + if v, ok := sdkInfo.dynamic[propertyName]; ok { + list = v.([]string) + } + + memberName := memberVariantDep.variant.Name() + list = append(list, memberName) + sdkInfo.dynamic[propertyName] = android.SortedUniqueStrings(list) + + if memberVariantDep.container != nil { + containerInfo := getModuleInfo(memberVariantDep.container) + containerInfo.deps = android.SortedUniqueStrings(append(containerInfo.deps, memberName)) + } + + // Make sure that the module info is created for each module. + getModuleInfo(memberVariantDep.variant) + } + + for _, memberName := range android.SortedStringKeys(name2Info) { + info := name2Info[memberName] + modules = append(modules, info) + } + + return modules } // filterOutComponents removes any item from the deps list that is a component of another item in @@ -1033,6 +1158,10 @@ func (s *sdk) GetAndroidBpContentsForTests() string { return contents.content.String() } +func (s *sdk) GetInfoContentsForTests() string { + return s.builderForTests.infoContents +} + func (s *sdk) GetUnversionedAndroidBpContentsForTests() string { contents := &generatedContents{} generateFilteredBpContents(contents, s.builderForTests.bpFile, func(module *bpModule) bool { @@ -1087,6 +1216,8 @@ type snapshotBuilder struct { // The target build release for which the snapshot is to be generated. targetBuildRelease *buildRelease + + infoContents string } func (s *snapshotBuilder) CopyToSnapshot(src android.Path, dest string) { @@ -1322,6 +1453,11 @@ type sdkMemberVariantDep struct { // The variant that is added to the sdk. variant android.SdkAware + // The optional container of this member, i.e. the module that is depended upon by the sdk + // (possibly transitively) and whose dependency on this module is why it was added to the sdk. + // Is nil if this a direct dependency of the sdk. + container android.SdkAware + // True if the member should be exported, i.e. accessible, from outside the sdk. export bool