1c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar/*
2c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar * Copyright 2018 The Android Open Source Project
3c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar *
4c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
5c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar * you may not use this file except in compliance with the License.
6c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar * You may obtain a copy of the License at
7c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar *
8c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
9c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar *
10c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar * Unless required by applicable law or agreed to in writing, software
11c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
12c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar * See the License for the specific language governing permissions and
14c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar * limitations under the License.
15c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar */
16526389b5eb93f99eaf4dba0b0c75b0b7df9a0f65Aurimas Liutikaspackage androidx.build.license
17c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar
18c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyarimport org.gradle.api.DefaultTask
19c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyarimport org.gradle.api.GradleException
20c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyarimport org.gradle.api.Project
21c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyarimport org.gradle.api.artifacts.ExternalDependency
22c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyarimport org.gradle.api.plugins.ExtraPropertiesExtension
23c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyarimport org.gradle.api.tasks.TaskAction
24c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyarimport java.io.File
25c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar
26c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar/**
27c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar * This task creates a configuration for the project that has all of its external dependencies
28c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar * and then ensures that those dependencies:
29c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar * a) come from prebuilts
30c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar * b) has a license file.
31c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar */
32c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyaropen class CheckExternalDependencyLicensesTask : DefaultTask() {
33c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar    @Suppress("unused")
34c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar    @TaskAction
35c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar    fun checkDependencies() {
36c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        val supportRoot = (project.rootProject.property("ext") as ExtraPropertiesExtension)
37c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                .get("supportRootFolder") as File
38c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        val prebuiltsRoot = File(supportRoot, "../../prebuilts").canonicalFile
39c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar
40c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        val checkerConfig = project.configurations.getByName(CONFIG)
41c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar
42c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        project
43c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                .configurations
44c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                .flatMap {
45c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                    it.allDependencies
46c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                            .filterIsInstance(ExternalDependency::class.java)
47c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                            .filterNot {
48c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                                it.group?.startsWith("com.android") == true
49c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                            }
50c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                            .filterNot {
51c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                                it.group?.startsWith("android.arch") == true
52c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                            }
53bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viverette                            .filterNot {
54bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viverette                                it.group?.startsWith("androidx") == true
55bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viverette                            }
56c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                }
57c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                .forEach {
58c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                    checkerConfig.dependencies.add(it)
59c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                }
60c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        val missingLicenses = checkerConfig.resolve().filter {
61c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            findLicenseFile(it.canonicalFile, prebuiltsRoot) == null
62c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        }
63c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        if (missingLicenses.isNotEmpty()) {
64c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            val suggestions = missingLicenses.joinToString("\n") {
65c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                "$it does not have a license file. It should probably live in " +
66c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                        "${it.parentFile.parentFile}"
67c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            }
68c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            throw GradleException("""
69c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                Any external library referenced in the support library
70c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                build must have a LICENSE or NOTICE file next to it in the prebuilts.
71c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                The following libraries are missing it:
72c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                $suggestions
73c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                """.trimIndent())
74c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        }
75c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar    }
76c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar
77c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar    private fun findLicenseFile(dependency: File, prebuiltsRoot: File): File? {
78c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        if (!dependency.absolutePath.startsWith(prebuiltsRoot.absolutePath)) {
79c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            throw GradleException("prebuilts should come from prebuilts folder. $dependency is" +
80c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                    " not there")
81c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        }
82c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        fun recurse(folder: File): File? {
83c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            if (folder == prebuiltsRoot) {
84c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                return null
85c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            }
86c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            if (!folder.isDirectory) {
87c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                return recurse(folder.parentFile)
88c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            }
89c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar
90c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            val found = folder.listFiles().firstOrNull {
91c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                it.name.toUpperCase().startsWith("NOTICE")
92c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                        || it.name.toUpperCase().startsWith("LICENSE")
93c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            }
94c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            return found ?: recurse(folder.parentFile)
95c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        }
96c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        return recurse(dependency)
97c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar    }
98c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar
99c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar    companion object {
100c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        private const val CONFIG = "allExternalDependencies"
101c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        const val ROOT_TASK_NAME = "checkExternalLicenses"
102c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        private const val PER_PROJECT_TASK_NAME = ROOT_TASK_NAME
103c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        fun configure(project: Project) {
104c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            val task = project.tasks.create(PER_PROJECT_TASK_NAME,
105c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                    CheckExternalDependencyLicensesTask::class.java)
106c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            project.configurations.create(CONFIG)
107c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            val rootTask = project.rootProject.tasks.findByName(ROOT_TASK_NAME)
108c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar                    ?: project.rootProject.tasks.create(ROOT_TASK_NAME)
109c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar            rootTask.dependsOn(task)
110c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar        }
111c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar    }
112c62cb5335923b041b7cd64cc879f9b0b889f457cYigit Boyar}