Merge "export_proguard_spec for libs deps" into main am: b4cd93ccc0
am: 671d826bba
am: 69ecb6812f
am: fe966999ba
am: 1421dac199
Original change: https://android-review.googlesource.com/c/platform/build/soong/+/2691152 Change-Id: I2b7029ea7d610f541586029459bbaae7ddb1a839 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
commit
2444405e7a
6 changed files with 327 additions and 44 deletions
28
java/aar.go
28
java/aar.go
|
@ -30,7 +30,6 @@ import (
|
|||
)
|
||||
|
||||
type AndroidLibraryDependency interface {
|
||||
LibraryDependency
|
||||
ExportPackage() android.Path
|
||||
ResourcesNodeDepSet() *android.DepSet[*resourcesNode]
|
||||
RRODirsDepSet() *android.DepSet[rroDir]
|
||||
|
@ -780,17 +779,9 @@ func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext)
|
|||
ctx.CheckbuildFile(a.aarFile)
|
||||
}
|
||||
|
||||
a.exportedProguardFlagFiles = append(a.exportedProguardFlagFiles,
|
||||
android.PathsForModuleSrc(ctx, a.dexProperties.Optimize.Proguard_flags_files)...)
|
||||
|
||||
ctx.VisitDirectDeps(func(m android.Module) {
|
||||
if ctx.OtherModuleDependencyTag(m) == staticLibTag {
|
||||
if lib, ok := m.(LibraryDependency); ok {
|
||||
a.exportedProguardFlagFiles = append(a.exportedProguardFlagFiles, lib.ExportedProguardFlagFiles()...)
|
||||
}
|
||||
}
|
||||
})
|
||||
a.exportedProguardFlagFiles = android.FirstUniquePaths(a.exportedProguardFlagFiles)
|
||||
proguardSpecInfo := a.collectProguardSpecInfo(ctx)
|
||||
ctx.SetProvider(ProguardSpecInfoProvider, proguardSpecInfo)
|
||||
a.exportedProguardFlagFiles = proguardSpecInfo.ProguardFlagsFiles.ToList()
|
||||
|
||||
prebuiltJniPackages := android.Paths{}
|
||||
ctx.VisitDirectDeps(func(module android.Module) {
|
||||
|
@ -941,10 +932,6 @@ var _ AndroidLibraryDependency = (*AARImport)(nil)
|
|||
func (a *AARImport) ExportPackage() android.Path {
|
||||
return a.exportPackage
|
||||
}
|
||||
func (a *AARImport) ExportedProguardFlagFiles() android.Paths {
|
||||
return android.Paths{a.proguardFlags}
|
||||
}
|
||||
|
||||
func (a *AARImport) ResourcesNodeDepSet() *android.DepSet[*resourcesNode] {
|
||||
return a.resourcesNodesDepSet
|
||||
}
|
||||
|
@ -1048,10 +1035,17 @@ func (a *AARImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
|
|||
|
||||
extractedAARDir := android.PathForModuleOut(ctx, "aar")
|
||||
a.classpathFile = extractedAARDir.Join(ctx, "classes-combined.jar")
|
||||
a.proguardFlags = extractedAARDir.Join(ctx, "proguard.txt")
|
||||
a.manifest = extractedAARDir.Join(ctx, "AndroidManifest.xml")
|
||||
aarRTxt := extractedAARDir.Join(ctx, "R.txt")
|
||||
a.assetsPackage = android.PathForModuleOut(ctx, "assets.zip")
|
||||
a.proguardFlags = extractedAARDir.Join(ctx, "proguard.txt")
|
||||
ctx.SetProvider(ProguardSpecInfoProvider, ProguardSpecInfo{
|
||||
ProguardFlagsFiles: android.NewDepSet[android.Path](
|
||||
android.POSTORDER,
|
||||
android.Paths{a.proguardFlags},
|
||||
nil,
|
||||
),
|
||||
})
|
||||
|
||||
ctx.Build(pctx, android.BuildParams{
|
||||
Rule: unzipAAR,
|
||||
|
|
10
java/app.go
10
java/app.go
|
@ -200,10 +200,6 @@ func (a *AndroidApp) IsInstallable() bool {
|
|||
return Bool(a.properties.Installable)
|
||||
}
|
||||
|
||||
func (a *AndroidApp) ExportedProguardFlagFiles() android.Paths {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AndroidApp) ResourcesNodeDepSet() *android.DepSet[*resourcesNode] {
|
||||
return a.aapt.resourcesNodesDepSet
|
||||
}
|
||||
|
@ -482,8 +478,10 @@ func (a *AndroidApp) aaptBuildActions(ctx android.ModuleContext) {
|
|||
func (a *AndroidApp) proguardBuildActions(ctx android.ModuleContext) {
|
||||
var staticLibProguardFlagFiles android.Paths
|
||||
ctx.VisitDirectDeps(func(m android.Module) {
|
||||
if lib, ok := m.(LibraryDependency); ok && ctx.OtherModuleDependencyTag(m) == staticLibTag {
|
||||
staticLibProguardFlagFiles = append(staticLibProguardFlagFiles, lib.ExportedProguardFlagFiles()...)
|
||||
depProguardInfo := ctx.OtherModuleProvider(m, ProguardSpecInfoProvider).(ProguardSpecInfo)
|
||||
staticLibProguardFlagFiles = append(staticLibProguardFlagFiles, depProguardInfo.UnconditionallyExportedProguardFlags.ToList()...)
|
||||
if ctx.OtherModuleDependencyTag(m) == staticLibTag {
|
||||
staticLibProguardFlagFiles = append(staticLibProguardFlagFiles, depProguardInfo.ProguardFlagsFiles.ToList()...)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
43
java/base.go
43
java/base.go
|
@ -1670,6 +1670,49 @@ func (j *Module) useCompose() bool {
|
|||
return android.InList("androidx.compose.runtime_runtime", j.properties.Static_libs)
|
||||
}
|
||||
|
||||
func (j *Module) collectProguardSpecInfo(ctx android.ModuleContext) ProguardSpecInfo {
|
||||
transitiveUnconditionalExportedFlags := []*android.DepSet[android.Path]{}
|
||||
transitiveProguardFlags := []*android.DepSet[android.Path]{}
|
||||
|
||||
ctx.VisitDirectDeps(func(m android.Module) {
|
||||
depProguardInfo := ctx.OtherModuleProvider(m, ProguardSpecInfoProvider).(ProguardSpecInfo)
|
||||
depTag := ctx.OtherModuleDependencyTag(m)
|
||||
|
||||
if depProguardInfo.UnconditionallyExportedProguardFlags != nil {
|
||||
transitiveUnconditionalExportedFlags = append(transitiveUnconditionalExportedFlags, depProguardInfo.UnconditionallyExportedProguardFlags)
|
||||
transitiveProguardFlags = append(transitiveProguardFlags, depProguardInfo.UnconditionallyExportedProguardFlags)
|
||||
}
|
||||
|
||||
if depTag == staticLibTag && depProguardInfo.ProguardFlagsFiles != nil {
|
||||
transitiveProguardFlags = append(transitiveProguardFlags, depProguardInfo.ProguardFlagsFiles)
|
||||
}
|
||||
})
|
||||
|
||||
directUnconditionalExportedFlags := android.Paths{}
|
||||
proguardFlagsForThisModule := android.PathsForModuleSrc(ctx, j.dexProperties.Optimize.Proguard_flags_files)
|
||||
exportUnconditionally := proptools.Bool(j.dexProperties.Optimize.Export_proguard_flags_files)
|
||||
if exportUnconditionally {
|
||||
// if we explicitly export, then our unconditional exports are the same as our transitive flags
|
||||
transitiveUnconditionalExportedFlags = transitiveProguardFlags
|
||||
directUnconditionalExportedFlags = proguardFlagsForThisModule
|
||||
}
|
||||
|
||||
return ProguardSpecInfo{
|
||||
Export_proguard_flags_files: exportUnconditionally,
|
||||
ProguardFlagsFiles: android.NewDepSet[android.Path](
|
||||
android.POSTORDER,
|
||||
proguardFlagsForThisModule,
|
||||
transitiveProguardFlags,
|
||||
),
|
||||
UnconditionallyExportedProguardFlags: android.NewDepSet[android.Path](
|
||||
android.POSTORDER,
|
||||
directUnconditionalExportedFlags,
|
||||
transitiveUnconditionalExportedFlags,
|
||||
),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Returns a copy of the supplied flags, but with all the errorprone-related
|
||||
// fields copied to the regular build's fields.
|
||||
func enableErrorproneFlags(flags javaBuilderFlags) javaBuilderFlags {
|
||||
|
|
|
@ -71,6 +71,10 @@ type DexProperties struct {
|
|||
|
||||
// Specifies the locations of files containing proguard flags.
|
||||
Proguard_flags_files []string `android:"path"`
|
||||
|
||||
// If true, transitive reverse dependencies of this module will have this
|
||||
// module's proguard spec appended to their optimization action
|
||||
Export_proguard_flags_files *bool
|
||||
}
|
||||
|
||||
// Keep the data uncompressed. We always need uncompressed dex for execution,
|
||||
|
|
246
java/dex_test.go
246
java/dex_test.go
|
@ -15,6 +15,7 @@
|
|||
package java
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"android/soong/android"
|
||||
|
@ -327,7 +328,7 @@ func TestD8(t *testing.T) {
|
|||
fooD8.Args["d8Flags"], staticLibHeader.String())
|
||||
}
|
||||
|
||||
func TestProguardFlagsInheritance(t *testing.T) {
|
||||
func TestProguardFlagsInheritanceStatic(t *testing.T) {
|
||||
result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, `
|
||||
android_app {
|
||||
name: "app",
|
||||
|
@ -380,3 +381,246 @@ func TestProguardFlagsInheritance(t *testing.T) {
|
|||
android.AssertStringDoesContain(t, "expected tertiary_lib's proguard flags from inherited dep",
|
||||
appR8.Args["r8Flags"], "tertiary.flags")
|
||||
}
|
||||
|
||||
func TestProguardFlagsInheritance(t *testing.T) {
|
||||
directDepFlagsFileName := "direct_dep.flags"
|
||||
transitiveDepFlagsFileName := "transitive_dep.flags"
|
||||
bp := `
|
||||
android_app {
|
||||
name: "app",
|
||||
static_libs: ["androidlib"], // this must be static_libs to initate dexing
|
||||
platform_apis: true,
|
||||
}
|
||||
|
||||
android_library {
|
||||
name: "androidlib",
|
||||
static_libs: ["app_dep"],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "app_dep",
|
||||
%s: ["dep"],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "dep",
|
||||
%s: ["transitive_dep"],
|
||||
optimize: {
|
||||
proguard_flags_files: ["direct_dep.flags"],
|
||||
export_proguard_flags_files: %v,
|
||||
},
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "transitive_dep",
|
||||
optimize: {
|
||||
proguard_flags_files: ["transitive_dep.flags"],
|
||||
export_proguard_flags_files: %v,
|
||||
},
|
||||
}
|
||||
`
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
depType string
|
||||
depExportsFlagsFiles bool
|
||||
transitiveDepType string
|
||||
transitiveDepExportsFlagsFiles bool
|
||||
expectedFlagsFiles []string
|
||||
}{
|
||||
{
|
||||
name: "libs_export_libs_export",
|
||||
depType: "libs",
|
||||
depExportsFlagsFiles: true,
|
||||
transitiveDepType: "libs",
|
||||
transitiveDepExportsFlagsFiles: true,
|
||||
expectedFlagsFiles: []string{directDepFlagsFileName, transitiveDepFlagsFileName},
|
||||
},
|
||||
{
|
||||
name: "static_export_libs_export",
|
||||
depType: "static_libs",
|
||||
depExportsFlagsFiles: true,
|
||||
transitiveDepType: "libs",
|
||||
transitiveDepExportsFlagsFiles: true,
|
||||
expectedFlagsFiles: []string{directDepFlagsFileName, transitiveDepFlagsFileName},
|
||||
},
|
||||
{
|
||||
name: "libs_no-export_static_export",
|
||||
depType: "libs",
|
||||
depExportsFlagsFiles: false,
|
||||
transitiveDepType: "static_libs",
|
||||
transitiveDepExportsFlagsFiles: true,
|
||||
expectedFlagsFiles: []string{transitiveDepFlagsFileName},
|
||||
},
|
||||
{
|
||||
name: "static_no-export_static_export",
|
||||
depType: "static_libs",
|
||||
depExportsFlagsFiles: false,
|
||||
transitiveDepType: "static_libs",
|
||||
transitiveDepExportsFlagsFiles: true,
|
||||
expectedFlagsFiles: []string{directDepFlagsFileName, transitiveDepFlagsFileName},
|
||||
},
|
||||
{
|
||||
name: "libs_export_libs_no-export",
|
||||
depType: "libs",
|
||||
depExportsFlagsFiles: true,
|
||||
transitiveDepType: "libs",
|
||||
transitiveDepExportsFlagsFiles: false,
|
||||
expectedFlagsFiles: []string{directDepFlagsFileName},
|
||||
},
|
||||
{
|
||||
name: "static_export_libs_no-export",
|
||||
depType: "static_libs",
|
||||
depExportsFlagsFiles: true,
|
||||
transitiveDepType: "libs",
|
||||
transitiveDepExportsFlagsFiles: false,
|
||||
expectedFlagsFiles: []string{directDepFlagsFileName},
|
||||
},
|
||||
{
|
||||
name: "libs_no-export_static_no-export",
|
||||
depType: "libs",
|
||||
depExportsFlagsFiles: false,
|
||||
transitiveDepType: "static_libs",
|
||||
transitiveDepExportsFlagsFiles: false,
|
||||
expectedFlagsFiles: []string{},
|
||||
},
|
||||
{
|
||||
name: "static_no-export_static_no-export",
|
||||
depType: "static_libs",
|
||||
depExportsFlagsFiles: false,
|
||||
transitiveDepType: "static_libs",
|
||||
transitiveDepExportsFlagsFiles: false,
|
||||
expectedFlagsFiles: []string{directDepFlagsFileName, transitiveDepFlagsFileName},
|
||||
},
|
||||
{
|
||||
name: "libs_no-export_libs_export",
|
||||
depType: "libs",
|
||||
depExportsFlagsFiles: false,
|
||||
transitiveDepType: "libs",
|
||||
transitiveDepExportsFlagsFiles: true,
|
||||
expectedFlagsFiles: []string{transitiveDepFlagsFileName},
|
||||
},
|
||||
{
|
||||
name: "static_no-export_libs_export",
|
||||
depType: "static_libs",
|
||||
depExportsFlagsFiles: false,
|
||||
transitiveDepType: "libs",
|
||||
transitiveDepExportsFlagsFiles: true,
|
||||
expectedFlagsFiles: []string{directDepFlagsFileName, transitiveDepFlagsFileName},
|
||||
},
|
||||
{
|
||||
name: "libs_export_static_export",
|
||||
depType: "libs",
|
||||
depExportsFlagsFiles: true,
|
||||
transitiveDepType: "static_libs",
|
||||
transitiveDepExportsFlagsFiles: true,
|
||||
expectedFlagsFiles: []string{directDepFlagsFileName, transitiveDepFlagsFileName},
|
||||
},
|
||||
{
|
||||
name: "static_export_static_export",
|
||||
depType: "static_libs",
|
||||
depExportsFlagsFiles: true,
|
||||
transitiveDepType: "static_libs",
|
||||
transitiveDepExportsFlagsFiles: true,
|
||||
expectedFlagsFiles: []string{directDepFlagsFileName, transitiveDepFlagsFileName},
|
||||
},
|
||||
{
|
||||
name: "libs_no-export_libs_no-export",
|
||||
depType: "libs",
|
||||
depExportsFlagsFiles: false,
|
||||
transitiveDepType: "libs",
|
||||
transitiveDepExportsFlagsFiles: false,
|
||||
expectedFlagsFiles: []string{},
|
||||
},
|
||||
{
|
||||
name: "static_no-export_libs_no-export",
|
||||
depType: "static_libs",
|
||||
depExportsFlagsFiles: false,
|
||||
transitiveDepType: "libs",
|
||||
transitiveDepExportsFlagsFiles: false,
|
||||
expectedFlagsFiles: []string{directDepFlagsFileName},
|
||||
},
|
||||
{
|
||||
name: "libs_export_static_no-export",
|
||||
depType: "libs",
|
||||
depExportsFlagsFiles: true,
|
||||
transitiveDepType: "static_libs",
|
||||
transitiveDepExportsFlagsFiles: false,
|
||||
expectedFlagsFiles: []string{directDepFlagsFileName, transitiveDepFlagsFileName},
|
||||
},
|
||||
{
|
||||
name: "static_export_static_no-export",
|
||||
depType: "static_libs",
|
||||
depExportsFlagsFiles: true,
|
||||
transitiveDepType: "static_libs",
|
||||
transitiveDepExportsFlagsFiles: false,
|
||||
expectedFlagsFiles: []string{directDepFlagsFileName, transitiveDepFlagsFileName},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := android.GroupFixturePreparers(
|
||||
PrepareForTestWithJavaDefaultModules,
|
||||
android.FixtureMergeMockFs(android.MockFS{
|
||||
directDepFlagsFileName: nil,
|
||||
transitiveDepFlagsFileName: nil,
|
||||
}),
|
||||
).RunTestWithBp(t,
|
||||
fmt.Sprintf(
|
||||
bp,
|
||||
tc.depType,
|
||||
tc.transitiveDepType,
|
||||
tc.depExportsFlagsFiles,
|
||||
tc.transitiveDepExportsFlagsFiles,
|
||||
),
|
||||
)
|
||||
appR8 := result.ModuleForTests("app", "android_common").Rule("r8")
|
||||
|
||||
shouldHaveDepFlags := android.InList(directDepFlagsFileName, tc.expectedFlagsFiles)
|
||||
if shouldHaveDepFlags {
|
||||
android.AssertStringDoesContain(t, "expected deps's proguard flags",
|
||||
appR8.Args["r8Flags"], directDepFlagsFileName)
|
||||
} else {
|
||||
android.AssertStringDoesNotContain(t, "app did not expect deps's proguard flags",
|
||||
appR8.Args["r8Flags"], directDepFlagsFileName)
|
||||
}
|
||||
|
||||
shouldHaveTransitiveDepFlags := android.InList(transitiveDepFlagsFileName, tc.expectedFlagsFiles)
|
||||
if shouldHaveTransitiveDepFlags {
|
||||
android.AssertStringDoesContain(t, "expected transitive deps's proguard flags",
|
||||
appR8.Args["r8Flags"], transitiveDepFlagsFileName)
|
||||
} else {
|
||||
android.AssertStringDoesNotContain(t, "app did not expect transitive deps's proguard flags",
|
||||
appR8.Args["r8Flags"], transitiveDepFlagsFileName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProguardFlagsInheritanceAppImport(t *testing.T) {
|
||||
bp := `
|
||||
android_app {
|
||||
name: "app",
|
||||
static_libs: ["aarimport"], // this must be static_libs to initate dexing
|
||||
platform_apis: true,
|
||||
}
|
||||
|
||||
android_library {
|
||||
name: "androidlib",
|
||||
static_libs: ["aarimport"],
|
||||
}
|
||||
|
||||
android_library_import {
|
||||
name: "aarimport",
|
||||
aars: ["import.aar"],
|
||||
}
|
||||
`
|
||||
result := android.GroupFixturePreparers(
|
||||
PrepareForTestWithJavaDefaultModules,
|
||||
).RunTestWithBp(t, bp)
|
||||
|
||||
appR8 := result.ModuleForTests("app", "android_common").Rule("r8")
|
||||
android.AssertStringDoesContain(t, "expected aarimports's proguard flags",
|
||||
appR8.Args["r8Flags"], "proguard.txt")
|
||||
}
|
||||
|
|
40
java/java.go
40
java/java.go
|
@ -225,6 +225,23 @@ var (
|
|||
}, "jar_name", "partition", "main_class")
|
||||
)
|
||||
|
||||
type ProguardSpecInfo struct {
|
||||
// If true, proguard flags files will be exported to reverse dependencies across libs edges
|
||||
// If false, proguard flags files will only be exported to reverse dependencies across
|
||||
// static_libs edges.
|
||||
Export_proguard_flags_files bool
|
||||
|
||||
// TransitiveDepsProguardSpecFiles is a depset of paths to proguard flags files that are exported from
|
||||
// all transitive deps. This list includes all proguard flags files from transitive static dependencies,
|
||||
// and all proguard flags files from transitive libs dependencies which set `export_proguard_spec: true`.
|
||||
ProguardFlagsFiles *android.DepSet[android.Path]
|
||||
|
||||
// implementation detail to store transitive proguard flags files from exporting shared deps
|
||||
UnconditionallyExportedProguardFlags *android.DepSet[android.Path]
|
||||
}
|
||||
|
||||
var ProguardSpecInfoProvider = blueprint.NewProvider(ProguardSpecInfo{})
|
||||
|
||||
// JavaInfo contains information about a java module for use by modules that depend on it.
|
||||
type JavaInfo struct {
|
||||
// HeaderJars is a list of jars that can be passed as the javac classpath in order to link
|
||||
|
@ -310,11 +327,6 @@ type UsesLibraryDependency interface {
|
|||
ClassLoaderContexts() dexpreopt.ClassLoaderContextMap
|
||||
}
|
||||
|
||||
// Provides transitive Proguard flag files to downstream DEX jars.
|
||||
type LibraryDependency interface {
|
||||
ExportedProguardFlagFiles() android.Paths
|
||||
}
|
||||
|
||||
// TODO(jungjw): Move this to kythe.go once it's created.
|
||||
type xref interface {
|
||||
XrefJavaFiles() android.Paths
|
||||
|
@ -626,12 +638,6 @@ type Library struct {
|
|||
InstallMixin func(ctx android.ModuleContext, installPath android.Path) (extraInstallDeps android.Paths)
|
||||
}
|
||||
|
||||
var _ LibraryDependency = (*Library)(nil)
|
||||
|
||||
func (j *Library) ExportedProguardFlagFiles() android.Paths {
|
||||
return j.exportedProguardFlagFiles
|
||||
}
|
||||
|
||||
var _ android.ApexModule = (*Library)(nil)
|
||||
|
||||
// Provides access to the list of permitted packages from apex boot jars.
|
||||
|
@ -730,15 +736,9 @@ func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) {
|
|||
j.installFile = ctx.InstallFile(installDir, j.Stem()+".jar", j.outputFile, extraInstallDeps...)
|
||||
}
|
||||
|
||||
j.exportedProguardFlagFiles = append(j.exportedProguardFlagFiles,
|
||||
android.PathsForModuleSrc(ctx, j.dexProperties.Optimize.Proguard_flags_files)...)
|
||||
ctx.VisitDirectDeps(func(m android.Module) {
|
||||
if lib, ok := m.(LibraryDependency); ok && ctx.OtherModuleDependencyTag(m) == staticLibTag {
|
||||
j.exportedProguardFlagFiles = append(j.exportedProguardFlagFiles, lib.ExportedProguardFlagFiles()...)
|
||||
}
|
||||
})
|
||||
j.exportedProguardFlagFiles = android.FirstUniquePaths(j.exportedProguardFlagFiles)
|
||||
|
||||
proguardSpecInfo := j.collectProguardSpecInfo(ctx)
|
||||
ctx.SetProvider(ProguardSpecInfoProvider, proguardSpecInfo)
|
||||
j.exportedProguardFlagFiles = proguardSpecInfo.ProguardFlagsFiles.ToList()
|
||||
}
|
||||
|
||||
func (j *Library) DepsMutator(ctx android.BottomUpMutatorContext) {
|
||||
|
|
Loading…
Reference in a new issue