Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  build.gradle   Sprache: unbekannt

 
rahmenlose Ansicht.gradle DruckansichtUnknown {[0] [0] [0]}Mathematik

plugins {
    alias(libs.plugins.dependency.analysis)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)
    alias(libs.plugins.python.envs.plugin)
    id 'org.mozilla.conventions.zip-test-reports'
    id 'org.mozilla.nimbus-gradle-plugin'
}

apply plugin: 'com.android.application'
apply plugin: 'kotlin-parcelize'
apply plugin: 'jacoco'

if (gradle.mozconfig.substs.MOZILLA_OFFICIAL) {
    apply plugin: 'com.google.android.gms.oss-licenses-plugin'
}

def isAppBundle = gradle.startParameter.taskNames.any { it.toLowerCase().contains("bundle") }

def versionCodeGradle = "$project.rootDir/tools/gradle/versionCode.gradle"
if (findProject(":geckoview") != null) {
    versionCodeGradle = "$project.rootDir/mobile/android/focus-android/tools/gradle/versionCode.gradle"
}
apply from: versionCodeGradle

import com.android.build.api.variant.FilterConfiguration

android {
    if (project.hasProperty("testBuildType")) {
        // Allowing to configure the test build type via command line flag (./gradlew -PtestBuildType=beta ..)
        // in order to run UI tests against other build variants than debug in automation.
        testBuildType project.property("testBuildType")
    }

    compileSdk { version = release(config.compileSdkMajorVersion) { minorApiLevel = config.compileSdkMinorVersion } }

    defaultConfig {
        applicationId "org.mozilla"
        minSdk config.minSdkVersion
        targetSdk config.targetSdkVersion
        versionCode 11 // This versionCode is "frozen" for local builds. For "release" builds we
                       // override this with a generated versionCode at build time.
        // The versionName is dynamically overridden for all the build variants at build time.
        versionName Config.generateDebugVersionName()
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        testInstrumentationRunnerArguments clearPackageData: 'true'
        if (gradle.mozconfig.substs.MOZ_INCLUDE_SOURCE_INFO) {
            buildConfigField("String", "VCS_HASH", "\"" + gradle.mozconfig.source_repo.MOZ_SOURCE_STAMP + "\"")
        } else {
            buildConfigField("String", "VCS_HASH", "\"\"")
        }

        vectorDrawables.useSupportLibrary = true
    }

    bundle {
        language {
            // Because we have runtime language selection we will keep all strings and languages
            // in the base APKs.
            enableSplit = false
        }
    }

    lint {
        lintConfig = file("lint.xml")
        baseline = file("lint-baseline.xml")
        sarifReport = true
        sarifOutput = file("../build/reports/lint/lint-report.sarif.json")
        abortOnError = false
    }

    // We have a three dimensional build configuration:
    // BUILD TYPE (debug, release) X PRODUCT FLAVOR (focus, klar)

    buildTypes {
        release {
            // We allow disabling optimization by passing `-PdisableOptimization` to gradle. This is used
            // in automation for UI testing non-debug builds.
            shrinkResources = !project.hasProperty("disableOptimization")
            minifyEnabled = !project.hasProperty("disableOptimization")
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            matchingFallbacks = ['release']

            // If building locally, just use debug signing. In official automation builds, the
            // files are signed using a separate service and not within gradle.
            // Also skip during buildconfig linting, and skip if the `disableDebugSigning` Gradle property is set.
            if (!project.hasProperty("disableDebugSigning") && !gradle.hasProperty("localProperties.disableDebugSigning") && !gradle.mozconfig.substs.MOZ_AUTOMATION && System.getenv("MOZ_BUILD_CONFIG_LINT") != "1") {
                signingConfig = signingConfigs.debug
            }

            if (gradle.hasProperty("localProperties.debuggable") ||
                gradle.mozconfig.substs.MOZ_ANDROID_DEBUGGABLE) {
                println ("All builds will be debuggable")
                debuggable true
            }
        }
        debug {
            applicationIdSuffix ".debug"
            matchingFallbacks = ['debug']
        }
        beta {
            initWith release
            applicationIdSuffix ".beta"
            // This is used when the user selects text in other third-party apps. See https://github.com/mozilla-mobile/focus-android/issues/6478
            manifestPlaceholders = [textSelectionSearchAction: "@string/text_selection_search_action_focus_beta"]
        }
        nightly {
            initWith release
            applicationIdSuffix ".nightly"
            // This is used when the user selects text in other third-party apps. See https://github.com/mozilla-mobile/focus-android/issues/6478
            manifestPlaceholders = [textSelectionSearchAction: "@string/text_selection_search_action_focus_nightly"]
        }
    }
    testOptions {
        animationsDisabled = true
        execution = 'ANDROIDX_TEST_ORCHESTRATOR'
        testCoverage {
            jacocoVersion = libs.versions.jacoco.get()
        }
        unitTests {
            includeAndroidResources = true
        }
    }

    buildFeatures {
        compose = true
        viewBinding = true
        buildConfig = true
    }

    flavorDimensions.add("product")

    productFlavors {
        // In most countries we are Firefox Focus - but in some we need to be Firefox Klar
        focus {
            dimension "product"

            applicationIdSuffix ".focus"

            // This is used when the user selects text in other third-party apps. See https://github.com/mozilla-mobile/focus-android/issues/6478
            manifestPlaceholders = [textSelectionSearchAction: "@string/text_selection_search_action_focus"]
        }
        klar {
            dimension "product"

            applicationIdSuffix ".klar"

            // This is used when the user selects text in other third-party apps. See https://github.com/mozilla-mobile/focus-android/issues/6478
            manifestPlaceholders = [textSelectionSearchAction: "@string/text_selection_search_action_klar"]
        }
    }

    splits {
        abi {
            enable = !isAppBundle

            reset()

            include "armeabi-v7a", "arm64-v8a", "x86_64"
        }
    }

    sourceSets {
        test {
            resources {
                // Make the default asset folder available as test resource folder. Robolectric seems
                // to fail to read assets for our setup. With this we can just read the files directly
                // and do not need to rely on Robolectric.
                srcDir "${projectDir}/src/main/assets/"
            }
        }

        // Release
        focusRelease.root = 'src/focusRelease'
        klarRelease.root = 'src/klarRelease'

        // Debug
        focusDebug.root = 'src/focusDebug'
        klarDebug.root = 'src/klarDebug'

        // Nightly
        focusNightly.root = 'src/focusNightly'
        klarNightly.root = 'src/klarNightly'
    }

    // Exclude native libraries for unsupported architectures from third-party dependencies
    packaging {
        jniLibs {
            excludes += ["**/armeabi/*.so", "**/mips/*.so", "**/mips64/*.so", "**/x86/*.so"]
            if (!isAppBundle) {
                useLegacyPackaging = true
            }
        }
    }

    namespace = 'org.mozilla.focus'
}

