For each sdk built generate a JSON file describing its contents
Some build scripts need to know information about the contents of an sdk, such as what APIs it provides (via java_sdk_library). Rather than duplicate that information in the scripts or attempt to access that information (where available) by looking at the contents of the snapshot this change generates a JSON file that sits alongside the snapshot itself. The info file can be generated without generating the snapshot zip file but whenever a snapshot zip file is generated the info is generated too. The info file sits alongside the zip file in out/mainline-sdks. Bug: 204763318 Test: m art-module-sdk m dist Change-Id: I289530bb21693dc6443826c24c17c9b5d85d2d8b
This commit is contained in:
@@ -795,6 +795,44 @@ sdk_snapshot {
|
|||||||
.intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
|
.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_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
|
.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"
|
||||||
|
}
|
||||||
|
]
|
||||||
`),
|
`),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
17
sdk/sdk.go
17
sdk/sdk.go
@@ -75,6 +75,8 @@ type sdk struct {
|
|||||||
|
|
||||||
snapshotFile android.OptionalPath
|
snapshotFile android.OptionalPath
|
||||||
|
|
||||||
|
infoFile android.OptionalPath
|
||||||
|
|
||||||
// The builder, preserved for testing.
|
// The builder, preserved for testing.
|
||||||
builderForTests *snapshotBuilder
|
builderForTests *snapshotBuilder
|
||||||
}
|
}
|
||||||
@@ -191,27 +193,32 @@ func (s *sdk) GenerateAndroidBuildActions(ctx android.ModuleContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate the snapshot from the member info.
|
// Generate the snapshot from the member info.
|
||||||
p := s.buildSnapshot(ctx, sdkVariants)
|
s.buildSnapshot(ctx, sdkVariants)
|
||||||
zip := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), p.Base(), p)
|
|
||||||
s.snapshotFile = android.OptionalPathForPath(zip)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *sdk) AndroidMkEntries() []android.AndroidMkEntries {
|
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{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return []android.AndroidMkEntries{android.AndroidMkEntries{
|
return []android.AndroidMkEntries{android.AndroidMkEntries{
|
||||||
Class: "FAKE",
|
Class: "FAKE",
|
||||||
OutputFile: s.snapshotFile,
|
OutputFile: s.snapshotFile,
|
||||||
DistFiles: android.MakeDefaultDistFiles(s.snapshotFile.Path()),
|
DistFiles: android.MakeDefaultDistFiles(s.snapshotFile.Path(), s.infoFile.Path()),
|
||||||
Include: "$(BUILD_PHONY_PACKAGE)",
|
Include: "$(BUILD_PHONY_PACKAGE)",
|
||||||
ExtraFooters: []android.AndroidMkExtraFootersFunc{
|
ExtraFooters: []android.AndroidMkExtraFootersFunc{
|
||||||
func(w io.Writer, name, prefix, moduleDir string) {
|
func(w io.Writer, name, prefix, moduleDir string) {
|
||||||
// Allow the sdk to be built by simply passing its name on the command line.
|
// Allow the sdk to be built by simply passing its name on the command line.
|
||||||
fmt.Fprintln(w, ".PHONY:", s.Name())
|
fmt.Fprintln(w, ".PHONY:", s.Name())
|
||||||
fmt.Fprintln(w, s.Name()+":", s.snapshotFile.String())
|
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())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
@@ -263,7 +263,10 @@ func TestSdkInstall(t *testing.T) {
|
|||||||
result := testSdkWithFs(t, sdk, nil)
|
result := testSdkWithFs(t, sdk, nil)
|
||||||
|
|
||||||
CheckSnapshot(t, result, "mysdk", "",
|
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 {
|
type EmbeddedPropertiesStruct struct {
|
||||||
|
@@ -142,6 +142,7 @@ func getSdkSnapshotBuildInfo(t *testing.T, result *android.TestResult, sdk *sdk)
|
|||||||
androidBpContents: sdk.GetAndroidBpContentsForTests(),
|
androidBpContents: sdk.GetAndroidBpContentsForTests(),
|
||||||
androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
|
androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
|
||||||
androidVersionedBpContents: sdk.GetVersionedAndroidBpContentsForTests(),
|
androidVersionedBpContents: sdk.GetVersionedAndroidBpContentsForTests(),
|
||||||
|
infoContents: sdk.GetInfoContentsForTests(),
|
||||||
snapshotTestCustomizations: map[snapshotTest]*snapshotTestCustomization{},
|
snapshotTestCustomizations: map[snapshotTest]*snapshotTestCustomization{},
|
||||||
targetBuildRelease: sdk.builderForTests.targetBuildRelease,
|
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)
|
type resultChecker func(t *testing.T, result *android.TestResult)
|
||||||
|
|
||||||
// snapshotTestPreparer registers a preparer that will be used to customize the specified
|
// 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
|
// The contents of the versioned Android.bp file
|
||||||
androidVersionedBpContents string
|
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
|
// The paths, relative to the snapshot root, of all files and directories copied into the
|
||||||
// snapshot.
|
// snapshot.
|
||||||
snapshotContents []string
|
snapshotContents []string
|
||||||
|
150
sdk/update.go
150
sdk/update.go
@@ -15,6 +15,8 @@
|
|||||||
package sdk
|
package sdk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -219,9 +221,19 @@ func (s *sdk) collectMembers(ctx android.ModuleContext) {
|
|||||||
exportedComponentsInfo = ctx.OtherModuleProvider(child, android.ExportedComponentsInfoProvider).(android.ExportedComponentsInfo)
|
exportedComponentsInfo = ctx.OtherModuleProvider(child, android.ExportedComponentsInfoProvider).(android.ExportedComponentsInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var container android.SdkAware
|
||||||
|
if parent != ctx.Module() {
|
||||||
|
container = parent.(android.SdkAware)
|
||||||
|
}
|
||||||
|
|
||||||
export := memberTag.ExportMember()
|
export := memberTag.ExportMember()
|
||||||
s.memberVariantDeps = append(s.memberVariantDeps, sdkMemberVariantDep{
|
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
|
// 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
|
// 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.
|
// 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.
|
// Aggregate all the sdkMemberVariantDep instances from all the sdk variants.
|
||||||
hasLicenses := false
|
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
|
// 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
|
// finalized version of the snapshot that is intended to be kept separate from the
|
||||||
generateUnversioned := version == soongSdkSnapshotVersionUnversioned || version == soongSdkSnapshotVersionCurrent
|
generateUnversioned := version == soongSdkSnapshotVersionUnversioned || version == soongSdkSnapshotVersionCurrent
|
||||||
snapshotZipFileSuffix := ""
|
snapshotFileSuffix := ""
|
||||||
if generateVersioned {
|
if generateVersioned {
|
||||||
snapshotZipFileSuffix = "-" + version
|
snapshotFileSuffix = "-" + version
|
||||||
}
|
}
|
||||||
|
|
||||||
currentBuildRelease := latestBuildRelease()
|
currentBuildRelease := latestBuildRelease()
|
||||||
@@ -489,7 +501,7 @@ be unnecessary as every module in the sdk already has its own licenses property.
|
|||||||
filesToZip := builder.filesToZip
|
filesToZip := builder.filesToZip
|
||||||
|
|
||||||
// zip them all
|
// 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
|
outputZipFile := android.PathForModuleOut(ctx, zipPath).OutputPath
|
||||||
outputDesc := "Building snapshot for " + ctx.ModuleName()
|
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
|
zipFile = outputZipFile
|
||||||
desc = outputDesc
|
desc = outputDesc
|
||||||
} else {
|
} 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
|
zipFile = android.PathForModuleOut(ctx, intermediatePath).OutputPath
|
||||||
desc = "Building intermediate snapshot for " + ctx.ModuleName()
|
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
|
// 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()
|
return contents.content.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *sdk) GetInfoContentsForTests() string {
|
||||||
|
return s.builderForTests.infoContents
|
||||||
|
}
|
||||||
|
|
||||||
func (s *sdk) GetUnversionedAndroidBpContentsForTests() string {
|
func (s *sdk) GetUnversionedAndroidBpContentsForTests() string {
|
||||||
contents := &generatedContents{}
|
contents := &generatedContents{}
|
||||||
generateFilteredBpContents(contents, s.builderForTests.bpFile, func(module *bpModule) bool {
|
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.
|
// The target build release for which the snapshot is to be generated.
|
||||||
targetBuildRelease *buildRelease
|
targetBuildRelease *buildRelease
|
||||||
|
|
||||||
|
infoContents string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *snapshotBuilder) CopyToSnapshot(src android.Path, dest 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.
|
// The variant that is added to the sdk.
|
||||||
variant android.SdkAware
|
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.
|
// True if the member should be exported, i.e. accessible, from outside the sdk.
|
||||||
export bool
|
export bool
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user