diff --git a/android/config.go b/android/config.go index 9f1fd6a1b..df3695cc6 100644 --- a/android/config.go +++ b/android/config.go @@ -1482,6 +1482,10 @@ func (c *config) MissingUsesLibraries() []string { return c.productVariables.MissingUsesLibraries } +func (c *config) TargetMultitreeUpdateMeta() bool { + return c.productVariables.MultitreeUpdateMeta +} + func (c *deviceConfig) DeviceArch() string { return String(c.config.productVariables.DeviceArch) } diff --git a/android/paths.go b/android/paths.go index e7829b961..e0e5ae580 100644 --- a/android/paths.go +++ b/android/paths.go @@ -1057,7 +1057,8 @@ func safePathForSource(ctx PathContext, pathComponents ...string) (SourcePath, e } // absolute path already checked by validateSafePath - if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) { + // special-case api surface gen files for now + if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) && !strings.Contains(ret.String(), ctx.Config().soongOutDir+"/.export") { return ret, fmt.Errorf("source path %q is in output", ret.String()) } @@ -1073,7 +1074,8 @@ func pathForSource(ctx PathContext, pathComponents ...string) (SourcePath, error } // absolute path already checked by validatePath - if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) { + // special-case for now + if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) && !strings.Contains(ret.String(), ctx.Config().soongOutDir+"/.export") { return ret, fmt.Errorf("source path %q is in output", ret.String()) } diff --git a/android/variable.go b/android/variable.go index 077b81097..b7e28d454 100644 --- a/android/variable.go +++ b/android/variable.go @@ -351,6 +351,8 @@ type productVariables struct { RecoverySnapshotDirsIncluded []string `json:",omitempty"` HostFakeSnapshotEnabled bool `json:",omitempty"` + MultitreeUpdateMeta bool `json:",omitempty"` + BoardVendorSepolicyDirs []string `json:",omitempty"` BoardOdmSepolicyDirs []string `json:",omitempty"` BoardReqdMaskPolicy []string `json:",omitempty"` diff --git a/apex/Android.bp b/apex/Android.bp index 41224ecd5..d3417c24d 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -14,6 +14,7 @@ bootstrap_go_package { "soong-cc", "soong-filesystem", "soong-java", + "soong-multitree", "soong-provenance", "soong-python", "soong-rust", diff --git a/apex/apex.go b/apex/apex.go index 76af1b82e..f16b72d96 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -33,6 +33,7 @@ import ( prebuilt_etc "android/soong/etc" "android/soong/filesystem" "android/soong/java" + "android/soong/multitree" "android/soong/python" "android/soong/rust" "android/soong/sh" @@ -358,6 +359,7 @@ type apexBundle struct { android.OverridableModuleBase android.SdkBase android.BazelModuleBase + multitree.ExportableModuleBase // Properties properties apexBundleProperties @@ -1359,6 +1361,21 @@ func (a *apexBundle) OutputFiles(tag string) (android.Paths, error) { } } +var _ multitree.Exportable = (*apexBundle)(nil) + +func (a *apexBundle) Exportable() bool { + if a.properties.ApexType == flattenedApex { + return false + } + return true +} + +func (a *apexBundle) TaggedOutputs() map[string]android.Paths { + ret := make(map[string]android.Paths) + ret["apex"] = android.Paths{a.outputFile} + return ret +} + var _ cc.Coverage = (*apexBundle)(nil) // Implements cc.Coverage @@ -2372,6 +2389,7 @@ func newApexBundle() *apexBundle { android.InitSdkAwareModule(module) android.InitOverridableModule(module, &module.overridableProperties.Overrides) android.InitBazelModule(module) + multitree.InitExportableModule(module) return module } diff --git a/cc/Android.bp b/cc/Android.bp index 9103a48b4..bbc2357a8 100644 --- a/cc/Android.bp +++ b/cc/Android.bp @@ -15,6 +15,7 @@ bootstrap_go_package { "soong-etc", "soong-fuzz", "soong-genrule", + "soong-multitree", "soong-snapshot", "soong-tradefed", ], @@ -65,6 +66,7 @@ bootstrap_go_package { "library.go", "library_headers.go", "library_sdk_member.go", + "library_stub.go", "native_bridge_sdk_trait.go", "object.go", "test.go", @@ -94,6 +96,7 @@ bootstrap_go_package { "gen_test.go", "genrule_test.go", "library_headers_test.go", + "library_stub_test.go", "library_test.go", "object_test.go", "prebuilt_test.go", diff --git a/cc/library_stub.go b/cc/library_stub.go new file mode 100644 index 000000000..4d0148df4 --- /dev/null +++ b/cc/library_stub.go @@ -0,0 +1,163 @@ +// Copyright 2021 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 cc + +import ( + "android/soong/android" + "android/soong/multitree" +) + +func init() { + RegisterLibraryStubBuildComponents(android.InitRegistrationContext) +} + +func RegisterLibraryStubBuildComponents(ctx android.RegistrationContext) { + // cc_api_stub_library shares a lot of ndk_library, and this will be refactored later + ctx.RegisterModuleType("cc_api_stub_library", CcApiStubLibraryFactory) + ctx.RegisterModuleType("cc_api_contribution", CcApiContributionFactory) +} + +func CcApiStubLibraryFactory() android.Module { + module, decorator := NewLibrary(android.DeviceSupported) + apiStubDecorator := &apiStubDecorator{ + libraryDecorator: decorator, + } + apiStubDecorator.BuildOnlyShared() + + module.compiler = apiStubDecorator + module.linker = apiStubDecorator + module.installer = nil + module.library = apiStubDecorator + module.Properties.HideFromMake = true // TODO: remove + + android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibBoth) + module.AddProperties(&module.Properties, + &apiStubDecorator.properties, + &apiStubDecorator.MutatedProperties, + &apiStubDecorator.apiStubLibraryProperties) + return module +} + +type apiStubLiraryProperties struct { + Imported_includes []string `android:"path"` +} + +type apiStubDecorator struct { + *libraryDecorator + properties libraryProperties + apiStubLibraryProperties apiStubLiraryProperties +} + +func (compiler *apiStubDecorator) stubsVersions(ctx android.BaseMutatorContext) []string { + firstVersion := String(compiler.properties.First_version) + return ndkLibraryVersions(ctx, android.ApiLevelOrPanic(ctx, firstVersion)) +} + +func (decorator *apiStubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects { + if decorator.stubsVersion() == "" { + decorator.setStubsVersion("current") + } // TODO: fix + symbolFile := String(decorator.properties.Symbol_file) + nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile, + android.ApiLevelOrPanic(ctx, decorator.stubsVersion()), + "") + return compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc) +} + +func (decorator *apiStubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, objects Objects) android.Path { + decorator.reexportDirs(android.PathsForModuleSrc(ctx, decorator.apiStubLibraryProperties.Imported_includes)...) + return decorator.libraryDecorator.link(ctx, flags, deps, objects) +} + +func init() { + pctx.HostBinToolVariable("gen_api_surface_build_files", "gen_api_surface_build_files") +} + +type CcApiContribution struct { + android.ModuleBase + properties ccApiContributionProperties +} + +type ccApiContributionProperties struct { + Symbol_file *string `android:"path"` + First_version *string + Export_include_dir *string +} + +func CcApiContributionFactory() android.Module { + module := &CcApiContribution{} + module.AddProperties(&module.properties) + android.InitAndroidModule(module) + return module +} + +// Do some simple validations +// Majority of the build rules will be created in the ctx of the api surface this module contributes to +func (contrib *CcApiContribution) GenerateAndroidBuildActions(ctx android.ModuleContext) { + if contrib.properties.Symbol_file == nil { + ctx.PropertyErrorf("symbol_file", "%v does not have symbol file", ctx.ModuleName()) + } + if contrib.properties.First_version == nil { + ctx.PropertyErrorf("first_version", "%v does not have first_version for stub variants", ctx.ModuleName()) + } +} + +// Path is out/soong/.export/ but will be different in final multi-tree layout +func outPathApiSurface(ctx android.ModuleContext, myModuleName string, pathComponent string) android.OutputPath { + return android.PathForOutput(ctx, ".export", ctx.ModuleName(), myModuleName, pathComponent) +} + +func (contrib *CcApiContribution) CopyFilesWithTag(apiSurfaceContext android.ModuleContext) map[string]android.Paths { + // copy map.txt for now + // hardlinks cannot be created since nsjail creates a different mountpoint for out/ + myDir := apiSurfaceContext.OtherModuleDir(contrib) + genMapTxt := outPathApiSurface(apiSurfaceContext, contrib.Name(), String(contrib.properties.Symbol_file)) + apiSurfaceContext.Build(pctx, android.BuildParams{ + Rule: android.Cp, + Description: "import map.txt file", + Input: android.PathForSource(apiSurfaceContext, myDir, String(contrib.properties.Symbol_file)), + Output: genMapTxt, + }) + + outputs := make(map[string]android.Paths) + outputs["map"] = []android.Path{genMapTxt} + + if contrib.properties.Export_include_dir != nil { + includeDir := android.PathForSource(apiSurfaceContext, myDir, String(contrib.properties.Export_include_dir)) + outputs["export_include_dir"] = []android.Path{includeDir} + } + return outputs +} + +var _ multitree.ApiContribution = (*CcApiContribution)(nil) + +/* +func (contrib *CcApiContribution) GenerateBuildFiles(apiSurfaceContext android.ModuleContext) android.Paths { + genAndroidBp := outPathApiSurface(apiSurfaceContext, contrib.Name(), "Android.bp") + + // generate Android.bp + apiSurfaceContext.Build(pctx, android.BuildParams{ + Rule: genApiSurfaceBuildFiles, + Description: "generate API surface build files", + Outputs: []android.WritablePath{genAndroidBp}, + Args: map[string]string{ + "name": contrib.Name() + "." + apiSurfaceContext.ModuleName(), //e.g. liblog.ndk + "symbol_file": String(contrib.properties.Symbol_file), + "first_version": String(contrib.properties.First_version), + }, + }) + return []android.Path{genAndroidBp} +} +*/ diff --git a/cc/library_stub_test.go b/cc/library_stub_test.go new file mode 100644 index 000000000..15b56d227 --- /dev/null +++ b/cc/library_stub_test.go @@ -0,0 +1,108 @@ +// Copyright 2021 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 cc + +import ( + _ "fmt" + _ "sort" + + "testing" + + "android/soong/android" + "android/soong/multitree" +) + +func TestCcApiStubLibraryOutputFiles(t *testing.T) { + bp := ` + cc_api_stub_library { + name: "foo", + symbol_file: "foo.map.txt", + first_version: "29", + } + ` + result := prepareForCcTest.RunTestWithBp(t, bp) + outputs := result.ModuleForTests("foo", "android_arm64_armv8-a_shared").AllOutputs() + expected_file_suffixes := []string{".c", "stub.map", ".o", ".so"} + for _, expected_file_suffix := range expected_file_suffixes { + android.AssertBoolEquals(t, expected_file_suffix+" file not found in output", true, android.SuffixInList(outputs, expected_file_suffix)) + } +} + +func TestCcApiStubLibraryVariants(t *testing.T) { + bp := ` + cc_api_stub_library { + name: "foo", + symbol_file: "foo.map.txt", + first_version: "29", + } + ` + result := prepareForCcTest.RunTestWithBp(t, bp) + variants := result.ModuleVariantsForTests("foo") + expected_variants := []string{"29", "30", "S", "Tiramisu"} //TODO: make this test deterministic by using fixtures + for _, expected_variant := range expected_variants { + android.AssertBoolEquals(t, expected_variant+" variant not found in foo", true, android.SubstringInList(variants, expected_variant)) + } +} + +func TestCcLibraryUsesCcApiStubLibrary(t *testing.T) { + bp := ` + cc_api_stub_library { + name: "foo", + symbol_file: "foo.map.txt", + first_version: "29", + } + cc_library { + name: "foo_user", + shared_libs: [ + "foo#29", + ], + } + + ` + prepareForCcTest.RunTestWithBp(t, bp) +} + +func TestApiSurfaceOutputs(t *testing.T) { + bp := ` + api_surface { + name: "mysdk", + contributions: [ + "foo", + ], + } + + cc_api_contribution { + name: "foo", + symbol_file: "foo.map.txt", + first_version: "29", + } + ` + result := android.GroupFixturePreparers( + prepareForCcTest, + multitree.PrepareForTestWithApiSurface, + ).RunTestWithBp(t, bp) + mysdk := result.ModuleForTests("mysdk", "") + + actual_surface_inputs := mysdk.Rule("phony").BuildParams.Inputs.Strings() + expected_file_suffixes := []string{"mysdk/foo/foo.map.txt"} + for _, expected_file_suffix := range expected_file_suffixes { + android.AssertBoolEquals(t, expected_file_suffix+" file not found in input", true, android.SuffixInList(actual_surface_inputs, expected_file_suffix)) + } + + // check args/inputs to rule + /*api_surface_gen_rule_args := result.ModuleForTests("mysdk", "").Rule("genApiSurfaceBuildFiles").Args + android.AssertStringEquals(t, "name", "foo.mysdk", api_surface_gen_rule_args["name"]) + android.AssertStringEquals(t, "symbol_file", "foo.map.txt", api_surface_gen_rule_args["symbol_file"])*/ +} diff --git a/cc/ndk_library.go b/cc/ndk_library.go index 5ef41eae5..c031b14ca 100644 --- a/cc/ndk_library.go +++ b/cc/ndk_library.go @@ -93,7 +93,7 @@ var ( type libraryProperties struct { // Relative path to the symbol map. // An example file can be seen here: TODO(danalbert): Make an example. - Symbol_file *string + Symbol_file *string `android:"path"` // The first API level a library was available. A library will be generated // for every API level beginning with this one. diff --git a/cc/testing.go b/cc/testing.go index 32f7c6080..ecdae8b15 100644 --- a/cc/testing.go +++ b/cc/testing.go @@ -29,6 +29,7 @@ func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) { RegisterBinaryBuildComponents(ctx) RegisterLibraryBuildComponents(ctx) RegisterLibraryHeadersBuildComponents(ctx) + RegisterLibraryStubBuildComponents(ctx) ctx.RegisterModuleType("cc_benchmark", BenchmarkFactory) ctx.RegisterModuleType("cc_object", ObjectFactory) diff --git a/multitree/Android.bp b/multitree/Android.bp new file mode 100644 index 000000000..9b16d2021 --- /dev/null +++ b/multitree/Android.bp @@ -0,0 +1,19 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +bootstrap_go_package { + name: "soong-multitree", + pkgPath: "android/soong/multitree", + deps: [ + "blueprint", + "soong-android", + ], + srcs: [ + "api_surface.go", + "export.go", + "metadata.go", + "import.go", + ], + pluginFor: ["soong_build"], +} diff --git a/multitree/api_surface.go b/multitree/api_surface.go new file mode 100644 index 000000000..f739a2430 --- /dev/null +++ b/multitree/api_surface.go @@ -0,0 +1,119 @@ +// Copyright 2021 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 multitree + +import ( + "android/soong/android" + "fmt" + + "github.com/google/blueprint" +) + +var ( + pctx = android.NewPackageContext("android/soong/multitree") +) + +func init() { + RegisterApiSurfaceBuildComponents(android.InitRegistrationContext) +} + +var PrepareForTestWithApiSurface = android.FixtureRegisterWithContext(RegisterApiSurfaceBuildComponents) + +func RegisterApiSurfaceBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("api_surface", ApiSurfaceFactory) +} + +type ApiSurface struct { + android.ModuleBase + ExportableModuleBase + properties apiSurfaceProperties + + allOutputs android.Paths + taggedOutputs map[string]android.Paths +} + +type apiSurfaceProperties struct { + Contributions []string +} + +func ApiSurfaceFactory() android.Module { + module := &ApiSurface{} + module.AddProperties(&module.properties) + android.InitAndroidModule(module) + InitExportableModule(module) + return module +} + +func (surface *ApiSurface) DepsMutator(ctx android.BottomUpMutatorContext) { + if surface.properties.Contributions != nil { + ctx.AddVariationDependencies(nil, nil, surface.properties.Contributions...) + } + +} +func (surface *ApiSurface) GenerateAndroidBuildActions(ctx android.ModuleContext) { + contributionFiles := make(map[string]android.Paths) + var allOutputs android.Paths + ctx.WalkDeps(func(child, parent android.Module) bool { + if contribution, ok := child.(ApiContribution); ok { + copied := contribution.CopyFilesWithTag(ctx) + for tag, files := range copied { + contributionFiles[child.Name()+"#"+tag] = files + } + for _, paths := range copied { + allOutputs = append(allOutputs, paths...) + } + return false // no transitive dependencies + } + return false + }) + + // phony target + ctx.Build(pctx, android.BuildParams{ + Rule: blueprint.Phony, + Output: android.PathForPhony(ctx, ctx.ModuleName()), + Inputs: allOutputs, + }) + + surface.allOutputs = allOutputs + surface.taggedOutputs = contributionFiles +} + +func (surface *ApiSurface) OutputFiles(tag string) (android.Paths, error) { + if tag != "" { + return nil, fmt.Errorf("unknown tag: %q", tag) + } + return surface.allOutputs, nil +} + +func (surface *ApiSurface) TaggedOutputs() map[string]android.Paths { + return surface.taggedOutputs +} + +func (surface *ApiSurface) Exportable() bool { + return true +} + +var _ android.OutputFileProducer = (*ApiSurface)(nil) +var _ Exportable = (*ApiSurface)(nil) + +type ApiContribution interface { + // copy files necessaryt to construct an API surface + // For C, it will be map.txt and .h files + // For Java, it will be api.txt + CopyFilesWithTag(ctx android.ModuleContext) map[string]android.Paths // output paths + + // Generate Android.bp in out/ to use the exported .txt files + // GenerateBuildFiles(ctx ModuleContext) Paths //output paths +} diff --git a/multitree/export.go b/multitree/export.go new file mode 100644 index 000000000..aecade58d --- /dev/null +++ b/multitree/export.go @@ -0,0 +1,67 @@ +// Copyright 2022 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 multitree + +import ( + "android/soong/android" + + "github.com/google/blueprint/proptools" +) + +type moduleExportProperty struct { + // True if the module is exported to the other components in a multi-tree. + // Any components in the multi-tree can import this module to use. + Export *bool +} + +type ExportableModuleBase struct { + properties moduleExportProperty +} + +type Exportable interface { + // Properties for the exporable module. + exportableModuleProps() *moduleExportProperty + + // Check if this module can be exported. + // If this returns false, the module will not be exported regardless of the 'export' value. + Exportable() bool + + // Returns 'true' if this module has 'export: true' + // This module will not be exported if it returns 'false' to 'Exportable()' interface even if + // it has 'export: true'. + IsExported() bool + + // Map from tags to outputs. + // Each module can tag their outputs for convenience. + TaggedOutputs() map[string]android.Paths +} + +type ExportableModule interface { + android.Module + android.OutputFileProducer + Exportable +} + +func InitExportableModule(module ExportableModule) { + module.AddProperties(module.exportableModuleProps()) +} + +func (m *ExportableModuleBase) exportableModuleProps() *moduleExportProperty { + return &m.properties +} + +func (m *ExportableModuleBase) IsExported() bool { + return proptools.Bool(m.properties.Export) +} diff --git a/multitree/import.go b/multitree/import.go new file mode 100644 index 000000000..1e5c421bc --- /dev/null +++ b/multitree/import.go @@ -0,0 +1,96 @@ +// Copyright 2022 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 multitree + +import ( + "android/soong/android" +) + +var ( + nameSuffix = ".imported" +) + +type MultitreeImportedModuleInterface interface { + GetMultitreeImportedModuleName() string +} + +func init() { + android.RegisterModuleType("imported_filegroup", importedFileGroupFactory) + + android.PreArchMutators(RegisterMultitreePreArchMutators) +} + +type importedFileGroupProperties struct { + // Imported modules from the other components in a multi-tree + Imported []string +} + +type importedFileGroup struct { + android.ModuleBase + + properties importedFileGroupProperties + srcs android.Paths +} + +func (ifg *importedFileGroup) Name() string { + return ifg.BaseModuleName() + nameSuffix +} + +func importedFileGroupFactory() android.Module { + module := &importedFileGroup{} + module.AddProperties(&module.properties) + + android.InitAndroidModule(module) + return module +} + +var _ MultitreeImportedModuleInterface = (*importedFileGroup)(nil) + +func (ifg *importedFileGroup) GetMultitreeImportedModuleName() string { + // The base module name of the imported filegroup is used as the imported module name + return ifg.BaseModuleName() +} + +var _ android.SourceFileProducer = (*importedFileGroup)(nil) + +func (ifg *importedFileGroup) Srcs() android.Paths { + return ifg.srcs +} + +func (ifg *importedFileGroup) GenerateAndroidBuildActions(ctx android.ModuleContext) { + // srcs from this module must not be used. Adding a dot path to avoid the empty + // source failure. Still soong returns error when a module wants to build against + // this source, which is intended. + ifg.srcs = android.PathsForModuleSrc(ctx, []string{"."}) +} + +func RegisterMultitreePreArchMutators(ctx android.RegisterMutatorsContext) { + ctx.BottomUp("multitree_imported_rename", MultitreeImportedRenameMutator).Parallel() +} + +func MultitreeImportedRenameMutator(ctx android.BottomUpMutatorContext) { + if m, ok := ctx.Module().(MultitreeImportedModuleInterface); ok { + name := m.GetMultitreeImportedModuleName() + if !ctx.OtherModuleExists(name) { + // Provide an empty filegroup not to break the build while updating the metadata. + // In other cases, soong will report an error to guide users to run 'm update-meta' + // first. + if !ctx.Config().TargetMultitreeUpdateMeta() { + ctx.ModuleErrorf("\"%s\" filegroup must be imported.\nRun 'm update-meta' first to import the filegroup.", name) + } + ctx.Rename(name) + } + } +} diff --git a/multitree/metadata.go b/multitree/metadata.go new file mode 100644 index 000000000..3fd721599 --- /dev/null +++ b/multitree/metadata.go @@ -0,0 +1,74 @@ +// Copyright 2022 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 multitree + +import ( + "android/soong/android" + "encoding/json" +) + +func init() { + android.RegisterSingletonType("update-meta", UpdateMetaSingleton) +} + +func UpdateMetaSingleton() android.Singleton { + return &updateMetaSingleton{} +} + +type jsonImported struct { + FileGroups map[string][]string `json:",omitempty"` +} + +type metadataJsonFlags struct { + Imported jsonImported `json:",omitempty"` + Exported map[string][]string `json:",omitempty"` +} + +type updateMetaSingleton struct { + importedModules []string + generatedMetadataFile android.OutputPath +} + +func (s *updateMetaSingleton) GenerateBuildActions(ctx android.SingletonContext) { + metadata := metadataJsonFlags{ + Imported: jsonImported{ + FileGroups: make(map[string][]string), + }, + Exported: make(map[string][]string), + } + ctx.VisitAllModules(func(module android.Module) { + if ifg, ok := module.(*importedFileGroup); ok { + metadata.Imported.FileGroups[ifg.BaseModuleName()] = ifg.properties.Imported + } + if e, ok := module.(ExportableModule); ok { + if e.IsExported() && e.Exportable() { + for tag, files := range e.TaggedOutputs() { + // TODO(b/219846705): refactor this to a dictionary + metadata.Exported[e.Name()+":"+tag] = append(metadata.Exported[e.Name()+":"+tag], files.Strings()...) + } + } + } + }) + jsonStr, err := json.Marshal(metadata) + if err != nil { + ctx.Errorf(err.Error()) + } + s.generatedMetadataFile = android.PathForOutput(ctx, "multitree", "metadata.json") + android.WriteFileRule(ctx, s.generatedMetadataFile, string(jsonStr)) +} + +func (s *updateMetaSingleton) MakeVars(ctx android.MakeVarsContext) { + ctx.Strict("MULTITREE_METADATA", s.generatedMetadataFile.String()) +}