// -------------------------------------------------------------------------------------------------
// Generate Kotlin code for the Focus Glean metrics.
// -------------------------------------------------------------------------------------------------
ext {
    // Enable expiration by major version.
    gleanExpireByVersion = 1
    gleanNamespace = "mozilla.telemetry.glean"
    gleanPythonEnvDir = gradle.mozconfig.substs.GRADLE_GLEAN_PARSER_VENV
    gleanYamlFiles = [
        "${project.projectDir}/metrics.yaml",
        "${project.projectDir}/legacy_metrics.yaml",
        "${project.projectDir}/pings.yaml",
        "${project.projectDir}/legacy_pings.yaml",
        "${project.projectDir}/tags.yaml",
    ]
}
apply plugin: "org.mozilla.telemetry.glean-gradle-plugin"

nimbus {
    // The path to the Nimbus feature manifest file
    manifestFile = "nimbus.fml.yaml"
    // Map from the variant name to the channel as experimenter and nimbus understand it.
    // If nimbus's channels were accurately set up well for this project, then this
    // shouldn't be needed.
    channels = [
            focusDebug: "debug",
            focusNightly: "nightly",
            focusBeta: "beta",
            focusRelease: "release",
            klarDebug: "debug",
            klarNightly: "nightly",
            klarBeta: "beta",
            klarRelease: "release",
    ]
    // This is generated by the FML and should be checked into git.
    // It will be fetched by Experimenter (the Nimbus experiment website)
    // and used to inform experiment configuration.
    experimenterManifest = ".experimenter.yaml"
}

