Merge changes Ie274263a,I45993324
* changes: Adds droidstubs support to sdk module Simplify building an SDK snapshot from the command line
This commit is contained in:
commit
62835fd85f
|
@ -164,6 +164,9 @@ type SnapshotBuilder interface {
|
|||
// to the zip
|
||||
CopyToSnapshot(src Path, dest string)
|
||||
|
||||
// Unzip the supplied zip into the snapshot relative directory destDir.
|
||||
UnzipToSnapshot(zipPath Path, destDir string)
|
||||
|
||||
// Get the AndroidBpFile for the snapshot.
|
||||
AndroidBpFile() GeneratedSnapshotFile
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ func init() {
|
|||
|
||||
android.RegisterModuleType("droidstubs", DroidstubsFactory)
|
||||
android.RegisterModuleType("droidstubs_host", DroidstubsHostFactory)
|
||||
|
||||
android.RegisterModuleType("prebuilt_stubs_sources", PrebuiltStubsSourcesFactory)
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -1163,6 +1165,7 @@ func (d *Droiddoc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
|
|||
//
|
||||
type Droidstubs struct {
|
||||
Javadoc
|
||||
android.SdkBase
|
||||
|
||||
properties DroidstubsProperties
|
||||
apiFile android.WritablePath
|
||||
|
@ -1208,6 +1211,7 @@ func DroidstubsFactory() android.Module {
|
|||
&module.Javadoc.properties)
|
||||
|
||||
InitDroiddocModule(module, android.HostAndDeviceSupported)
|
||||
android.InitSdkAwareModule(module)
|
||||
return module
|
||||
}
|
||||
|
||||
|
@ -1913,3 +1917,88 @@ func zipSyncCmd(ctx android.ModuleContext, rule *android.RuleBuilder,
|
|||
func zipSyncCleanupCmd(rule *android.RuleBuilder, srcJarDir android.ModuleOutPath) {
|
||||
rule.Command().Text("rm -rf").Text(srcJarDir.String())
|
||||
}
|
||||
|
||||
var _ android.PrebuiltInterface = (*PrebuiltStubsSources)(nil)
|
||||
|
||||
type PrebuiltStubsSourcesProperties struct {
|
||||
Srcs []string `android:"path"`
|
||||
}
|
||||
|
||||
type PrebuiltStubsSources struct {
|
||||
android.ModuleBase
|
||||
android.DefaultableModuleBase
|
||||
prebuilt android.Prebuilt
|
||||
android.SdkBase
|
||||
|
||||
properties PrebuiltStubsSourcesProperties
|
||||
|
||||
srcs android.Paths
|
||||
stubsSrcJar android.ModuleOutPath
|
||||
}
|
||||
|
||||
func (p *PrebuiltStubsSources) GenerateAndroidBuildActions(ctx android.ModuleContext) {
|
||||
p.srcs = android.PathsForModuleSrc(ctx, p.properties.Srcs)
|
||||
}
|
||||
|
||||
func (p *PrebuiltStubsSources) Prebuilt() *android.Prebuilt {
|
||||
return &p.prebuilt
|
||||
}
|
||||
|
||||
func (p *PrebuiltStubsSources) Name() string {
|
||||
return p.prebuilt.Name(p.ModuleBase.Name())
|
||||
}
|
||||
|
||||
func (p *PrebuiltStubsSources) Srcs() android.Paths {
|
||||
return append(android.Paths{}, p.srcs...)
|
||||
}
|
||||
|
||||
// prebuilt_stubs_sources imports a set of java source files as if they were
|
||||
// generated by droidstubs.
|
||||
//
|
||||
// By default, a prebuilt_stubs_sources has a single variant that expects a
|
||||
// set of `.java` files generated by droidstubs.
|
||||
//
|
||||
// Specifying `host_supported: true` will produce two variants, one for use as a dependency of device modules and one
|
||||
// for host modules.
|
||||
//
|
||||
// Intended only for use by sdk snapshots.
|
||||
func PrebuiltStubsSourcesFactory() android.Module {
|
||||
module := &PrebuiltStubsSources{}
|
||||
|
||||
module.AddProperties(&module.properties)
|
||||
|
||||
android.InitPrebuiltModule(module, &module.properties.Srcs)
|
||||
android.InitSdkAwareModule(module)
|
||||
InitDroiddocModule(module, android.HostAndDeviceSupported)
|
||||
return module
|
||||
}
|
||||
|
||||
func (d *Droidstubs) BuildSnapshot(sdkModuleContext android.ModuleContext, builder android.SnapshotBuilder) {
|
||||
stubsSrcJar := d.stubsSrcJar
|
||||
|
||||
snapshotRelativeDir := filepath.Join("java", d.Name()+"_stubs_sources")
|
||||
builder.UnzipToSnapshot(stubsSrcJar, snapshotRelativeDir)
|
||||
|
||||
name := d.Name()
|
||||
bp := builder.AndroidBpFile()
|
||||
bp.Printfln("prebuilt_stubs_sources {")
|
||||
bp.Indent()
|
||||
bp.Printfln("name: %q,", builder.VersionedSdkMemberName(name))
|
||||
bp.Printfln("sdk_member_name: %q,", name)
|
||||
bp.Printfln("srcs: [%q],", snapshotRelativeDir)
|
||||
bp.Dedent()
|
||||
bp.Printfln("}")
|
||||
bp.Printfln("")
|
||||
|
||||
// This module is for the case when the source tree for the unversioned module
|
||||
// doesn't exist (i.e. building in an unbundled tree). "prefer:" is set to false
|
||||
// so that this module does not eclipse the unversioned module if it exists.
|
||||
bp.Printfln("prebuilt_stubs_sources {")
|
||||
bp.Indent()
|
||||
bp.Printfln("name: %q,", name)
|
||||
bp.Printfln("srcs: [%q],", snapshotRelativeDir)
|
||||
bp.Printfln("prefer: false,")
|
||||
bp.Dedent()
|
||||
bp.Printfln("}")
|
||||
bp.Printfln("")
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ func testContext(bp string, fs map[string][]byte) *android.TestContext {
|
|||
ctx.RegisterModuleType("droiddoc", android.ModuleFactoryAdaptor(DroiddocFactory))
|
||||
ctx.RegisterModuleType("droiddoc_host", android.ModuleFactoryAdaptor(DroiddocHostFactory))
|
||||
ctx.RegisterModuleType("droiddoc_template", android.ModuleFactoryAdaptor(ExportedDroiddocDirFactory))
|
||||
ctx.RegisterModuleType("prebuilt_stubs_sources", android.ModuleFactoryAdaptor(PrebuiltStubsSourcesFactory))
|
||||
ctx.RegisterModuleType("java_sdk_library", android.ModuleFactoryAdaptor(SdkLibraryFactory))
|
||||
ctx.RegisterModuleType("java_sdk_library_import", android.ModuleFactoryAdaptor(sdkLibraryImportFactory))
|
||||
ctx.RegisterModuleType("override_android_app", android.ModuleFactoryAdaptor(OverrideAndroidAppModuleFactory))
|
||||
|
@ -207,6 +208,9 @@ func testContext(bp string, fs map[string][]byte) *android.TestContext {
|
|||
"cert/new_cert.pk8": nil,
|
||||
|
||||
"testdata/data": nil,
|
||||
|
||||
"stubs-sources/foo/Foo.java": nil,
|
||||
"stubs/sources/foo/Foo.java": nil,
|
||||
}
|
||||
|
||||
for k, v := range fs {
|
||||
|
@ -415,7 +419,7 @@ func TestPrebuilts(t *testing.T) {
|
|||
ctx, _ := testJava(t, `
|
||||
java_library {
|
||||
name: "foo",
|
||||
srcs: ["a.java"],
|
||||
srcs: ["a.java", ":stubs-source"],
|
||||
libs: ["bar", "sdklib"],
|
||||
static_libs: ["baz"],
|
||||
}
|
||||
|
@ -439,6 +443,11 @@ func TestPrebuilts(t *testing.T) {
|
|||
name: "sdklib",
|
||||
jars: ["b.jar"],
|
||||
}
|
||||
|
||||
prebuilt_stubs_sources {
|
||||
name: "stubs-source",
|
||||
srcs: ["stubs/sources/**/*.java"],
|
||||
}
|
||||
`)
|
||||
|
||||
javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
|
||||
|
@ -447,6 +456,19 @@ func TestPrebuilts(t *testing.T) {
|
|||
bazJar := ctx.ModuleForTests("baz", "android_common").Rule("combineJar").Output
|
||||
sdklibStubsJar := ctx.ModuleForTests("sdklib.stubs", "android_common").Rule("combineJar").Output
|
||||
|
||||
inputs := []string{}
|
||||
for _, p := range javac.BuildParams.Inputs {
|
||||
inputs = append(inputs, p.String())
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"a.java",
|
||||
"stubs/sources/foo/Foo.java",
|
||||
}
|
||||
if !reflect.DeepEqual(expected, inputs) {
|
||||
t.Errorf("foo inputs incorrect: expected %q, found %q", expected, inputs)
|
||||
}
|
||||
|
||||
if !strings.Contains(javac.Args["classpath"], barJar.String()) {
|
||||
t.Errorf("foo classpath %v does not contain %q", javac.Args["classpath"], barJar.String())
|
||||
}
|
||||
|
|
11
sdk/sdk.go
11
sdk/sdk.go
|
@ -16,6 +16,7 @@ package sdk
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/blueprint"
|
||||
|
@ -50,6 +51,8 @@ type sdkProperties struct {
|
|||
Java_libs []string
|
||||
// The list of native libraries in this SDK
|
||||
Native_shared_libs []string
|
||||
// The list of stub sources in this SDK
|
||||
Stubs_sources []string
|
||||
|
||||
Snapshot bool `blueprint:"mutated"`
|
||||
}
|
||||
|
@ -121,6 +124,13 @@ func (s *sdk) AndroidMkEntries() android.AndroidMkEntries {
|
|||
OutputFile: s.snapshotFile,
|
||||
DistFile: s.snapshotFile,
|
||||
Include: "$(BUILD_PHONY_PACKAGE)",
|
||||
ExtraFooters: []android.AndroidMkExtraFootersFunc{
|
||||
func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) {
|
||||
// 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())
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,6 +177,7 @@ type sdkMemberVesionedDepTag struct {
|
|||
func memberMutator(mctx android.BottomUpMutatorContext) {
|
||||
if m, ok := mctx.Module().(*sdk); ok {
|
||||
mctx.AddVariationDependencies(nil, sdkMemberDepTag, m.properties.Java_libs...)
|
||||
mctx.AddVariationDependencies(nil, sdkMemberDepTag, m.properties.Stubs_sources...)
|
||||
|
||||
targets := mctx.MultiTargets()
|
||||
for _, target := range targets {
|
||||
|
|
|
@ -45,6 +45,8 @@ func testSdkContext(t *testing.T, bp string) (*android.TestContext, android.Conf
|
|||
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))
|
||||
ctx.RegisterModuleType("droidstubs", android.ModuleFactoryAdaptor(java.DroidstubsFactory))
|
||||
ctx.RegisterModuleType("prebuilt_stubs_sources", android.ModuleFactoryAdaptor(java.PrebuiltStubsSourcesFactory))
|
||||
|
||||
// from cc package
|
||||
ctx.RegisterModuleType("cc_library", android.ModuleFactoryAdaptor(cc.LibraryFactory))
|
||||
|
@ -104,6 +106,8 @@ func testSdkContext(t *testing.T, bp string) (*android.TestContext, android.Conf
|
|||
"include/Test.h": nil,
|
||||
"aidl/foo/bar/Test.aidl": nil,
|
||||
"libfoo.so": nil,
|
||||
"stubs-sources/foo/bar/Foo.java": nil,
|
||||
"foo/bar/Foo.java": nil,
|
||||
})
|
||||
|
||||
return ctx, config
|
||||
|
@ -323,6 +327,39 @@ func TestBasicSdkWithCc(t *testing.T) {
|
|||
ensureListContains(t, pathsToStrings(cpplibForMyApex2.Rule("ld").Implicits), sdkMemberV2.String())
|
||||
}
|
||||
|
||||
// Note: This test does not verify that a droidstubs can be referenced, either
|
||||
// directly or indirectly from an APEX as droidstubs can never be a part of an
|
||||
// apex.
|
||||
func TestBasicSdkWithDroidstubs(t *testing.T) {
|
||||
testSdk(t, `
|
||||
sdk {
|
||||
name: "mysdk",
|
||||
stubs_sources: ["mystub"],
|
||||
}
|
||||
sdk_snapshot {
|
||||
name: "mysdk@10",
|
||||
stubs_sources: ["mystub_mysdk@10"],
|
||||
}
|
||||
prebuilt_stubs_sources {
|
||||
name: "mystub_mysdk@10",
|
||||
sdk_member_name: "mystub",
|
||||
srcs: ["stubs-sources/foo/bar/Foo.java"],
|
||||
}
|
||||
droidstubs {
|
||||
name: "mystub",
|
||||
srcs: ["foo/bar/Foo.java"],
|
||||
sdk_version: "none",
|
||||
system_modules: "none",
|
||||
}
|
||||
java_library {
|
||||
name: "myjavalib",
|
||||
srcs: [":mystub"],
|
||||
sdk_version: "none",
|
||||
system_modules: "none",
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestDepNotInRequiredSdks(t *testing.T) {
|
||||
testSdkError(t, `module "myjavalib".*depends on "otherlib".*that isn't part of the required SDKs:.*`, `
|
||||
sdk {
|
||||
|
@ -417,6 +454,7 @@ func TestSnapshot(t *testing.T) {
|
|||
name: "mysdk",
|
||||
java_libs: ["myjavalib"],
|
||||
native_shared_libs: ["mynativelib"],
|
||||
stubs_sources: ["myjavaapistubs"],
|
||||
}
|
||||
|
||||
java_library {
|
||||
|
@ -444,15 +482,26 @@ func TestSnapshot(t *testing.T) {
|
|||
system_shared_libs: [],
|
||||
stl: "none",
|
||||
}
|
||||
|
||||
droidstubs {
|
||||
name: "myjavaapistubs",
|
||||
srcs: ["foo/bar/Foo.java"],
|
||||
system_modules: "none",
|
||||
sdk_version: "none",
|
||||
}
|
||||
`)
|
||||
|
||||
var copySrcs []string
|
||||
var copyDests []string
|
||||
buildParams := ctx.ModuleForTests("mysdk", "android_common").Module().BuildParamsForTests()
|
||||
var zipBp android.BuildParams
|
||||
for _, bp := range buildParams {
|
||||
if bp.Rule.String() == "android/soong/android.Cp" {
|
||||
ruleString := bp.Rule.String()
|
||||
if ruleString == "android/soong/android.Cp" {
|
||||
copySrcs = append(copySrcs, bp.Input.String())
|
||||
copyDests = append(copyDests, bp.Output.Rel()) // rooted at the snapshot root
|
||||
} else if ruleString == "<local rule>:m.mysdk_android_common.snapshot" {
|
||||
zipBp = bp
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -472,6 +521,19 @@ func TestSnapshot(t *testing.T) {
|
|||
ensureListContains(t, copyDests, "arm64/include_gen/mynativelib/aidl/foo/bar/Test.h")
|
||||
ensureListContains(t, copyDests, "java/myjavalib.jar")
|
||||
ensureListContains(t, copyDests, "arm64/lib/mynativelib.so")
|
||||
|
||||
// Ensure that the droidstubs .srcjar as repackaged into a temporary zip file
|
||||
// and then merged together with the intermediate snapshot zip.
|
||||
snapshotCreationInputs := zipBp.Implicits.Strings()
|
||||
ensureListContains(t, snapshotCreationInputs,
|
||||
filepath.Join(buildDir, ".intermediates/mysdk/android_common/tmp/java/myjavaapistubs_stubs_sources.zip"))
|
||||
ensureListContains(t, snapshotCreationInputs,
|
||||
filepath.Join(buildDir, ".intermediates/mysdk/android_common/mysdk-current.unmerged.zip"))
|
||||
actual := zipBp.Output.String()
|
||||
expected := filepath.Join(buildDir, ".intermediates/mysdk/android_common/mysdk-current.zip")
|
||||
if actual != expected {
|
||||
t.Errorf("Expected snapshot output to be %q but was %q", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
var buildDir string
|
||||
|
|
|
@ -80,6 +80,16 @@ func (s *sdk) javaLibs(ctx android.ModuleContext) []android.SdkAware {
|
|||
return result
|
||||
}
|
||||
|
||||
func (s *sdk) stubsSources(ctx android.ModuleContext) []android.SdkAware {
|
||||
result := []android.SdkAware{}
|
||||
ctx.VisitDirectDeps(func(m android.Module) {
|
||||
if j, ok := m.(*java.Droidstubs); ok {
|
||||
result = append(result, j)
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// archSpecificNativeLibInfo represents an arch-specific variant of a native lib
|
||||
type archSpecificNativeLibInfo struct {
|
||||
name string
|
||||
|
@ -236,8 +246,8 @@ func (s *sdk) buildSnapshot(ctx android.ModuleContext) android.OutputPath {
|
|||
ctx: ctx,
|
||||
version: "current",
|
||||
snapshotDir: snapshotDir.OutputPath,
|
||||
androidBpFile: bp,
|
||||
filesToZip: []android.Path{bp.path},
|
||||
androidBpFile: bp,
|
||||
}
|
||||
|
||||
// copy exported AIDL files and stub jar files
|
||||
|
@ -246,6 +256,12 @@ func (s *sdk) buildSnapshot(ctx android.ModuleContext) android.OutputPath {
|
|||
m.BuildSnapshot(ctx, builder)
|
||||
}
|
||||
|
||||
// copy stubs sources
|
||||
stubsSources := s.stubsSources(ctx)
|
||||
for _, m := range stubsSources {
|
||||
m.BuildSnapshot(ctx, builder)
|
||||
}
|
||||
|
||||
// copy exported header files and stub *.so files
|
||||
nativeLibInfos := s.nativeMemberInfos(ctx)
|
||||
for _, info := range nativeLibInfos {
|
||||
|
@ -266,6 +282,15 @@ func (s *sdk) buildSnapshot(ctx android.ModuleContext) android.OutputPath {
|
|||
bp.Dedent()
|
||||
bp.Printfln("],") // java_libs
|
||||
}
|
||||
if len(stubsSources) > 0 {
|
||||
bp.Printfln("stubs_sources: [")
|
||||
bp.Indent()
|
||||
for _, m := range stubsSources {
|
||||
bp.Printfln("%q,", builder.VersionedSdkMemberName(m.Name()))
|
||||
}
|
||||
bp.Dedent()
|
||||
bp.Printfln("],") // stubs_sources
|
||||
}
|
||||
if len(nativeLibInfos) > 0 {
|
||||
bp.Printfln("native_shared_libs: [")
|
||||
bp.Indent()
|
||||
|
@ -284,16 +309,45 @@ func (s *sdk) buildSnapshot(ctx android.ModuleContext) android.OutputPath {
|
|||
filesToZip := builder.filesToZip
|
||||
|
||||
// zip them all
|
||||
zipFile := android.PathForModuleOut(ctx, ctx.ModuleName()+"-current.zip").OutputPath
|
||||
outputZipFile := android.PathForModuleOut(ctx, ctx.ModuleName()+"-current.zip").OutputPath
|
||||
outputRuleName := "snapshot"
|
||||
outputDesc := "Building snapshot for " + ctx.ModuleName()
|
||||
|
||||
// If there are no zips to merge then generate the output zip directly.
|
||||
// Otherwise, generate an intermediate zip file into which other zips can be
|
||||
// merged.
|
||||
var zipFile android.OutputPath
|
||||
var ruleName string
|
||||
var desc string
|
||||
if len(builder.zipsToMerge) == 0 {
|
||||
zipFile = outputZipFile
|
||||
ruleName = outputRuleName
|
||||
desc = outputDesc
|
||||
} else {
|
||||
zipFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"-current.unmerged.zip").OutputPath
|
||||
ruleName = "intermediate snapshot"
|
||||
desc = "Building intermediate snapshot for " + ctx.ModuleName()
|
||||
}
|
||||
|
||||
rb := android.NewRuleBuilder()
|
||||
rb.Command().
|
||||
BuiltTool(ctx, "soong_zip").
|
||||
FlagWithArg("-C ", builder.snapshotDir.String()).
|
||||
FlagWithRspFileInputList("-l ", filesToZip).
|
||||
FlagWithOutput("-o ", zipFile)
|
||||
rb.Build(pctx, ctx, "snapshot", "Building snapshot for "+ctx.ModuleName())
|
||||
rb.Build(pctx, ctx, ruleName, desc)
|
||||
|
||||
return zipFile
|
||||
if len(builder.zipsToMerge) != 0 {
|
||||
rb := android.NewRuleBuilder()
|
||||
rb.Command().
|
||||
BuiltTool(ctx, "merge_zips").
|
||||
Output(outputZipFile).
|
||||
Input(zipFile).
|
||||
Inputs(builder.zipsToMerge)
|
||||
rb.Build(pctx, ctx, outputRuleName, outputDesc)
|
||||
}
|
||||
|
||||
return outputZipFile
|
||||
}
|
||||
|
||||
func buildSharedNativeLibSnapshot(ctx android.ModuleContext, info *nativeLibInfo, builder android.SnapshotBuilder) {
|
||||
|
@ -404,8 +458,9 @@ type snapshotBuilder struct {
|
|||
ctx android.ModuleContext
|
||||
version string
|
||||
snapshotDir android.OutputPath
|
||||
filesToZip android.Paths
|
||||
androidBpFile *generatedFile
|
||||
filesToZip android.Paths
|
||||
zipsToMerge android.Paths
|
||||
}
|
||||
|
||||
func (s *snapshotBuilder) CopyToSnapshot(src android.Path, dest string) {
|
||||
|
@ -418,6 +473,25 @@ func (s *snapshotBuilder) CopyToSnapshot(src android.Path, dest string) {
|
|||
s.filesToZip = append(s.filesToZip, path)
|
||||
}
|
||||
|
||||
func (s *snapshotBuilder) UnzipToSnapshot(zipPath android.Path, destDir string) {
|
||||
ctx := s.ctx
|
||||
|
||||
// Repackage the zip file so that the entries are in the destDir directory.
|
||||
// This will allow the zip file to be merged into the snapshot.
|
||||
tmpZipPath := android.PathForModuleOut(ctx, "tmp", destDir+".zip").OutputPath
|
||||
rb := android.NewRuleBuilder()
|
||||
rb.Command().
|
||||
BuiltTool(ctx, "zip2zip").
|
||||
FlagWithInput("-i ", zipPath).
|
||||
FlagWithOutput("-o ", tmpZipPath).
|
||||
Flag("**/*:" + destDir)
|
||||
rb.Build(pctx, ctx, "repackaging "+destDir,
|
||||
"Repackaging zip file "+destDir+" for snapshot "+ctx.ModuleName())
|
||||
|
||||
// Add the repackaged zip file to the files to merge.
|
||||
s.zipsToMerge = append(s.zipsToMerge, tmpZipPath)
|
||||
}
|
||||
|
||||
func (s *snapshotBuilder) AndroidBpFile() android.GeneratedSnapshotFile {
|
||||
return s.androidBpFile
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue