1/*
2 * Copyright 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.build
18
19import androidx.build.SupportConfig.INSTRUMENTATION_RUNNER
20import androidx.build.license.CheckExternalDependencyLicensesTask
21import com.android.build.gradle.LibraryExtension
22import com.android.build.gradle.internal.dsl.LintOptions
23import com.android.build.gradle.tasks.GenerateBuildConfig
24import net.ltgt.gradle.errorprone.ErrorProneBasePlugin
25import net.ltgt.gradle.errorprone.ErrorProneToolChain
26import org.gradle.api.JavaVersion
27import org.gradle.api.Plugin
28import org.gradle.api.Project
29import java.io.File
30
31/**
32 * Support library specific com.android.library plugin that sets common configurations needed for
33 * support library modules.
34 */
35class SupportAndroidLibraryPlugin : Plugin<Project> {
36
37    override fun apply(project: Project) {
38        val supportLibraryExtension = project.extensions.create("supportLibrary",
39                SupportLibraryExtension::class.java, project)
40        apply(project, supportLibraryExtension)
41        CheckExternalDependencyLicensesTask.configure(project)
42        val isCoreSupportLibrary = project.rootProject.name == "support"
43
44        project.afterEvaluate {
45            val library = project.extensions.findByType(LibraryExtension::class.java)
46                    ?: return@afterEvaluate
47
48            library.defaultConfig.minSdkVersion(supportLibraryExtension.minSdkVersion)
49
50            // Java 8 is only fully supported on API 24+ and not all Java 8 features are binary
51            // compatible with API < 24, so use Java 7 for both source AND target.
52            val javaVersion: JavaVersion
53            if (supportLibraryExtension.java8Library) {
54                if (library.defaultConfig.minSdkVersion.apiLevel < 24) {
55                    throw IllegalArgumentException("Libraries can only support Java 8 if "
56                            + "minSdkVersion is 24 or higher")
57                }
58                javaVersion = JavaVersion.VERSION_1_8
59            } else {
60                javaVersion = JavaVersion.VERSION_1_7
61            }
62
63            library.compileOptions.setSourceCompatibility(javaVersion)
64            library.compileOptions.setTargetCompatibility(javaVersion)
65
66            VersionFileWriterTask.setUpAndroidLibrary(project, library)
67            DiffAndDocs.registerAndroidProject(project, library, supportLibraryExtension)
68
69            library.libraryVariants.all { libraryVariant ->
70                if (libraryVariant.getBuildType().getName().equals("debug")) {
71                    @Suppress("DEPRECATION")
72                    val javaCompile = libraryVariant.javaCompile
73                    if (supportLibraryExtension.failOnUncheckedWarnings) {
74                        javaCompile.options.compilerArgs.add("-Xlint:unchecked")
75                    }
76                    if (supportLibraryExtension.failOnDeprecationWarnings) {
77                        javaCompile.options.compilerArgs.add("-Xlint:deprecation")
78                    }
79                    javaCompile.options.compilerArgs.add("-Werror")
80                }
81            }
82        }
83
84        project.apply(mapOf("plugin" to "com.android.library"))
85        project.apply(mapOf("plugin" to ErrorProneBasePlugin::class.java))
86
87        project.afterEvaluate {
88            project.tasks.all({
89                if (it is GenerateBuildConfig) {
90                    // Disable generating BuildConfig.java
91                    it.enabled = false
92                }
93            })
94        }
95
96        project.configurations.all { configuration ->
97            if (isCoreSupportLibrary && project.name != "annotations") {
98                // While this usually happens naturally due to normal project dependencies, force
99                // evaluation on the annotations project in case the below substitution is the only
100                // dependency to this project. See b/70650240 on what happens when this is missing.
101                project.evaluationDependsOn(":annotation")
102
103                // In projects which compile as part of the "core" support libraries (which include
104                // the annotations), replace any transitive pointer to the deployed Maven
105                // coordinate version of annotations with a reference to the local project. These
106                // usually originate from test dependencies and otherwise cause multiple copies on
107                // the classpath. We do not do this for non-"core" projects as they need to
108                // depend on the Maven coordinate variant.
109                configuration.resolutionStrategy.dependencySubstitution.apply {
110                    substitute(module("androidx.annotation:annotation"))
111                            .with(project(":annotation"))
112                }
113            }
114        }
115
116        val library = project.extensions.findByType(LibraryExtension::class.java)
117                ?: throw Exception("Failed to find Android extension")
118
119        library.compileSdkVersion(SupportConfig.CURRENT_SDK_VERSION)
120
121        library.buildToolsVersion = SupportConfig.BUILD_TOOLS_VERSION
122
123        // Update the version meta-data in each Manifest.
124        library.defaultConfig.addManifestPlaceholders(
125                mapOf("target-sdk-version" to SupportConfig.CURRENT_SDK_VERSION))
126
127        // Set test runner.
128        library.defaultConfig.testInstrumentationRunner = INSTRUMENTATION_RUNNER
129
130        library.testOptions.unitTests.isReturnDefaultValues = true
131
132        // Use a local debug keystore to avoid build server issues.
133        library.signingConfigs.findByName("debug")?.storeFile =
134                SupportConfig.getKeystore(project)
135
136        project.afterEvaluate {
137            setUpLint(library.lintOptions, SupportConfig.getLintBaseline(project),
138                    (supportLibraryExtension.mavenVersion?.isFinalApi()) ?: false)
139        }
140
141        project.tasks.getByName("uploadArchives").dependsOn("lintRelease")
142
143        setUpSoureJarTaskForAndroidProject(project, library)
144
145        val toolChain = ErrorProneToolChain.create(project)
146        project.dependencies.add("errorprone", ERROR_PRONE_VERSION)
147        library.libraryVariants.all { libraryVariant ->
148            if (libraryVariant.getBuildType().getName().equals("debug")) {
149                @Suppress("DEPRECATION")
150                libraryVariant.javaCompile.configureWithErrorProne(toolChain)
151            }
152        }
153    }
154}
155
156private fun setUpLint(lintOptions: LintOptions, baseline: File, verifyTranslations: Boolean) {
157    // Always lint check NewApi as fatal.
158    lintOptions.isAbortOnError = true
159    lintOptions.isIgnoreWarnings = true
160
161    // Skip lintVital tasks on assemble. We explicitly run lintRelease for libraries.
162    lintOptions.isCheckReleaseBuilds = false
163
164    // Write output directly to the console (and nowhere else).
165    lintOptions.textOutput("stderr")
166    lintOptions.textReport = true
167    lintOptions.htmlReport = false
168
169    // Format output for convenience.
170    lintOptions.isExplainIssues = true
171    lintOptions.isNoLines = false
172    lintOptions.isQuiet = true
173
174    lintOptions.fatal("NewApi")
175    lintOptions.fatal("ObsoleteSdkInt")
176    lintOptions.fatal("VisibleForTests")
177    lintOptions.fatal("NoHardKeywords")
178
179    if (verifyTranslations) {
180        lintOptions.fatal("MissingTranslation")
181    } else {
182        lintOptions.disable("MissingTranslation")
183    }
184
185    // Set baseline file for all legacy lint warnings.
186    if (baseline.exists()) {
187        lintOptions.baseline(baseline)
188    }
189}
190