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 java.io.File
23 import java.io.Serializable
24 import java.util.Objects
25 import java.util.concurrent.TimeUnit
26 import org.gradle.api.DefaultTask
27 import org.gradle.api.GradleException
28 import org.gradle.api.Project
29 import org.gradle.api.artifacts.Dependency
30 import org.gradle.api.artifacts.ProjectDependency
31 import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectComponentPublication
32 import org.gradle.api.provider.ListProperty
33 import org.gradle.api.provider.Property
34 import org.gradle.api.publish.maven.MavenPublication
35 import org.gradle.api.tasks.Input
36 import org.gradle.api.tasks.OutputFile
37 import org.gradle.api.tasks.TaskAction
38 import org.gradle.api.tasks.TaskProvider
39 import org.gradle.api.tasks.bundling.Zip
40
41 const val CREATE_BUILD_INFO_TASK = "createBuildInfo"
42
43 abstract class CreateLibraryBuildInfoTask : DefaultTask() {
44 @get:Input abstract val artifactId: Property<String>
45 @get:Input abstract val groupId: Property<String>
46 @get:Input abstract val version: Property<String>
47 @get:Input abstract val sha: Property<String>
48 @get:Input abstract val projectZipPath: Property<String>
49 @get:Input abstract val projectDirectoryRelativeToRootProject: Property<String>
50 @get:Input abstract val dependencyList: ListProperty<LibraryBuildInfoFile.Dependency>
51
52 @get:OutputFile abstract val outputFile: Property<File>
53
54 @TaskAction
55 fun createFile() {
56 val info = LibraryBuildInfoFile()
57 info.artifactId = artifactId.get()
58 info.groupId = groupId.get()
59 info.groupIdRequiresSameVersion = true
60 info.version = version.get()
61 info.path = projectDirectoryRelativeToRootProject.get()
62 info.sha = sha.get()
63 info.projectZipPath = projectZipPath.get()
64 info.dependencies = dependencyList.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 ): TaskProvider<CreateLibraryBuildInfoTask> {
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 return 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 }
111 }
112
Listnull113 fun List<Dependency>.asBuildInfoDependencies() =
114 filter { it.group?.startsWith("com.android.tools.metalava") ?: false }
<lambda>null115 .map {
116 LibraryBuildInfoFile.Dependency().apply {
117 this.artifactId = it.name.toString()
118 this.groupId = it.group.toString()
119 this.version = it.version.toString()
120 this.isTipOfTree = it is ProjectDependency
121 }
122 }
123 .toHashSet()
<lambda>null124 .sortedWith(compareBy({ it.groupId }, { it.artifactId }, { it.version }))
125
126 /**
127 * Object outlining the format of a library's build info file. This object will be serialized to
128 * json. This file should match the corresponding class in Jetpad because this object will be
129 * serialized to json and the result will be parsed by Jetpad. DO NOT TOUCH.
130 *
131 * @property groupId library maven group Id
132 * @property artifactId library maven artifact Id
133 * @property version library maven version
134 * @property path local project directory path used for development, rooted at framework/support
135 * @property sha the sha of the latest commit to modify the library (aka a commit that touches a
136 * file within [path])
137 * @property groupIdRequiresSameVersion boolean that determines if all libraries with [groupId] have
138 * the same version
139 * @property dependencies a list of dependencies on other androidx libraries
140 * @property checks arraylist of [Check]s that is used by Jetpad
141 */
142 class LibraryBuildInfoFile {
143 var groupId: String? = null
144 var artifactId: String? = null
145 var version: String? = null
146 var path: String? = null
147 var sha: String? = null
148 var projectZipPath: String? = null
149 var groupIdRequiresSameVersion: Boolean? = null
150 var dependencies: List<Dependency> = arrayListOf()
151 var checks: ArrayList<Check> = arrayListOf()
152
153 /** @property isTipOfTree boolean that specifies whether the dependency is tip-of-tree */
154 class Dependency : Serializable {
155 var groupId: String? = null
156 var artifactId: String? = null
157 var version: String? = null
158 var isTipOfTree = false
159
equalsnull160 override fun equals(other: Any?): Boolean {
161 if (this === other) return true
162 if (other == null || javaClass != other.javaClass) return false
163 other as Dependency
164 return isTipOfTree == other.isTipOfTree &&
165 groupId == other.groupId &&
166 artifactId == other.artifactId &&
167 version == other.version
168 }
169
hashCodenull170 override fun hashCode(): Int {
171 return Objects.hash(groupId, artifactId, version, isTipOfTree)
172 }
173
174 companion object {
175 private const val serialVersionUID = 346431634564L
176 }
177 }
178
179 inner class Check {
180 var name: String? = null
181 var passing = false
182 }
183 }
184