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