dependencies {
    implementation project(':components:browser-domains')
    implementation project(':components:browser-engine-gecko')
    implementation project(':components:browser-errorpages')
    implementation project(':components:browser-icons')
    implementation project(':components:browser-menu')
    implementation project(':components:browser-state')
    implementation project(':components:browser-toolbar')
    implementation project(':components:compose-awesomebar')
    implementation project(':components:compose-base')
    implementation project(':components:compose-cfr')
    implementation project(':components:concept-awesomebar')
    implementation project(':components:concept-engine')
    implementation project(':components:concept-fetch')
    implementation project(':components:concept-menu')
    implementation project(':components:feature-app-links')
    implementation project(':components:feature-awesomebar')
    implementation project(':components:feature-contextmenu')
    implementation project(':components:feature-customtabs')
    implementation project(':components:feature-downloads')
    implementation project(':components:feature-findinpage')
    implementation project(':components:feature-intent')
    implementation project(':components:feature-media')
    implementation project(':components:feature-prompts')
    implementation project(':components:feature-search')
    implementation project(':components:feature-session')
    implementation project(':components:feature-sitepermissions')
    implementation project(':components:feature-tabs')
    implementation project(':components:feature-toolbar')
    implementation project(':components:feature-top-sites')
    implementation project(':components:feature-webcompat')
    implementation project(':components:feature-webcompat-reporter')
    implementation project(':components:lib-auth')
    implementation project(':components:lib-crash')
    implementation project(':components:lib-crash-sentry')
    implementation project(':components:lib-publicsuffixlist')
    implementation project(':components:lib-state')
    implementation project(":components:service-glean")
    implementation project(':components:service-location')
    implementation project(':components:service-nimbus')
    implementation project(':components:support-ktx')
    implementation project(':components:support-utils')
    implementation project(':components:support-remotesettings')
    implementation project(':components:support-appservices')
    implementation project(':components:support-license')
    implementation project(':components:support-locale')
    implementation project(':components:support-webextensions')
    implementation project(':components:ui-autocomplete')
    implementation project(':components:ui-colors')
    implementation project(':components:ui-icons')
    implementation project(':components:ui-tabcounter')
    implementation project(':components:ui-widgets')

    implementation libs.androidx.activity
    implementation libs.androidx.appcompat
    implementation libs.androidx.browser
    implementation libs.androidx.cardview
    implementation libs.androidx.collection
    implementation platform(libs.androidx.compose.bom)
    implementation libs.androidx.compose.foundation
    implementation libs.androidx.compose.material.icons
    implementation libs.androidx.compose.material3
    implementation libs.androidx.compose.runtime.livedata
    implementation libs.androidx.compose.ui
    implementation libs.androidx.compose.ui.tooling
    implementation libs.androidx.constraintlayout
    implementation libs.androidx.constraintlayout.compose
    implementation libs.androidx.core.ktx
    implementation libs.androidx.core.splashscreen
    implementation libs.androidx.datastore.preferences
    implementation libs.androidx.emoji2
    implementation libs.androidx.fragment
    implementation libs.androidx.lifecycle.process
    implementation libs.androidx.lifecycle.viewmodel
    implementation libs.androidx.palette
    implementation libs.androidx.preference
    implementation libs.androidx.recyclerview
    implementation libs.androidx.savedstate
    implementation libs.androidx.transition
    implementation libs.androidx.work.runtime
    implementation libs.google.material
    implementation libs.kotlinx.coroutines
    implementation libs.mozilla.glean
    implementation libs.play.review
    implementation libs.play.review.ktx
    implementation libs.sentry

    debugImplementation libs.leakcanary

    testImplementation project(':components:lib-fetch-okhttp')
    testImplementation project(':components:support-test')
    testImplementation project(':components:support-test-libstate')

    testImplementation libs.androidx.arch.core.testing
    testImplementation libs.androidx.test.core
    testImplementation libs.androidx.test.rules
    testImplementation libs.androidx.test.runner
    testImplementation libs.androidx.work.testing
    testImplementation platform(libs.junit.bom)
    testImplementation libs.junit.jupiter
    testRuntimeOnly libs.junit.platform.launcher
    testImplementation libs.kotlin.test
    testImplementation libs.kotlinx.coroutines.test
    testImplementation libs.mockito
    testImplementation libs.mockwebserver
    testImplementation libs.mozilla.glean.forUnitTests
    testImplementation libs.robolectric

    // We need a version constraint due to AGP silently trying to downgrade the version otherwise.
    // https://issuetracker.google.com/issues/349628366
    constraints {
        implementation(libs.androidx.concurrent)
    }
    androidTestImplementation project(':components:support-android-test')
    androidTestImplementation libs.androidx.concurrent
    androidTestImplementation libs.androidx.test.core
    androidTestImplementation libs.androidx.test.espresso.core
    androidTestImplementation libs.androidx.test.espresso.idling.resource
    androidTestImplementation libs.androidx.test.espresso.intents
    androidTestImplementation libs.androidx.test.espresso.web
    androidTestImplementation libs.androidx.test.junit
    androidTestImplementation libs.androidx.test.monitor
    androidTestImplementation libs.androidx.test.runner
    androidTestImplementation libs.androidx.test.uiautomator
    androidTestImplementation libs.kotlin.test
    androidTestImplementation libs.mockwebserver
    androidTestImplementation libs.okhttp
    androidTestRuntimeOnly libs.okio
    constraints {
        // Various AndroidX dependencies pull in 1.1.1 transitively; OkHttp 5 requires 1.2.0.
        implementation(libs.androidx.startup.runtime)
        // okhttp is test-only; prevents older releases being resolved on the main classpath.
        implementation(libs.okio)
    }

    androidTestUtil libs.androidx.test.orchestrator

    lintChecks project(':components:tooling-lint')
}
// -------------------------------------------------------------------------------------------------
//  Dynamically set versionCode (See tools/build/versionCode.gradle
// -------------------------------------------------------------------------------------------------

