From d1063c15869080e5f7c07852a512aaa12905fb11 Mon Sep 17 00:00:00 2001 From: Jiyong Park Date: Wed, 17 Jul 2019 20:08:41 +0900 Subject: [PATCH] Introduce module type 'sdk' This change introduces a new module type named 'sdk'. It is a logical group of prebuilt modules that together provide a context (e.g. APIs) in which Mainline modules (such as APEXes) are built. A prebuilt module (e.g. java_import) can join an sdk by adding it to the sdk module as shown below: sdk { name: "mysdk#20", java_libs: ["myjavalib_mysdk_20"], } java_import { name: "myjavalib_mysdk_20", srcs: ["myjavalib-v20.jar"], sdk_member_name: "myjavalib", } sdk { name: "mysdk#21", java_libs: ["myjavalib_mysdk_21"], } java_import { name: "myjavalib_mysdk_21", srcs: ["myjavalib-v21.jar"], sdk_member_name: "myjavalib", } java_library { name: "myjavalib", srcs: ["**/*/*.java"], } An APEX can specify the SDK(s) that it wants to build with via the new 'uses_sdks' property. apex { name: "myapex", java_libs: ["libX", "libY"], uses_sdks: ["mysdk#20"], } With this, libX, libY, and their transitive dependencies are all built with the version 20 of myjavalib (the first java_import module) instead of the other one (which is for version 21) and java_library having the same name (which is for ToT). Bug: 138182343 Test: m (sdk_test.go added) Change-Id: I7e14c524a7d6a0d9f575fb20822080f39818c01e --- Android.bp | 21 +++ android/sdk.go | 146 +++++++++++++++++++++ apex/apex.go | 36 ++++-- apex/apex_test.go | 4 +- apex/key.go | 4 +- cc/cc.go | 2 + cc/prebuilt.go | 2 + java/androidmk.go | 2 +- java/java.go | 4 + sdk/sdk.go | 172 +++++++++++++++++++++++++ sdk/sdk_test.go | 318 ++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 698 insertions(+), 13 deletions(-) create mode 100644 android/sdk.go create mode 100644 sdk/sdk.go create mode 100644 sdk/sdk_test.go diff --git a/Android.bp b/Android.bp index 25038c653..36045529c 100644 --- a/Android.bp +++ b/Android.bp @@ -67,6 +67,7 @@ bootstrap_go_package { "android/proto.go", "android/register.go", "android/rule_builder.go", + "android/sdk.go", "android/sh_binary.go", "android/singleton.go", "android/testing.go", @@ -474,6 +475,26 @@ bootstrap_go_package { pluginFor: ["soong_build"], } +bootstrap_go_package { + name: "soong-sdk", + pkgPath: "android/soong/sdk", + deps: [ + "blueprint", + "soong", + "soong-android", + "soong-apex", + "soong-cc", + "soong-java", + ], + srcs: [ + "sdk/sdk.go", + ], + testSrcs: [ + "sdk/sdk_test.go", + ], + pluginFor: ["soong_build"], +} + // // Defaults to enable various configurations of host bionic // diff --git a/android/sdk.go b/android/sdk.go new file mode 100644 index 000000000..52c392f4d --- /dev/null +++ b/android/sdk.go @@ -0,0 +1,146 @@ +// Copyright (C) 2019 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. + +package android + +import ( + "strings" + + "github.com/google/blueprint/proptools" +) + +// SdkAware is the interface that must be supported by any module to become a member of SDK or to be +// built with SDK +type SdkAware interface { + Module + sdkBase() *SdkBase + MakeMemberOf(sdk SdkRef) + IsInAnySdk() bool + ContainingSdk() SdkRef + MemberName() string + BuildWithSdks(sdks SdkRefs) + RequiredSdks() SdkRefs +} + +// SdkRef refers to a version of an SDK +type SdkRef struct { + Name string + Version string +} + +const ( + // currentVersion refers to the in-development version of an SDK + currentVersion = "current" +) + +// IsCurrentVersion determines if the SdkRef is referencing to an in-development version of an SDK +func (s SdkRef) IsCurrentVersion() bool { + return s.Version == currentVersion +} + +// IsCurrentVersionOf determines if the SdkRef is referencing to an in-development version of the +// specified SDK +func (s SdkRef) IsCurrentVersionOf(name string) bool { + return s.Name == name && s.IsCurrentVersion() +} + +// ParseSdkRef parses a `name#version` style string into a corresponding SdkRef struct +func ParseSdkRef(ctx BaseModuleContext, str string, property string) SdkRef { + tokens := strings.Split(str, "#") + if len(tokens) < 1 || len(tokens) > 2 { + ctx.PropertyErrorf(property, "%q does not follow name#version syntax", str) + return SdkRef{Name: "invalid sdk name", Version: "invalid sdk version"} + } + + name := tokens[0] + + version := currentVersion // If version is omitted, defaults to "current" + if len(tokens) == 2 { + version = tokens[1] + } + + return SdkRef{Name: name, Version: version} +} + +type SdkRefs []SdkRef + +func (refs SdkRefs) Contains(s SdkRef) bool { + for _, r := range refs { + if r == s { + return true + } + } + return false +} + +type sdkProperties struct { + // The SDK that this module is a member of. nil if it is not a member of any SDK + ContainingSdk *SdkRef `blueprint:"mutated"` + + // The list of SDK names and versions that are used to build this module + RequiredSdks SdkRefs `blueprint:"mutated"` + + // Name of the module that this sdk member is representing + Sdk_member_name *string +} + +// SdkBase is a struct that is expected to be included in module types to implement the SdkAware +// interface. InitSdkAwareModule should be called to initialize this struct. +type SdkBase struct { + properties sdkProperties +} + +func (s *SdkBase) sdkBase() *SdkBase { + return s +} + +// MakeMemberof sets this module to be a member of a specific SDK +func (s *SdkBase) MakeMemberOf(sdk SdkRef) { + s.properties.ContainingSdk = &sdk +} + +// IsInAnySdk returns true if this module is a member of any SDK +func (s *SdkBase) IsInAnySdk() bool { + return s.properties.ContainingSdk != nil +} + +// ContainingSdk returns the SDK that this module is a member of +func (s *SdkBase) ContainingSdk() SdkRef { + if s.properties.ContainingSdk != nil { + return *s.properties.ContainingSdk + } + return SdkRef{Name: "", Version: currentVersion} +} + +// Membername returns the name of the module that this SDK member is overriding +func (s *SdkBase) MemberName() string { + return proptools.String(s.properties.Sdk_member_name) +} + +// BuildWithSdks is used to mark that this module has to be built with the given SDK(s). +func (s *SdkBase) BuildWithSdks(sdks SdkRefs) { + s.properties.RequiredSdks = sdks +} + +// RequiredSdks returns the SDK(s) that this module has to be built with +func (s *SdkBase) RequiredSdks() SdkRefs { + return s.properties.RequiredSdks +} + +// InitSdkAwareModule initializes the SdkBase struct. This must be called by all modules including +// SdkBase. +func InitSdkAwareModule(m SdkAware) { + base := m.sdkBase() + m.AddProperties(&base.properties) +} diff --git a/apex/apex.go b/apex/apex.go index 4f79c4227..4383876da 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -185,7 +185,7 @@ func init() { pctx.HostBinToolVariable("zipalign", "zipalign") pctx.HostBinToolVariable("jsonmodify", "jsonmodify") - android.RegisterModuleType("apex", apexBundleFactory) + android.RegisterModuleType("apex", BundleFactory) android.RegisterModuleType("apex_test", testApexBundleFactory) android.RegisterModuleType("apex_vndk", vndkApexBundleFactory) android.RegisterModuleType("apex_defaults", defaultsFactory) @@ -195,12 +195,14 @@ func init() { ctx.TopDown("apex_vndk_gather", apexVndkGatherMutator).Parallel() ctx.BottomUp("apex_vndk_add_deps", apexVndkAddDepsMutator).Parallel() }) - android.PostDepsMutators(func(ctx android.RegisterMutatorsContext) { - ctx.TopDown("apex_deps", apexDepsMutator) - ctx.BottomUp("apex", apexMutator).Parallel() - ctx.BottomUp("apex_flattened", apexFlattenedMutator).Parallel() - ctx.BottomUp("apex_uses", apexUsesMutator).Parallel() - }) + android.PostDepsMutators(RegisterPostDepsMutators) +} + +func RegisterPostDepsMutators(ctx android.RegisterMutatorsContext) { + ctx.TopDown("apex_deps", apexDepsMutator) + ctx.BottomUp("apex", apexMutator).Parallel() + ctx.BottomUp("apex_flattened", apexFlattenedMutator).Parallel() + ctx.BottomUp("apex_uses", apexUsesMutator).Parallel() } var ( @@ -409,6 +411,12 @@ type apexBundleProperties struct { // To distinguish between flattened and non-flattened variants. // if set true, then this variant is flattened variant. Flattened bool `blueprint:"mutated"` + + // List of SDKs that are used to build this APEX. A reference to an SDK should be either + // `name#version` or `name` which is an alias for `name#current`. If left empty, `platform#current` + // is implied. This value affects all modules included in this APEX. In other words, they are + // also built with the SDKs specified here. + Uses_sdks []string } type apexTargetBundleProperties struct { @@ -535,6 +543,7 @@ type apexFile struct { type apexBundle struct { android.ModuleBase android.DefaultableModuleBase + android.SdkBase properties apexBundleProperties targetProperties apexTargetBundleProperties @@ -737,6 +746,16 @@ func (a *apexBundle) DepsMutator(ctx android.BottomUpMutatorContext) { if cert != "" { ctx.AddDependency(ctx.Module(), certificateTag, cert) } + + // TODO(jiyong): ensure that all apexes are with non-empty uses_sdks + if len(a.properties.Uses_sdks) > 0 { + sdkRefs := []android.SdkRef{} + for _, str := range a.properties.Uses_sdks { + parsed := android.ParseSdkRef(ctx, str, "uses_sdks") + sdkRefs = append(sdkRefs, parsed) + } + a.BuildWithSdks(sdkRefs) + } } func (a *apexBundle) getCertString(ctx android.BaseModuleContext) string { @@ -1706,6 +1725,7 @@ func newApexBundle() *apexBundle { }) android.InitAndroidMultiTargetsArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon) android.InitDefaultableModule(module) + android.InitSdkAwareModule(module) return module } @@ -1721,7 +1741,7 @@ func testApexBundleFactory() android.Module { return bundle } -func apexBundleFactory() android.Module { +func BundleFactory() android.Module { return newApexBundle() } diff --git a/apex/apex_test.go b/apex/apex_test.go index 7f36f51e1..60a309eb5 100644 --- a/apex/apex_test.go +++ b/apex/apex_test.go @@ -96,10 +96,10 @@ func testApexContext(t *testing.T, bp string, handlers ...testCustomizer) (*andr config.TestProductVariables.Platform_vndk_version = proptools.StringPtr("VER") ctx := android.NewTestArchContext() - ctx.RegisterModuleType("apex", android.ModuleFactoryAdaptor(apexBundleFactory)) + ctx.RegisterModuleType("apex", android.ModuleFactoryAdaptor(BundleFactory)) ctx.RegisterModuleType("apex_test", android.ModuleFactoryAdaptor(testApexBundleFactory)) ctx.RegisterModuleType("apex_vndk", android.ModuleFactoryAdaptor(vndkApexBundleFactory)) - ctx.RegisterModuleType("apex_key", android.ModuleFactoryAdaptor(apexKeyFactory)) + ctx.RegisterModuleType("apex_key", android.ModuleFactoryAdaptor(ApexKeyFactory)) ctx.RegisterModuleType("apex_defaults", android.ModuleFactoryAdaptor(defaultsFactory)) ctx.RegisterModuleType("prebuilt_apex", android.ModuleFactoryAdaptor(PrebuiltFactory)) diff --git a/apex/key.go b/apex/key.go index 08cd45ebd..ffde315f9 100644 --- a/apex/key.go +++ b/apex/key.go @@ -27,7 +27,7 @@ import ( var String = proptools.String func init() { - android.RegisterModuleType("apex_key", apexKeyFactory) + android.RegisterModuleType("apex_key", ApexKeyFactory) android.RegisterSingletonType("apex_keys_text", apexKeysTextFactory) } @@ -53,7 +53,7 @@ type apexKeyProperties struct { Installable *bool } -func apexKeyFactory() android.Module { +func ApexKeyFactory() android.Module { module := &apexKey{} module.AddProperties(&module.properties) android.InitAndroidArchModule(module, android.HostAndDeviceDefault, android.MultilibCommon) diff --git a/cc/cc.go b/cc/cc.go index fb7fca246..f4e777126 100644 --- a/cc/cc.go +++ b/cc/cc.go @@ -407,6 +407,7 @@ type Module struct { android.ModuleBase android.DefaultableModuleBase android.ApexModuleBase + android.SdkBase Properties BaseProperties VendorProperties VendorProperties @@ -546,6 +547,7 @@ func (c *Module) Init() android.Module { android.InitDefaultableModule(c) android.InitApexModule(c) + android.InitSdkAwareModule(c) return c } diff --git a/cc/prebuilt.go b/cc/prebuilt.go index 8c72b6910..4e6cdd755 100644 --- a/cc/prebuilt.go +++ b/cc/prebuilt.go @@ -152,6 +152,7 @@ func NewPrebuiltSharedLibrary(hod android.HostOrDeviceSupported) (*Module, *libr // Prebuilt libraries can be included in APEXes android.InitApexModule(module) + android.InitSdkAwareModule(module) return module, library } @@ -176,6 +177,7 @@ func NewPrebuiltStaticLibrary(hod android.HostOrDeviceSupported) (*Module, *libr module.AddProperties(&prebuilt.properties) android.InitPrebuiltModule(module, &prebuilt.properties.Srcs) + android.InitSdkAwareModule(module) return module, library } diff --git a/java/androidmk.go b/java/androidmk.go index 0e8e42248..f006705ab 100644 --- a/java/androidmk.go +++ b/java/androidmk.go @@ -155,7 +155,7 @@ func (j *TestHelperLibrary) AndroidMk() android.AndroidMkData { } func (prebuilt *Import) AndroidMk() android.AndroidMkData { - if !prebuilt.IsForPlatform() { + if !prebuilt.IsForPlatform() || !prebuilt.ContainingSdk().IsCurrentVersion() { return android.AndroidMkData{ Disabled: true, } diff --git a/java/java.go b/java/java.go index b05d7bbd5..b55e29a0b 100644 --- a/java/java.go +++ b/java/java.go @@ -290,6 +290,7 @@ type Module struct { android.ModuleBase android.DefaultableModuleBase android.ApexModuleBase + android.SdkBase properties CompilerProperties protoProperties android.ProtoProperties @@ -1632,6 +1633,7 @@ func LibraryFactory() android.Module { InitJavaModule(module, android.HostAndDeviceSupported) android.InitApexModule(module) + android.InitSdkAwareModule(module) return module } @@ -1912,6 +1914,7 @@ type Import struct { android.DefaultableModuleBase android.ApexModuleBase prebuilt android.Prebuilt + android.SdkBase properties ImportProperties @@ -2068,6 +2071,7 @@ func ImportFactory() android.Module { android.InitPrebuiltModule(module, &module.properties.Jars) InitJavaModule(module, android.HostAndDeviceSupported) android.InitApexModule(module) + android.InitSdkAwareModule(module) return module } diff --git a/sdk/sdk.go b/sdk/sdk.go new file mode 100644 index 000000000..fcb3fb7fe --- /dev/null +++ b/sdk/sdk.go @@ -0,0 +1,172 @@ +// Copyright (C) 2019 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. + +package sdk + +import ( + "github.com/google/blueprint" + + "android/soong/android" + // This package doesn't depend on the apex package, but import it to make its mutators to be + // registered before mutators in this package. See RegisterPostDepsMutators for more details. + _ "android/soong/apex" +) + +func init() { + android.RegisterModuleType("sdk", ModuleFactory) + android.PreDepsMutators(RegisterPreDepsMutators) + android.PostDepsMutators(RegisterPostDepsMutators) +} + +type sdk struct { + android.ModuleBase + android.DefaultableModuleBase + + properties sdkProperties +} + +type sdkProperties struct { + // The list of java_import modules that provide Java stubs for this SDK + Java_libs []string + Native_shared_libs []string +} + +// sdk defines an SDK which is a logical group of modules (e.g. native libs, headers, java libs, etc.) +// which Mainline modules like APEX can choose to build with. +func ModuleFactory() android.Module { + s := &sdk{} + s.AddProperties(&s.properties) + android.InitAndroidMultiTargetsArchModule(s, android.HostAndDeviceSupported, android.MultilibCommon) + android.InitDefaultableModule(s) + return s +} + +func (s *sdk) GenerateAndroidBuildActions(ctx android.ModuleContext) { + // TODO(jiyong): add build rules for creating stubs from members of this SDK +} + +// RegisterPreDepsMutators registers pre-deps mutators to support modules implementing SdkAware +// interface and the sdk module type. This function has been made public to be called by tests +// outside of the sdk package +func RegisterPreDepsMutators(ctx android.RegisterMutatorsContext) { + ctx.BottomUp("SdkMember", memberMutator).Parallel() + ctx.TopDown("SdkMember_deps", memberDepsMutator).Parallel() + ctx.BottomUp("SdkMemberInterVersion", memberInterVersionMutator).Parallel() +} + +// RegisterPostDepshMutators registers post-deps mutators to support modules implementing SdkAware +// interface and the sdk module type. This function has been made public to be called by tests +// outside of the sdk package +func RegisterPostDepsMutators(ctx android.RegisterMutatorsContext) { + // These must run AFTER apexMutator. Note that the apex package is imported even though there is + // no direct dependency to the package here. sdkDepsMutator sets the SDK requirements from an + // APEX to its dependents. Since different versions of the same SDK can be used by different + // APEXes, the apex and its dependents (which includes the dependencies to the sdk members) + // should have been mutated for the apex before the SDK requirements are set. + ctx.TopDown("SdkDepsMutator", sdkDepsMutator).Parallel() + ctx.BottomUp("SdkDepsReplaceMutator", sdkDepsReplaceMutator).Parallel() +} + +type dependencyTag struct { + blueprint.BaseDependencyTag +} + +// For dependencies from an SDK module to its members +// e.g. mysdk -> libfoo and libbar +var sdkMemberDepTag dependencyTag + +// For dependencies from an in-development version of an SDK member to frozen versions of the same member +// e.g. libfoo -> libfoo.mysdk.11 and libfoo.mysdk.12 +type sdkMemberVesionedDepTag struct { + dependencyTag + member string + version string +} + +// Step 1: create dependencies from an SDK module to its members. +func memberMutator(mctx android.BottomUpMutatorContext) { + if m, ok := mctx.Module().(*sdk); ok { + mctx.AddVariationDependencies(nil, sdkMemberDepTag, m.properties.Java_libs...) + + targets := mctx.MultiTargets() + for _, target := range targets { + mctx.AddFarVariationDependencies([]blueprint.Variation{ + {Mutator: "arch", Variation: target.String()}, + {Mutator: "image", Variation: "core"}, + {Mutator: "link", Variation: "shared"}, + }, sdkMemberDepTag, m.properties.Native_shared_libs...) + } + } +} + +// Step 2: record that dependencies of SDK modules are members of the SDK modules +func memberDepsMutator(mctx android.TopDownMutatorContext) { + if _, ok := mctx.Module().(*sdk); ok { + mySdkRef := android.ParseSdkRef(mctx, mctx.ModuleName(), "name") + mctx.VisitDirectDeps(func(child android.Module) { + if member, ok := child.(android.SdkAware); ok { + member.MakeMemberOf(mySdkRef) + } + }) + } +} + +// Step 3: create dependencies from the in-development version of an SDK member to frozen versions +// of the same member. By having these dependencies, they are mutated for multiple Mainline modules +// (apex and apk), each of which might want different sdks to be built with. For example, if both +// apex A and B are referencing libfoo which is a member of sdk 'mysdk', the two APEXes can be +// built with libfoo.mysdk.11 and libfoo.mysdk.12, respectively depending on which sdk they are +// using. +func memberInterVersionMutator(mctx android.BottomUpMutatorContext) { + if m, ok := mctx.Module().(android.SdkAware); ok && m.IsInAnySdk() { + if !m.ContainingSdk().IsCurrentVersion() { + memberName := m.MemberName() + tag := sdkMemberVesionedDepTag{member: memberName, version: m.ContainingSdk().Version} + mctx.AddReverseDependency(mctx.Module(), tag, memberName) + } + } +} + +// Step 4: transitively ripple down the SDK requirements from the root modules like APEX to its +// descendants +func sdkDepsMutator(mctx android.TopDownMutatorContext) { + if m, ok := mctx.Module().(android.SdkAware); ok { + // Module types for Mainline modules (e.g. APEX) are expected to implement RequiredSdks() + // by reading its own properties like `uses_sdks`. + requiredSdks := m.RequiredSdks() + if len(requiredSdks) > 0 { + mctx.VisitDirectDeps(func(m android.Module) { + if dep, ok := m.(android.SdkAware); ok { + dep.BuildWithSdks(requiredSdks) + } + }) + } + } +} + +// Step 5: if libfoo.mysdk.11 is in the context where version 11 of mysdk is requested, the +// versioned module is used instead of the un-versioned (in-development) module libfoo +func sdkDepsReplaceMutator(mctx android.BottomUpMutatorContext) { + if m, ok := mctx.Module().(android.SdkAware); ok && m.IsInAnySdk() { + if sdk := m.ContainingSdk(); !sdk.IsCurrentVersion() { + if m.RequiredSdks().Contains(sdk) { + // Note that this replacement is done only for the modules that have the same + // variations as the current module. Since current module is already mutated for + // apex references in other APEXes are not affected by this replacement. + memberName := m.MemberName() + mctx.ReplaceDependencies(memberName) + } + } + } +} diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go new file mode 100644 index 000000000..9eca72fdc --- /dev/null +++ b/sdk/sdk_test.go @@ -0,0 +1,318 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sdk + +import ( + "io/ioutil" + "os" + "strings" + "testing" + + "android/soong/android" + "android/soong/apex" + "android/soong/cc" + "android/soong/java" +) + +func testSdkContext(t *testing.T, bp string) (*android.TestContext, android.Config) { + config := android.TestArchConfig(buildDir, nil) + ctx := android.NewTestArchContext() + + // from android package + ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators) + ctx.PreArchMutators(func(ctx android.RegisterMutatorsContext) { + ctx.BottomUp("prebuilts", android.PrebuiltMutator).Parallel() + }) + ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) { + ctx.TopDown("prebuilt_select", android.PrebuiltSelectModuleMutator).Parallel() + ctx.BottomUp("prebuilt_postdeps", android.PrebuiltPostDepsMutator).Parallel() + }) + + // from java package + ctx.RegisterModuleType("android_app_certificate", android.ModuleFactoryAdaptor(java.AndroidAppCertificateFactory)) + ctx.RegisterModuleType("java_library", android.ModuleFactoryAdaptor(java.LibraryFactory)) + ctx.RegisterModuleType("java_import", android.ModuleFactoryAdaptor(java.ImportFactory)) + + // from cc package + ctx.RegisterModuleType("cc_library", android.ModuleFactoryAdaptor(cc.LibraryFactory)) + ctx.RegisterModuleType("cc_library_shared", android.ModuleFactoryAdaptor(cc.LibrarySharedFactory)) + ctx.RegisterModuleType("cc_object", android.ModuleFactoryAdaptor(cc.ObjectFactory)) + ctx.RegisterModuleType("cc_prebuilt_library_shared", android.ModuleFactoryAdaptor(cc.PrebuiltSharedLibraryFactory)) + ctx.RegisterModuleType("cc_prebuilt_library_static", android.ModuleFactoryAdaptor(cc.PrebuiltStaticLibraryFactory)) + ctx.RegisterModuleType("llndk_library", android.ModuleFactoryAdaptor(cc.LlndkLibraryFactory)) + ctx.RegisterModuleType("toolchain_library", android.ModuleFactoryAdaptor(cc.ToolchainLibraryFactory)) + ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) { + ctx.BottomUp("image", cc.ImageMutator).Parallel() + ctx.BottomUp("link", cc.LinkageMutator).Parallel() + ctx.BottomUp("vndk", cc.VndkMutator).Parallel() + ctx.BottomUp("test_per_src", cc.TestPerSrcMutator).Parallel() + ctx.BottomUp("version", cc.VersionMutator).Parallel() + ctx.BottomUp("begin", cc.BeginMutator).Parallel() + }) + + // from apex package + ctx.RegisterModuleType("apex", android.ModuleFactoryAdaptor(apex.BundleFactory)) + ctx.RegisterModuleType("apex_key", android.ModuleFactoryAdaptor(apex.ApexKeyFactory)) + ctx.PostDepsMutators(apex.RegisterPostDepsMutators) + + // from this package + ctx.RegisterModuleType("sdk", android.ModuleFactoryAdaptor(ModuleFactory)) + ctx.PreDepsMutators(RegisterPreDepsMutators) + ctx.PostDepsMutators(RegisterPostDepsMutators) + + ctx.Register() + + bp = bp + ` + apex_key { + name: "myapex.key", + public_key: "myapex.avbpubkey", + private_key: "myapex.pem", + } + + android_app_certificate { + name: "myapex.cert", + certificate: "myapex", + } + ` + cc.GatherRequiredDepsForTest(android.Android) + + ctx.MockFileSystem(map[string][]byte{ + "Android.bp": []byte(bp), + "build/make/target/product/security": nil, + "apex_manifest.json": nil, + "system/sepolicy/apex/myapex-file_contexts": nil, + "system/sepolicy/apex/myapex2-file_contexts": nil, + "myapex.avbpubkey": nil, + "myapex.pem": nil, + "myapex.x509.pem": nil, + "myapex.pk8": nil, + "Test.java": nil, + "Test.cpp": nil, + "libfoo.so": nil, + }) + + return ctx, config +} + +func testSdk(t *testing.T, bp string) (*android.TestContext, android.Config) { + ctx, config := testSdkContext(t, bp) + _, errs := ctx.ParseFileList(".", []string{"Android.bp"}) + android.FailIfErrored(t, errs) + _, errs = ctx.PrepareBuildActions(config) + android.FailIfErrored(t, errs) + return ctx, config +} + +// ensure that 'result' contains 'expected' +func ensureContains(t *testing.T, result string, expected string) { + t.Helper() + if !strings.Contains(result, expected) { + t.Errorf("%q is not found in %q", expected, result) + } +} + +// ensures that 'result' does not contain 'notExpected' +func ensureNotContains(t *testing.T, result string, notExpected string) { + t.Helper() + if strings.Contains(result, notExpected) { + t.Errorf("%q is found in %q", notExpected, result) + } +} + +func ensureListContains(t *testing.T, result []string, expected string) { + t.Helper() + if !android.InList(expected, result) { + t.Errorf("%q is not found in %v", expected, result) + } +} + +func ensureListNotContains(t *testing.T, result []string, notExpected string) { + t.Helper() + if android.InList(notExpected, result) { + t.Errorf("%q is found in %v", notExpected, result) + } +} + +func pathsToStrings(paths android.Paths) []string { + ret := []string{} + for _, p := range paths { + ret = append(ret, p.String()) + } + return ret +} + +func TestBasicSdkWithJava(t *testing.T) { + ctx, _ := testSdk(t, ` + sdk { + name: "mysdk#1", + java_libs: ["sdkmember_mysdk_1"], + } + + sdk { + name: "mysdk#2", + java_libs: ["sdkmember_mysdk_2"], + } + + java_import { + name: "sdkmember", + prefer: false, + host_supported: true, + } + + java_import { + name: "sdkmember_mysdk_1", + sdk_member_name: "sdkmember", + host_supported: true, + } + + java_import { + name: "sdkmember_mysdk_2", + sdk_member_name: "sdkmember", + host_supported: true, + } + + java_library { + name: "myjavalib", + srcs: ["Test.java"], + libs: ["sdkmember"], + system_modules: "none", + sdk_version: "none", + compile_dex: true, + host_supported: true, + } + + apex { + name: "myapex", + java_libs: ["myjavalib"], + uses_sdks: ["mysdk#1"], + key: "myapex.key", + certificate: ":myapex.cert", + } + + apex { + name: "myapex2", + java_libs: ["myjavalib"], + uses_sdks: ["mysdk#2"], + key: "myapex.key", + certificate: ":myapex.cert", + } + `) + + sdkMemberV1 := ctx.ModuleForTests("sdkmember_mysdk_1", "android_common_myapex").Rule("combineJar").Output + sdkMemberV2 := ctx.ModuleForTests("sdkmember_mysdk_2", "android_common_myapex2").Rule("combineJar").Output + + javalibForMyApex := ctx.ModuleForTests("myjavalib", "android_common_myapex") + javalibForMyApex2 := ctx.ModuleForTests("myjavalib", "android_common_myapex2") + + // Depending on the uses_sdks value, different libs are linked + ensureListContains(t, pathsToStrings(javalibForMyApex.Rule("javac").Implicits), sdkMemberV1.String()) + ensureListContains(t, pathsToStrings(javalibForMyApex2.Rule("javac").Implicits), sdkMemberV2.String()) +} + +func TestBasicSdkWithCc(t *testing.T) { + ctx, _ := testSdk(t, ` + sdk { + name: "mysdk#1", + native_shared_libs: ["sdkmember_mysdk_1"], + } + + sdk { + name: "mysdk#2", + native_shared_libs: ["sdkmember_mysdk_2"], + } + + cc_prebuilt_library_shared { + name: "sdkmember", + srcs: ["libfoo.so"], + prefer: false, + system_shared_libs: [], + stl: "none", + } + + cc_prebuilt_library_shared { + name: "sdkmember_mysdk_1", + sdk_member_name: "sdkmember", + srcs: ["libfoo.so"], + system_shared_libs: [], + stl: "none", + } + + cc_prebuilt_library_shared { + name: "sdkmember_mysdk_2", + sdk_member_name: "sdkmember", + srcs: ["libfoo.so"], + system_shared_libs: [], + stl: "none", + } + + cc_library_shared { + name: "mycpplib", + srcs: ["Test.cpp"], + shared_libs: ["sdkmember"], + system_shared_libs: [], + stl: "none", + } + + apex { + name: "myapex", + native_shared_libs: ["mycpplib"], + uses_sdks: ["mysdk#1"], + key: "myapex.key", + certificate: ":myapex.cert", + } + + apex { + name: "myapex2", + native_shared_libs: ["mycpplib"], + uses_sdks: ["mysdk#2"], + key: "myapex.key", + certificate: ":myapex.cert", + } + `) + + sdkMemberV1 := ctx.ModuleForTests("sdkmember_mysdk_1", "android_arm64_armv8-a_core_shared_myapex").Rule("toc").Output + sdkMemberV2 := ctx.ModuleForTests("sdkmember_mysdk_2", "android_arm64_armv8-a_core_shared_myapex2").Rule("toc").Output + + cpplibForMyApex := ctx.ModuleForTests("mycpplib", "android_arm64_armv8-a_core_shared_myapex") + cpplibForMyApex2 := ctx.ModuleForTests("mycpplib", "android_arm64_armv8-a_core_shared_myapex2") + + // Depending on the uses_sdks value, different libs are linked + ensureListContains(t, pathsToStrings(cpplibForMyApex.Rule("ld").Implicits), sdkMemberV1.String()) + ensureListContains(t, pathsToStrings(cpplibForMyApex2.Rule("ld").Implicits), sdkMemberV2.String()) +} + +var buildDir string + +func setUp() { + var err error + buildDir, err = ioutil.TempDir("", "soong_sdk_test") + if err != nil { + panic(err) + } +} + +func tearDown() { + os.RemoveAll(buildDir) +} + +func TestMain(m *testing.M) { + run := func() int { + setUp() + defer tearDown() + + return m.Run() + } + + os.Exit(run()) +}