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