androidComponents {
    onVariants(selector().all()) { variant ->
        def buildType = variant.buildType

        project.logger.debug("----------------------------------------------")
        project.logger.debug("Variant name:      " + variant.name)
        project.logger.debug("Build type:        " + buildType)
        project.logger.debug("Flavor:            " + variant.flavorName)

        if (buildType == "release" || buildType == "nightly" || buildType == "beta") {
            def baseVersionCode = generatedVersionCode
            def versionName = buildType == "nightly" ?
                    "${Config.nightlyVersionName(project)}" :
                    "${Config.releaseVersionName(project)}"
            project.logger.debug("versionName override: $versionName")

            // The Google Play Store does not allow multiple APKs for the same app that all have the
            // same version code. Therefore we need to have different version codes for our ARM and x86
            // builds. See https://developer.android.com/studio/publish/versioning

            // Our generated version code now has a length of 9 (See tools/gradle/versionCode.gradle).
            // Our x86 builds need a higher version code to avoid installing ARM builds on an x86 device
            // with ARM compatibility mode.

            // AAB builds need a version code that is distinct from any APK builds. Since AAB and APK
            // builds may run in parallel, AAB and APK version codes might be based on the same
            // (minute granularity) time of day. To avoid conflicts, we ensure the minute portion
            // of the version code is even for APKs and odd for AABs.

            variant.outputs.each { output ->
                def abi = output.filters.find { it.filterType == FilterConfiguration.FilterType.ABI }?.identifier
                if (isAppBundle) {
                    abi = "AAB"
                }

                // We use the same version code generator, that we inherited from Fennec, across all channels - even on
                // channels that never shipped a Fennec build.

                // ensure baseVersionCode is an even number
                if (baseVersionCode % 2) {
                    baseVersionCode = baseVersionCode + 1
                }

                def versionCodeOverride = baseVersionCode

                if (abi == "AAB") {
                    // AAB version code is odd
                    versionCodeOverride = versionCodeOverride + 1
                } else if (abi == "x86_64") {
                    versionCodeOverride = versionCodeOverride + 6
                } else if (abi == "x86") {
                    versionCodeOverride = versionCodeOverride + 4
                } else if (abi == "arm64-v8a") {
                    versionCodeOverride = versionCodeOverride + 2
                } else if (abi == "armeabi-v7a") {
                    versionCodeOverride = versionCodeOverride + 0
                } else {
                    throw new RuntimeException("Unknown ABI: " + abi)
                }
                project.logger.debug("versionCode for $abi = $versionCodeOverride")

                output.versionName.set(versionName)
                output.versionCode.set(versionCodeOverride)
            }
        }
    }
}

