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 */
16package androidx.build.license
17
18import org.gradle.api.DefaultTask
19import org.gradle.api.GradleException
20import org.gradle.api.Project
21import org.gradle.api.artifacts.ExternalDependency
22import org.gradle.api.plugins.ExtraPropertiesExtension
23import org.gradle.api.tasks.TaskAction
24import java.io.File
25
26/**
27 * This task creates a configuration for the project that has all of its external dependencies
28 * and then ensures that those dependencies:
29 * a) come from prebuilts
30 * b) has a license file.
31 */
32open class CheckExternalDependencyLicensesTask : DefaultTask() {
33    @Suppress("unused")
34    @TaskAction
35    fun checkDependencies() {
36        val supportRoot = (project.rootProject.property("ext") as ExtraPropertiesExtension)
37                .get("supportRootFolder") as File
38        val prebuiltsRoot = File(supportRoot, "../../prebuilts").canonicalFile
39
40        val checkerConfig = project.configurations.getByName(CONFIG)
41
42        project
43                .configurations
44                .flatMap {
45                    it.allDependencies
46                            .filterIsInstance(ExternalDependency::class.java)
47                            .filterNot {
48                                it.group?.startsWith("com.android") == true
49                            }
50                            .filterNot {
51                                it.group?.startsWith("android.arch") == true
52                            }
53                            .filterNot {
54                                it.group?.startsWith("androidx") == true
55                            }
56                }
57                .forEach {
58                    checkerConfig.dependencies.add(it)
59                }
60        val missingLicenses = checkerConfig.resolve().filter {
61            findLicenseFile(it.canonicalFile, prebuiltsRoot) == null
62        }
63        if (missingLicenses.isNotEmpty()) {
64            val suggestions = missingLicenses.joinToString("\n") {
65                "$it does not have a license file. It should probably live in " +
66                        "${it.parentFile.parentFile}"
67            }
68            throw GradleException("""
69                Any external library referenced in the support library
70                build must have a LICENSE or NOTICE file next to it in the prebuilts.
71                The following libraries are missing it:
72                $suggestions
73                """.trimIndent())
74        }
75    }
76
77    private fun findLicenseFile(dependency: File, prebuiltsRoot: File): File? {
78        if (!dependency.absolutePath.startsWith(prebuiltsRoot.absolutePath)) {
79            throw GradleException("prebuilts should come from prebuilts folder. $dependency is" +
80                    " not there")
81        }
82        fun recurse(folder: File): File? {
83            if (folder == prebuiltsRoot) {
84                return null
85            }
86            if (!folder.isDirectory) {
87                return recurse(folder.parentFile)
88            }
89
90            val found = folder.listFiles().firstOrNull {
91                it.name.toUpperCase().startsWith("NOTICE")
92                        || it.name.toUpperCase().startsWith("LICENSE")
93            }
94            return found ?: recurse(folder.parentFile)
95        }
96        return recurse(dependency)
97    }
98
99    companion object {
100        private const val CONFIG = "allExternalDependencies"
101        const val ROOT_TASK_NAME = "checkExternalLicenses"
102        private const val PER_PROJECT_TASK_NAME = ROOT_TASK_NAME
103        fun configure(project: Project) {
104            val task = project.tasks.create(PER_PROJECT_TASK_NAME,
105                    CheckExternalDependencyLicensesTask::class.java)
106            project.configurations.create(CONFIG)
107            val rootTask = project.rootProject.tasks.findByName(ROOT_TASK_NAME)
108                    ?: project.rootProject.tasks.create(ROOT_TASK_NAME)
109            rootTask.dependsOn(task)
110        }
111    }
112}