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 package androidx.build.license
17 
18 import org.gradle.api.DefaultTask
19 import org.gradle.api.GradleException
20 import org.gradle.api.Project
21 import org.gradle.api.artifacts.ExternalDependency
22 import org.gradle.api.plugins.ExtraPropertiesExtension
23 import org.gradle.api.tasks.TaskAction
24 import 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  */
32 open class CheckExternalDependencyLicensesTask : DefaultTask() {
33     @Suppress("unused")
34     @TaskAction
checkDependenciesnull35     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 
findLicenseFilenull77     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
configurenull103         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 }