1 /*
<lambda>null2 * Copyright (C) 2023 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 package com.android.tools.metalava.buildinfo
18
19 import com.android.tools.metalava.buildinfo.LibraryBuildInfoFile.Check
20 import com.android.tools.metalava.version
21 import com.google.gson.GsonBuilder
22 import org.gradle.api.DefaultTask
23 import org.gradle.api.Project
24 import org.gradle.api.artifacts.Dependency
25 import org.gradle.api.artifacts.ProjectDependency
26 import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectComponentPublication
27 import org.gradle.api.provider.ListProperty
28 import org.gradle.api.provider.Property
29 import org.gradle.api.publish.maven.MavenPublication
30 import org.gradle.api.tasks.Input
31 import org.gradle.api.tasks.OutputFile
32 import org.gradle.api.tasks.TaskAction
33 import org.gradle.api.tasks.TaskProvider
34 import org.gradle.api.tasks.bundling.Zip
35 import java.io.File
36 import java.io.Serializable
37 import java.util.Objects
38
39 const val CREATE_BUILD_INFO_TASK = "createBuildInfo"
40
41 abstract class CreateLibraryBuildInfoTask : DefaultTask() {
42 @get:Input abstract val artifactId: Property<String>
43 @get:Input abstract val groupId: Property<String>
44 @get:Input abstract val version: Property<String>
45 @get:Input abstract val sha: Property<String>
46 @get:Input abstract val projectZipPath: Property<String>
47 @get:Input abstract val projectDirectoryRelativeToRootProject: Property<String>
48 @get:Input abstract val dependencyList: ListProperty<LibraryBuildInfoFile.Dependency>
49 @get:Input abstract val target: Property<String>
50
51 @get:OutputFile abstract val outputFile: Property<File>
52
53 @TaskAction
54 fun createFile() {
55 val info = LibraryBuildInfoFile()
56 info.artifactId = artifactId.get()
57 info.groupId = groupId.get()
58 info.groupIdRequiresSameVersion = true
59 info.version = version.get()
60 info.path = projectDirectoryRelativeToRootProject.get()
61 info.sha = sha.get()
62 info.projectZipPath = projectZipPath.get()
63 info.dependencies = dependencyList.get()
64 info.target = target.get()
65 info.checks = arrayListOf()
66 val gson = GsonBuilder().setPrettyPrinting().create()
67 val serializedInfo: String = gson.toJson(info)
68 outputFile.get().writeText(serializedInfo)
69 }
70 }
71
configureBuildInfoTasknull72 internal fun configureBuildInfoTask(
73 project: Project,
74 mavenPublication: MavenPublication,
75 inCI: Boolean,
76 distributionDirectory: File,
77 archiveTaskProvider: TaskProvider<Zip>
78 ) {
79 // Unfortunately, dependency information is only available through internal API
80 // (See https://github.com/gradle/gradle/issues/21345).
81 val dependencies =
82 (mavenPublication as ProjectComponentPublication).component.map {
83 it.usages.orEmpty().flatMap { it.dependencies }
84 }
85
86 val buildInfoTask = project.tasks.register(CREATE_BUILD_INFO_TASK, CreateLibraryBuildInfoTask::class.java) {
87 it.artifactId.set(project.provider { project.name })
88 it.groupId.set(project.provider { project.group as String })
89 it.version.set(project.version())
90 it.projectDirectoryRelativeToRootProject.set(
91 project.provider {
92 "/" + project.layout.projectDirectory.asFile.relativeTo(project.rootDir).toString()
93 }
94 )
95 // Only set sha when in CI to keep local builds faster
96 it.sha.set(project.provider { if (inCI) project.providers.exec {
97 it.workingDir = project.projectDir
98 it.commandLine("git", "rev-parse", "--verify", "HEAD")
99 }.standardOutput.asText.get().trim() else "" })
100 it.dependencyList.set(dependencies.map { it.asBuildInfoDependencies() })
101 it.projectZipPath.set(archiveTaskProvider.flatMap { task -> task.archiveFileName })
102 it.outputFile.set(
103 project.provider {
104 File(
105 distributionDirectory,
106 "build-info/${project.group}_${project.name}_build_info.txt"
107 )
108 }
109 )
110 // This should always be "metalava" unless the target changes
111 it.target.set("metalava")
112 }
113 project.configurations.consumable(BUILD_INFO_PROVIDER_CONFIGURATION) { configuration ->
114 configuration.attributes.setBuildInfoAttributes(project)
115 }
116 project.artifacts.add(BUILD_INFO_PROVIDER_CONFIGURATION, buildInfoTask)
117 }
118 private const val BUILD_INFO_PROVIDER_CONFIGURATION = "buildInfoProvider"
119
Listnull120 fun List<Dependency>.asBuildInfoDependencies() =
121 filter { it.group?.startsWith("com.android.tools.metalava") ?: false }
<lambda>null122 .map {
123 LibraryBuildInfoFile.Dependency().apply {
124 this.artifactId = it.name.toString()
125 this.groupId = it.group.toString()
126 this.version = it.version.toString()
127 this.isTipOfTree = it is ProjectDependency
128 }
129 }
130 .toHashSet()
<lambda>null131 .sortedWith(compareBy({ it.groupId }, { it.artifactId }, { it.version }))
132
133 /**
134 * Object outlining the format of a library's build info file. This object will be serialized to
135 * json. This file should match the corresponding class in Jetpad because this object will be
136 * serialized to json and the result will be parsed by Jetpad. DO NOT TOUCH.
137 *
138 * @property groupId library maven group Id
139 * @property artifactId library maven artifact Id
140 * @property version library maven version
141 * @property path local project directory path used for development, rooted at framework/support
142 * @property sha the sha of the latest commit to modify the library (aka a commit that touches a
143 * file within [path])
144 * @property groupIdRequiresSameVersion boolean that determines if all libraries with [groupId] have
145 * the same version
146 * @property dependencies a list of dependencies on other androidx libraries
147 * @property checks arraylist of [Check]s that is used by Jetpad
148 * @property target the target for metalava, used by Jetpad
149 */
150 class LibraryBuildInfoFile {
151 var groupId: String? = null
152 var artifactId: String? = null
153 var version: String? = null
154 var path: String? = null
155 var sha: String? = null
156 var projectZipPath: String? = null
157 var groupIdRequiresSameVersion: Boolean? = null
158 var dependencies: List<Dependency> = arrayListOf()
159 var checks: ArrayList<Check> = arrayListOf()
160 var target: String? = null
161
162 /** @property isTipOfTree boolean that specifies whether the dependency is tip-of-tree */
163 class Dependency : Serializable {
164 var groupId: String? = null
165 var artifactId: String? = null
166 var version: String? = null
167 var isTipOfTree = false
168
equalsnull169 override fun equals(other: Any?): Boolean {
170 if (this === other) return true
171 if (other == null || javaClass != other.javaClass) return false
172 other as Dependency
173 return isTipOfTree == other.isTipOfTree &&
174 groupId == other.groupId &&
175 artifactId == other.artifactId &&
176 version == other.version
177 }
178
hashCodenull179 override fun hashCode(): Int {
180 return Objects.hash(groupId, artifactId, version, isTipOfTree)
181 }
182
183 companion object {
184 private const val serialVersionUID = 346431634564L
185 }
186 }
187
188 inner class Check {
189 var name: String? = null
190 var passing = false
191 }
192 }
193