// -------------------------------------------------------------------------------------------------
// MLS and Sentry: Read tokens from local files if they exist
// -------------------------------------------------------------------------------------------------

android.defaultConfig.with {
    try {
        def token = new File("${rootDir}/.mls_token").text.trim()
        buildConfigField 'String', 'MLS_TOKEN', '"' + token + '"'
    } catch (FileNotFoundException ignored) {
        buildConfigField 'String', 'MLS_TOKEN', '""'
    }

    try {
        def token = new File("${rootDir}/.sentry_token").text.trim()
        buildConfigField 'String', 'SENTRY_TOKEN', '"' + token + '"'
    } catch (FileNotFoundException ignored) {
        buildConfigField 'String', 'SENTRY_TOKEN', '""'
    }
}

// -------------------------------------------------------------------------------------------------
// L10N: Generate list of locales
// Focus provides its own (Android independent) locale switcher. That switcher requires a list
// of locale codes. We generate that list here to avoid having to manually maintain a list of locales:
// -------------------------------------------------------------------------------------------------

def getEnabledLocales() {
    def resDir = file('src/main/res')

    def potentialLanguageDirs = resDir.listFiles(new FilenameFilter() {
        @Override
        boolean accept(File dir, String name) {
            return name.startsWith("values-")
        }
    })

    def langs = potentialLanguageDirs.findAll {
        // Only select locales where strings.xml exists
        // Some locales might only contain e.g. sumo URLS in urls.xml, and should be skipped (see es vs es-ES/es-MX/etc)
        return  file(new File(it, "strings.xml")).exists()
    } .collect {
        // And reduce down to actual values-* names
        return it.name
    } .collect {
        return it.substring("values-".length())
    } .collect {
        if (it.length() > 3 && it.contains("-r")) {
            // Android resource dirs add an "r" prefix to the region - we need to strip that for java usage
            // Add 1 to have the index of the r, without the dash
            def regionPrefixPosition = it.indexOf("-r") + 1

            return it.substring(0, regionPrefixPosition) + it.substring(regionPrefixPosition + 1)
        } else {
            return it
        }
    }.collect {
        return '"' + it + '"'
    }

    // en-US is the default language (in "values") and therefore needs to be added separately
    langs << "\"en-US\""

    return langs.sort { it }
}

// -------------------------------------------------------------------------------------------------
// Nimbus: Read endpoint from local.properties of a local file if it exists
// -------------------------------------------------------------------------------------------------

// Non-debug builds use the .nimbus file; the debug build type uses a local.properties override.
android.defaultConfig.with {
    try {
        def url = new File("${rootDir}/.nimbus").text.trim()
        buildConfigField 'String', 'NIMBUS_ENDPOINT', '"' + url + '"'
    } catch (FileNotFoundException ignored) {
        buildConfigField 'String', 'NIMBUS_ENDPOINT', 'null'
    }
}

android.buildTypes.debug.with {
    if (gradle.hasProperty("localProperties.nimbus.remote-settings.url")) {
        def url = gradle.getProperty("localProperties.nimbus.remote-settings.url")
        buildConfigField 'String', 'NIMBUS_ENDPOINT', '"' + url + '"'
    } else {
        buildConfigField 'String', 'NIMBUS_ENDPOINT', 'null'
    }
}

abstract class GenerateLocaleList extends DefaultTask {
    @Input
    abstract ListProperty<String> getEnabledLocales()

    @OutputDirectory
    abstract DirectoryProperty getOutputDir()

