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
17
18import androidx.build.DiffAndDocs
19import androidx.build.PublishDocsRulesKt
20import androidx.build.gmaven.GMavenVersionChecker
21import androidx.build.license.CheckExternalDependencyLicensesTask
22import com.android.build.gradle.internal.coverage.JacocoReportTask
23import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask
24import org.gradle.api.logging.configuration.ShowStacktrace
25
26import javax.tools.ToolProvider
27
28def supportRoot = ext.supportRootFolder
29if (supportRoot == null) {
30    throw new RuntimeException("variable supportRootFolder is not set. you must set it before" +
31            " including this script")
32}
33def init = new Properties()
34ext.init = init
35rootProject.ext.versionChecker = new GMavenVersionChecker(rootProject.logger)
36ext.runningInBuildServer = System.env.DIST_DIR != null && System.env.OUT_DIR != null
37
38apply from: "${supportRoot}/buildSrc/dependencies.gradle"
39apply from: "${supportRoot}/buildSrc/build_dependencies.gradle"
40apply from: "${supportRoot}/buildSrc/unbundled_check.gradle"
41
42
43def enableDoclavaAndJDiff(p, dacOptions, rules = []) {
44    p.configurations {
45        doclava
46        jdiff
47    }
48
49    p.dependencies {
50        doclava build_libs.doclava
51        // tools.jar required for com.sun.javadoc
52        doclava files(((URLClassLoader) ToolProvider.getSystemToolClassLoader()).getURLs())
53        jdiff build_libs.jdiff
54        jdiff build_libs.xml_parser_apis
55        jdiff build_libs.xerces_impl
56    }
57
58    return DiffAndDocs.configureDiffAndDocs(rootProject, supportRootFolder,
59            dacOptions, rules)
60}
61
62def getFullSdkPath() {
63    if (isUnbundledBuild(ext.supportRootFolder)) {
64        Properties properties = new Properties()
65        File propertiesFile = new File('local.properties')
66        if (propertiesFile.exists()) {
67            propertiesFile.withInputStream {
68                properties.load(it)
69            }
70        }
71        File location = findSdkLocation(properties, supportRootFolder)
72        return location.getAbsolutePath()
73    } else {
74        final String osName = System.getProperty("os.name").toLowerCase();
75        final boolean isMacOsX =
76                osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx");
77        final String platform = isMacOsX ? 'darwin' : 'linux'
78        return "${repos.prebuiltsRoot}/fullsdk-${platform}"
79    }
80}
81
82/**
83 * Adapted from com.android.build.gradle.internal.SdkHandler
84 */
85public static File findSdkLocation(Properties properties, File rootDir) {
86    String sdkDirProp = properties.getProperty("sdk.dir");
87    if (sdkDirProp != null) {
88        File sdk = new File(sdkDirProp);
89        if (!sdk.isAbsolute()) {
90            sdk = new File(rootDir, sdkDirProp);
91        }
92        return sdk
93    }
94
95    sdkDirProp = properties.getProperty("android.dir");
96    if (sdkDirProp != null) {
97        return new File(rootDir, sdkDirProp);
98    }
99
100    String envVar = System.getenv("ANDROID_HOME");
101    if (envVar != null) {
102        return new File(envVar);
103    }
104
105    String property = System.getProperty("android.home");
106    if (property != null) {
107        return new File(property);
108    }
109    return null;
110}
111
112def setSdkInLocalPropertiesFile() {
113    final File fullSdkPath = file(getFullSdkPath())
114    if (fullSdkPath.exists()) {
115        project.ext.fullSdkPath = fullSdkPath
116        File props = file("local.properties")
117        props.write "sdk.dir=${fullSdkPath.getAbsolutePath()}"
118        ext.usingFullSdk = true
119    } else {
120        throw Exception("You are using non ub-supportlib-* checkout. You need to check out "
121                + "ub-supportlib-* to work on support library. See go/supportlib for details.")
122    }
123}
124
125def setupRepoOutAndBuildNumber() {
126    // common support repo folder which works well for prebuilts.
127    ext.supportRepoOut = ''
128    ext.buildNumber = "0"
129    /*
130     * With the build server you are given two env variables.
131     * The OUT_DIR is a temporary directory you can use to put things during the build.
132     * The DIST_DIR is where you want to save things from the build.
133     *
134     * The build server will copy the contents of DIST_DIR to somewhere and make it available.
135     */
136    if (ext.runningInBuildServer) {
137        buildDir = new File(System.env.OUT_DIR + '/gradle/frameworks/support/build')
138                .getCanonicalFile()
139        project.ext.distDir = new File(System.env.DIST_DIR).getCanonicalFile()
140
141        // the build server does not pass the build number so we infer it from the last folder of
142        // the dist path.
143        ext.buildNumber = project.ext.distDir.getName()
144
145        // the build server should always print out full stack traces for any failures.
146        gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS
147    } else {
148        buildDir = file("${ext.supportRootFolder}/../../out/host/gradle/frameworks/support/build")
149        project.ext.distDir = new File("${ext.supportRootFolder}/../../out/dist")
150    }
151    subprojects {
152        // Change buildDir first so that all plugins pick up the new value.
153        project.buildDir = new File("$project.parent.buildDir/../$project.name/build")
154    }
155    ext.supportRepoOut = new File(buildDir, 'support_repo')
156    ext.testApkDistOut = ext.distDir
157    ext.testResultsDistDir = new File(distDir, "host-test-reports")
158    ext.docsDir = new File(buildDir, 'javadoc')
159}
160
161def configureBuildOnServer() {
162    def buildOnServerTask = rootProject.tasks.create("buildOnServer")
163    rootProject.tasks.whenTaskAdded { task ->
164        if ("createArchive".equals(task.name)
165                || "createDiffArchive".equals(task.name)
166                || "distDocs".equals(task.name)
167                || "dejetifyArchive".equals(task.name)
168                || CheckExternalDependencyLicensesTask.ROOT_TASK_NAME.equals(task.name)) {
169            buildOnServerTask.dependsOn task
170        }
171    }
172    def docsProject = rootProject.findProject(":docs-fake")
173    subprojects {
174        if (docsProject == project) {
175            return
176        }
177        project.tasks.whenTaskAdded { task ->
178            if ("assembleErrorProne".equals(task.name)
179                    || "assembleAndroidTest".equals(task.name)
180                    || "assembleDebug".equals(task.name)) {
181                buildOnServerTask.dependsOn task
182            }
183        }
184    }
185    buildOnServerTask.dependsOn createJacocoAntUberJarTask()
186    return buildOnServerTask
187}
188
189def createJacocoAntUberJarTask() {
190    def myJacoco = project.configurations.create('myJacoco')
191    project.dependencies.add('myJacoco', build_libs.jacoco_ant)
192
193    return project.tasks.create(
194            name: "JacocoAntUberJar",
195            type: Jar) {
196        inputs.files myJacoco
197        from {
198            myJacoco
199                    .resolvedConfiguration
200                    .resolvedArtifacts.collect{ zipTree(it.getFile()) }} {
201            // exclude all the signatures the jar might have
202            exclude "META-INF/*.SF"
203            exclude "META-INF/*.DSA"
204            exclude "META-INF/*.RSA"
205        }
206        destinationDir file(project.distDir)
207        archiveName "jacocoant.jar"
208    }
209}
210
211def configureSubProjects() {
212    subprojects {
213        repos.addMavenRepositories(repositories)
214
215        // Only modify Android projects.
216        if (project.name.equals('noto-emoji-compat')) {
217            // disable tests and return
218            project.tasks.whenTaskAdded { task ->
219                if (task instanceof org.gradle.api.tasks.testing.Test) {
220                    task.enabled = false
221                }
222            }
223            return
224        }
225
226        project.plugins.whenPluginAdded { plugin ->
227            def isAndroidLibrary = "com.android.build.gradle.LibraryPlugin"
228                    .equals(plugin.class.name)
229            def isAndroidApp = "com.android.build.gradle.AppPlugin".equals(plugin.class.name)
230
231            if (isAndroidLibrary || isAndroidApp) {
232                // Enable code coverage for debug builds only if we are not running inside the IDE,
233                // since enabling coverage reports breaks the method parameter resolution in the IDE
234                // debugger.
235                project.android.buildTypes.debug.testCoverageEnabled =
236                        !project.hasProperty('android.injected.invoked.from.ide')
237
238                // Copy the class files in a jar to be later used to generate code coverage report
239                project.android.testVariants.all { v ->
240                    // check if the variant has any source files
241                    // and test coverage is enabled
242                    if (v.buildType.testCoverageEnabled
243                            && v.sourceSets.any { !it.java.sourceFiles.isEmpty() }) {
244                        def jarifyTask = project.tasks.create(
245                                name: "package${v.name.capitalize()}ClassFilesForCoverageReport",
246                                type: Jar) {
247                            from v.testedVariant.javaCompile.destinationDir
248                            exclude "**/R.class"
249                            exclude "**/R\$*.class"
250                            exclude "**/BuildConfig.class"
251                            destinationDir file(project.distDir)
252                            archiveName "${project.name}-${v.baseName}-allclasses.jar"
253                        }
254
255                        jarifyTask.dependsOn v.getJavaCompiler()
256                        v.assemble.dependsOn jarifyTask
257                    }
258                }
259            }
260        }
261
262        // Copy instrumentation test APKs and app APKs into the dist dir
263        // For test apks, they are uploaded only if we have java test sources.
264        // For regular app apks, they are uploaded only if they have java sources.
265        project.tasks.whenTaskAdded { task ->
266            if (task.name.startsWith("packageDebug")) {
267                def testApk = task.name.contains("AndroidTest")
268                task.doLast {
269                    def source = testApk ? project.android.sourceSets.androidTest
270                            : project.android.sourceSets.main
271                    def hasKotlinSources = false
272                    if (source.hasProperty('kotlin')) {
273                        if (!source.kotlin.files.isEmpty()) {
274                            hasKotlinSources = true
275                        } else {
276                            // kotlin files does not show in java sources due to the *.java filter
277                            // so we need to check them manually
278                            hasKotlinSources = source.java.sourceDirectoryTrees.any {
279                                !fileTree(dir: it.dir, include:'**/*.kt').files.isEmpty()
280                            }
281                        }
282                    }
283                    def hasSourceCode = !source.java.sourceFiles.isEmpty() || hasKotlinSources
284                    if (task.hasProperty("outputDirectory") && (hasSourceCode || !testApk)) {
285                        copy {
286                            from(task.outputDirectory)
287                            include '*.apk'
288                            into(rootProject.ext.testApkDistOut)
289                            rename { String fileName ->
290                                // Exclude media-compat-test-* modules from existing support library
291                                // presubmit tests.
292                                if (fileName.contains("media-compat-test")) {
293                                    fileName.replace("-debug-androidTest", "")
294                                } else {
295                                    // multiple modules may have the same name so prefix the name with
296                                    // the module's path to ensure it is unique.
297                                    // e.g. palette-v7-debug-androidTest.apk becomes
298                                    // support-palette-v7_palette-v7-debug-androidTest.apk
299                                    "${project.getPath().replace(':', '-').substring(1)}_${fileName}"
300                                }
301                            }
302                        }
303                    }
304                }
305            }
306        }
307
308        // copy host side test results to DIST
309        project.tasks.whenTaskAdded { task ->
310            if (task instanceof org.gradle.api.tasks.testing.Test) {
311                def junitReport = task.reports.junitXml
312                if (junitReport.enabled) {
313                    def zipTask = project.tasks.create(name : "zipResultsOf${task.name.capitalize()}", type : Zip) {
314                        destinationDir(testResultsDistDir)
315                        // first one is always :, drop it.
316                        archiveName("${project.getPath().split(":").join("_").substring(1)}.zip")
317                    }
318                    if (project.rootProject.ext.runningInBuildServer) {
319                        task.ignoreFailures = true
320                    }
321                    task.finalizedBy zipTask
322                    task.doFirst {
323                        zipTask.from(junitReport.destination)
324                    }
325                }
326            }
327        }
328
329        project.afterEvaluate { p ->
330            // remove dependency on the test so that we still get coverage even if some tests fail
331            p.tasks.findAll { it instanceof JacocoReportTask }.each { task ->
332                def toBeRemoved = new ArrayList()
333                def dependencyList = task.taskDependencies.values
334                dependencyList.each { dep ->
335                    if (dep instanceof String) {
336                        def t = tasks.findByName(dep)
337                        if (t instanceof DeviceProviderInstrumentTestTask) {
338                            toBeRemoved.add(dep)
339                            task.mustRunAfter(t)
340                        }
341                    }
342                }
343                toBeRemoved.each { dep ->
344                    dependencyList.remove(dep)
345                }
346            }
347        }
348    }
349}
350
351def setupRelease() {
352    apply from: "${ext.supportRootFolder}/buildSrc/release.gradle"
353}
354
355ext.init.enableDoclavaAndJDiff = this.&enableDoclavaAndJDiff
356ext.init.setSdkInLocalPropertiesFile = this.&setSdkInLocalPropertiesFile
357ext.init.setupRepoOutAndBuildNumber = this.&setupRepoOutAndBuildNumber
358ext.init.setupRelease = this.&setupRelease
359ext.init.configureSubProjects = this.&configureSubProjects
360ext.init.configureBuildOnServer = this.&configureBuildOnServer
361