1/*
2 * Copyright (C) 2017 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
17import com.android.build.gradle.internal.coverage.JacocoPlugin
18import com.android.build.gradle.internal.coverage.JacocoReportTask
19import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask
20import com.google.common.base.Charsets
21import com.google.common.io.Files
22
23import org.gradle.api.logging.configuration.ShowStacktrace
24
25def supportRoot = ext.supportRootFolder
26if (supportRoot == null) {
27    throw new RuntimeException("variable supportRootFolder is not set. you must set it before" +
28            " including this script")
29}
30def init = new Properties()
31ext.init = init
32ext.init.debugKeystore = file("${supportRoot}/development/keystore/debug.keystore")
33
34ext.runningInBuildServer = System.env.DIST_DIR != null && System.env.OUT_DIR != null
35
36apply from: "${supportRoot}/buildSrc/dependencies.gradle"
37ext.docs = [:]
38ext.docs.offline = rootProject.getProperties().containsKey("offlineDocs")
39ext.docs.dac = [
40        libraryroot: "android/support",
41        dataname: "SUPPORT_DATA"
42]
43
44def loadDefaultVersions() {
45    apply from: "${supportRootFolder}/buildSrc/versions.gradle"
46}
47
48def enableDoclavaAndJDiff(p) {
49    p.configurations {
50        doclava
51        jdiff
52    }
53
54    p.dependencies {
55        doclava project(':doclava')
56        jdiff project(':jdiff')
57        jdiff libs.xml_parser_apis
58        jdiff libs.xerces_impl
59    }
60    apply from: "${ext.supportRootFolder}/buildSrc/diff_and_docs.gradle"
61}
62
63def setSdkInLocalPropertiesFile() {
64    final String osName = System.getProperty("os.name").toLowerCase();
65    final boolean isMacOsX =
66            osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx");
67    final String platform = isMacOsX ? 'darwin' : 'linux'
68    System.setProperty('android.dir', "${supportRootFolder}/../../")
69    ext.buildToolsVersion = '26.0.0'
70    final String fullSdkPath = "${repos.prebuiltsRoot}/fullsdk-${platform}"
71    if (file(fullSdkPath).exists()) {
72        gradle.ext.currentSdk = 26
73        project.ext.androidJar =
74                files("${fullSdkPath}/platforms/android-${gradle.currentSdk}/android.jar")
75        project.ext.androidSrcJar =
76                file("${fullSdkPath}/platforms/android-${gradle.currentSdk}/android-stubs-src.jar")
77        project.ext.androidApiTxt = null
78        System.setProperty('android.home', "${repos.prebuiltsRoot}/fullsdk-${platform}")
79        File props = file("local.properties")
80        props.write "sdk.dir=${fullSdkPath}"
81        ext.usingFullSdk = true
82    } else {
83        gradle.ext.currentSdk = 'current'
84        project.ext.androidJar = files("${repos.prebuiltsRoot}/sdk/current/android.jar")
85        project.ext.androidSrcJar = null
86        project.ext.androidApiTxt = file("${repos.prebuiltsRoot}/sdk/api/26.txt")
87        File props = file("local.properties")
88        props.write "android.dir=../../"
89        ext.usingFullSdk = false
90    }
91}
92
93def setupRepoOutAndBuildNumber() {
94    ext.supportRepoOut = ''
95    ext.buildNumber = Integer.toString(ext.extraVersion)
96
97    /*
98     * With the build server you are given two env variables.
99     * The OUT_DIR is a temporary directory you can use to put things during the build.
100     * The DIST_DIR is where you want to save things from the build.
101     *
102     * The build server will copy the contents of DIST_DIR to somewhere and make it available.
103     */
104    if (ext.runningInBuildServer) {
105        buildDir = new File(System.env.OUT_DIR + '/gradle/frameworks/support/build')
106                .getCanonicalFile()
107        project.ext.distDir = new File(System.env.DIST_DIR).getCanonicalFile()
108
109        // the build server does not pass the build number so we infer it from the last folder of
110        // the dist path.
111        ext.buildNumber = project.ext.distDir.getName()
112
113        // the build server should always print out full stack traces for any failures.
114        gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS
115    } else {
116        buildDir = file("${ext.supportRootFolder}/../../out/host/gradle/frameworks/support/build")
117        project.ext.distDir = new File("${ext.supportRootFolder}/../../out/dist")
118    }
119    subprojects {
120        // Change buildDir first so that all plugins pick up the new value.
121        project.buildDir = new File("$project.parent.buildDir/../$project.name/build")
122    }
123    ext.supportRepoOut = new File(buildDir, 'support_repo')
124    ext.testApkDistOut = ext.distDir
125    ext.testResultsDistDir = new File(distDir, "host-test-reports")
126    ext.docsDir = new File(buildDir, 'javadoc')
127}
128
129def configureSubProjects() {
130    // lint every library
131    def lintTask = project.tasks.create("lint")
132    subprojects {
133        repos.addMavenRepositories(repositories)
134
135        // Only modify Android projects.
136        if (project.name.equals('doclava')
137                || project.name.equals('jdiff')
138                || project.name.equals('support-testutils')
139                || project.name.equals('noto-emoji-compat')) {
140            // disable tests and return
141            project.tasks.whenTaskAdded { task ->
142                if (task instanceof org.gradle.api.tasks.testing.Test) {
143                    task.enabled = false
144                }
145            }
146            return
147        }
148
149        // Current SDK is set in studioCompat.gradle.
150        project.ext.currentSdk = gradle.ext.currentSdk
151        apply plugin: 'maven'
152
153        version = rootProject.ext.supportVersion
154        group = 'com.android.support'
155
156        project.plugins.whenPluginAdded { plugin ->
157            def isAndroidLibrary = "com.android.build.gradle.LibraryPlugin"
158                    .equals(plugin.class.name)
159            def isAndroidApp = "com.android.build.gradle.AppPlugin".equals(plugin.class.name)
160            def isJavaLibrary = "org.gradle.api.plugins.JavaPlugin".equals(plugin.class.name)
161
162            if (isAndroidLibrary || isAndroidApp) {
163                project.android.buildToolsVersion = rootProject.buildToolsVersion
164
165                // Enable code coverage for debug builds only if we are not running inside the IDE,
166                // since enabling coverage reports breaks the method parameter resolution in the IDE
167                // debugger.
168                project.android.buildTypes.debug.testCoverageEnabled =
169                        !project.hasProperty('android.injected.invoked.from.ide')
170
171                // Copy the class files in a jar to be later used to generate code coverage report
172                project.android.testVariants.all { v ->
173                    // check if the variant has any source files
174                    // and test coverage is enabled
175                    if (v.buildType.testCoverageEnabled
176                            && v.sourceSets.any { !it.java.sourceFiles.isEmpty() }) {
177                        def jarifyTask = project.tasks.create(
178                                name: "package${v.name.capitalize()}ClassFilesForCoverageReport",
179                                type: Jar) {
180                            from v.testedVariant.javaCompile.destinationDir
181                            exclude "**/R.class"
182                            exclude "**/R\$*.class"
183                            exclude "**/BuildConfig.class"
184                            destinationDir file(project.distDir)
185                            archiveName "${project.archivesBaseName}-${v.baseName}-allclasses.jar"
186                        }
187                        def jacocoAntConfig =
188                                project.configurations[JacocoPlugin.ANT_CONFIGURATION_NAME]
189                        def jacocoAntArtifacts = jacocoAntConfig.resolvedConfiguration
190                                .resolvedArtifacts
191                        def version = jacocoAntArtifacts.find { "org.jacoco.ant".equals(it.name) }
192                                .moduleVersion.id.version
193                        def collectJacocoAntPackages = project.tasks.create(
194                                name: "collectJacocoAntPackages",
195                                type: Jar) {
196                            from(jacocoAntArtifacts.collect { zipTree(it.getFile()) }) {
197                                // exclude all the signatures the jar might have
198                                exclude "META-INF/*.SF"
199                                exclude "META-INF/*.DSA"
200                                exclude "META-INF/*.RSA"
201                            }
202                            destinationDir file(project.distDir)
203                            archiveName "jacocoant-" + version + ".jar"
204                        }
205                        jarifyTask.dependsOn v.getJavaCompiler()
206                        v.assemble.dependsOn jarifyTask, collectJacocoAntPackages
207                    }
208                }
209
210                // Enforce NewApi lint check as fatal.
211                project.android.lintOptions.check 'NewApi'
212                project.android.lintOptions.fatal 'NewApi'
213                lintTask.dependsOn project.lint
214            }
215
216            if (isAndroidLibrary || isJavaLibrary) {
217                // Add library to the aggregate dependency report.
218                task allDeps(type: DependencyReportTask) {}
219
220                project.afterEvaluate {
221                    Upload uploadTask = (Upload) project.tasks.uploadArchives;
222                    uploadTask.repositories.mavenDeployer {
223                        // Disable unique names for SNAPSHOTS so they can be updated in place.
224                        setUniqueVersion(false)
225                    }
226                    uploadTask.doLast {
227                        // Remove any invalid maven-metadata.xml files that may have been
228                        // created for SNAPSHOT versions that are *not* uniquely versioned.
229                        repositories.mavenDeployer.pom*.each { pom ->
230                            if (pom.version.endsWith('-SNAPSHOT')) {
231                                final File artifactDir = new File(
232                                        rootProject.ext.supportRepoOut,
233                                        pom.groupId.replace('.', '/')
234                                                + '/' + pom.artifactId
235                                                + '/' + pom.version)
236                                delete fileTree(dir: artifactDir,
237                                        include: 'maven-metadata.xml*')
238                            }
239                        }
240                    }
241
242                    uploadTask.repositories.mavenDeployer.pom*.whenConfigured { pom ->
243                        pom.dependencies.findAll { dep ->
244                            dep.groupId == 'com.android.support' &&
245                                    dep.artifactId != 'support-annotations'
246                        }*.type = 'aar'
247                    }
248
249                    // Before the upload, make sure the repo is ready.
250                    uploadTask.dependsOn rootProject.tasks.prepareRepo
251
252                    // Make the mainupload depend on this one.
253                    mainUpload.dependsOn uploadTask
254                }
255
256            }
257        }
258
259        // Copy instrumentation test APKs and app APKs into the dist dir
260        // For test apks, they are uploaded only if we have java test sources.
261        // For regular app apks, they are uploaded only if they have java sources.
262        project.tasks.whenTaskAdded { task ->
263            if (task.name.startsWith("packageDebug")) {
264                def testApk = task.name.contains("AndroidTest")
265                task.doLast {
266                    def source = testApk ? project.android.sourceSets.androidTest
267                            : project.android.sourceSets.main
268                    if (task.hasProperty("outputFile") && !source.java.sourceFiles.isEmpty()) {
269                        copy {
270                            from(task.outputFile)
271                            into(rootProject.ext.testApkDistOut)
272                            rename { String fileName ->
273                                // multiple modules may have the same name so prefix the name with
274                                // the module's path to ensure it is unique.
275                                // e.g. palette-v7-debug-androidTest.apk becomes
276                                // support-palette-v7_palette-v7-debug-androidTest.apk
277                                "${project.getPath().replace(':', '-').substring(1)}_${fileName}"
278                            }
279                        }
280                    }
281                }
282            }
283        }
284
285        // copy host side test results to DIST
286        project.tasks.whenTaskAdded { task ->
287            if (task instanceof org.gradle.api.tasks.testing.Test) {
288                def junitReport = task.reports.junitXml
289                if (junitReport.enabled) {
290                    def zipTask = project.tasks.create(name : "zipResultsOf${task.name.capitalize()}", type : Zip) {
291                        destinationDir(testResultsDistDir)
292                        // first one is always :, drop it.
293                        archiveName("${project.getPath().split(":").join("_").substring(1)}.zip")
294                    }
295                    if (project.rootProject.ext.runningInBuildServer) {
296                        task.ignoreFailures = true
297                    }
298                    task.finalizedBy zipTask
299                    task.doFirst {
300                        zipTask.from(junitReport.destination)
301                    }
302                }
303            }
304        }
305
306        project.afterEvaluate {
307            // The archivesBaseName isn't available initially, so set it now
308            def createZipTask = project.tasks.findByName("createSeparateZip")
309            if (createZipTask != null) {
310                createZipTask.appendix = archivesBaseName
311                createZipTask.from versionDir()
312            }
313        }
314
315        project.afterEvaluate { p ->
316            // remove dependency on the test so that we still get coverage even if some tests fail
317            p.tasks.findAll { it instanceof JacocoReportTask }.each { task ->
318                def toBeRemoved = new ArrayList()
319                def dependencyList = task.taskDependencies.values
320                dependencyList.each { dep ->
321                    if (dep instanceof String) {
322                        def t = tasks.findByName(dep)
323                        if (t instanceof DeviceProviderInstrumentTestTask) {
324                            toBeRemoved.add(dep)
325                            task.mustRunAfter(t)
326                        }
327                    }
328                }
329                toBeRemoved.each { dep ->
330                    dependencyList.remove(dep)
331                }
332            }
333        }
334    }
335}
336
337def setupRelease() {
338    apply from: "${ext.supportRootFolder}/buildSrc/release.gradle"
339}
340
341ext.init.enableDoclavaAndJDiff = this.&enableDoclavaAndJDiff
342ext.init.setSdkInLocalPropertiesFile = this.&setSdkInLocalPropertiesFile
343ext.init.setupRepoOutAndBuildNumber = this.&setupRepoOutAndBuildNumber
344ext.init.setupRelease = this.&setupRelease
345ext.init.loadDefaultVersions = this.&loadDefaultVersions
346ext.init.configureSubProjects = this.&configureSubProjects