    @TaskAction
    void generate() {
        def dir = new File(outputDir.get().asFile, "org/mozilla/focus/generated")
        dir.mkdirs()
        def localeList = new File(dir, "LocalesList.kt")
        localeList.delete()
        localeList.createNewFile()
        localeList << "/* This Source Code Form is subject to the terms of the Mozilla Public" << "\n"
        localeList << " * License, v. 2.0. If a copy of the MPL was not distributed with this" << "\n"
        localeList << " * file, You can obtain one at http://mozilla.org/MPL/2.0/. */" << "\n" << "\n"
        localeList << "package org.mozilla.focus.generated" << "\n" << "\n"
        localeList << "import java.util.Collections" << "\n"
        localeList << "\n"
        localeList << "/**"
        localeList << "\n"
        localeList << " * Provides a list of bundled locales based on the language files in the res folder."
        localeList << "\n"
        localeList << " */"
        localeList << "\n"
        localeList << "object LocalesList {" << "\n"
        localeList << "    " << "val BUNDLED_LOCALES: List<String> = Collections.unmodifiableList("
        localeList << "\n"
        localeList << "        " << "listOf("
        localeList << "\n"
        localeList << "            "
        localeList << enabledLocales.get().join(",\n" + "            ")
        localeList << ",\n"
        localeList << "        )," << "\n"
        localeList << "    )" << "\n"
        localeList << "}" << "\n"
    }
}

// Built at script scope so the closure resolves getEnabledLocales() to the script method rather
// than the task's same-named property accessor.
def enabledLocalesProvider = project.provider { getEnabledLocales() }

def generateLocaleList = tasks.register('generateLocaleList', GenerateLocaleList) {
    enabledLocales.set(enabledLocalesProvider)
}

androidComponents {
    onVariants(selector().all()) { variant ->
        variant.sources.java.addGeneratedSourceDirectory(generateLocaleList) { it.outputDir }
    }
}

// The variant Sources API wires compilation automatically, but DAGP's source exploder
// consumes the sources separately, so make it depend on the generator.
tasks.matching { it.name.startsWith("explodeCodeSource") }.configureEach { t ->
    t.dependsOn(generateLocaleList)
}

if (project.hasProperty("coverage")) {
    tasks.withType(Test).configureEach {
        jacoco.includeNoLocationClasses = true
        jacoco.excludes = ['jdk.internal.*']
    }

    androidComponents {
        onVariants(selector().all()) { variant ->
            tasks.register("jacoco${variant.name.capitalize()}TestReport", JacocoReport) {

                dependsOn(["test${variant.name.capitalize()}UnitTest"])
                reports {
                    html.required = true
                    xml.required = true
                }

                def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*',
                                  '**/*Test*.*', 'android/**/*.*', '**/*$[0-9].*']
                def kotlinTree = fileTree(dir: "$project.layout.buildDirectory/tmp/kotlin-classes/${variant.name}", excludes: fileFilter)
                def javaTree = fileTree(dir: "$project.layout.buildDirectory/intermediates/classes/${variant.flavorName ?: ''}/${variant.buildType}",
                        excludes: fileFilter)
                def mainSrc = "$project.projectDir/src/main/java"
                sourceDirectories.setFrom(files([mainSrc]))
                classDirectories.setFrom(files([kotlinTree, javaTree]))
                executionData.setFrom(fileTree(dir: project.layout.buildDirectory, includes: [
                        "jacoco/test${variant.name.capitalize()}UnitTest.exec", 'outputs/code-coverage/connected/*coverage.ec'
                ]))
            }
        }
    }

    android {
        buildTypes {
            debug {
                testCoverageEnabled = true
                applicationIdSuffix ".coverage"
            }
        }
    }
}

mozilla {
    ktlintSourcePaths = ["src/**/*.kt", "!**/build/**/*.kt"]
}

tasks.register("detekt", Exec) {
    group = "verification"
    description = "Run detekt static analysis."
    workingDir file("${gradle.mozconfig.topsrcdir}")
    commandLine "./mach", "gradle", "-p", "mobile/android/focus-android", ":detekt"
}

[zur Elbe Produktseite wechseln0.60Quellennavigators]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=752002