• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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