1 /*
2 * Copyright (C) 2020 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
18
19 import com.android.tools.metalava.LibraryBuildInfoFile.Check
20 import com.google.gson.GsonBuilder
21 import org.gradle.api.DefaultTask
22 import org.gradle.api.GradleException
23 import org.gradle.api.Project
24 import org.gradle.api.provider.Property
25 import org.gradle.api.tasks.Input
26 import org.gradle.api.tasks.OutputFile
27 import org.gradle.api.tasks.TaskAction
28 import org.gradle.api.tasks.TaskProvider
29 import java.io.File
30 import java.util.concurrent.TimeUnit
31
32 const val CREATE_BUILD_INFO_TASK = "createBuildInfo"
33
34 abstract class CreateLibraryBuildInfoTask : DefaultTask() {
35 @get:Input
36 abstract val artifactId: Property<String>
37 @get:Input
38 abstract val groupId: Property<String>
39 @get:Input
40 abstract val version: Property<String>
41 @get:Input
42 abstract val sha: Property<String>
43
44 @get:OutputFile
45 abstract val outputFile: Property<File>
46
47 @get:OutputFile
48 abstract val aggregateOutputFile: Property<File>
49
50 @TaskAction
createFilenull51 fun createFile() {
52 val info = LibraryBuildInfoFile()
53 info.artifactId = artifactId.get()
54 info.groupId = groupId.get()
55 info.groupIdRequiresSameVersion = false
56 info.version = version.get()
57 info.path = "/"
58 info.sha = sha.get()
59 info.dependencies = arrayListOf()
60 info.checks = arrayListOf()
61 val gson = GsonBuilder().setPrettyPrinting().create()
62 val serializedInfo: String = gson.toJson(info)
63 outputFile.get().writeText(serializedInfo)
64
65 aggregateOutputFile.get().let {
66 it.writeText("{ \"artifacts\": [\n")
67 it.appendText(serializedInfo)
68 it.appendText("]}")
69 }
70 }
71 }
72
configureBuildInfoTasknull73 fun configureBuildInfoTask(
74 project: Project,
75 inCI: Boolean,
76 distributionDirectory: File
77 ): TaskProvider<CreateLibraryBuildInfoTask> {
78 return project.tasks.register(CREATE_BUILD_INFO_TASK, CreateLibraryBuildInfoTask::class.java) {
79 it.artifactId.set(project.provider<String> { project.name })
80 it.groupId.set(project.provider<String> { project.group as String })
81 it.version.set(project.provider<String> { project.version as String })
82 // Only set sha when in CI to keep local builds faster
83 it.sha.set(project.provider<String> { if (inCI) getGitSha(project.projectDir) else "" })
84 it.outputFile.set(project.provider<File> {
85 File(
86 distributionDirectory,
87 "build-info/${project.group}_${project.name}_build_info.txt"
88 )
89 })
90 it.aggregateOutputFile.set(project.provider<File> {
91 File(distributionDirectory, "androidx_aggregate_build_info.txt")}
92 )
93 }
94 }
95
getGitShanull96 fun getGitSha(directory: File): String {
97 val process = ProcessBuilder("git", "rev-parse", "--verify", "HEAD")
98 .directory(directory)
99 .redirectOutput(ProcessBuilder.Redirect.PIPE)
100 .redirectError(ProcessBuilder.Redirect.PIPE)
101 .start()
102 // Read output, waiting for process to finish, as needed
103 val stdout = process.inputStream.bufferedReader().readText()
104 val stderr = process.errorStream.bufferedReader().readText()
105 val message = stdout + stderr
106 // wait potentially a little bit longer in case Git was waiting for us to
107 // read its response before it exited
108 process.waitFor(10, TimeUnit.SECONDS)
109 if (stderr != "") {
110 throw GradleException("Unable to call git. Response was: $message")
111 }
112 check(process.exitValue() == 0) { "Nonzero exit value running git command." }
113 return stdout.trim()
114 }
115
116 /**
117 * Object outlining the format of a library's build info file.
118 * This object will be serialized to json.
119 * This file should match the corresponding class in Jetpad because
120 * this object will be serialized to json and the result will be parsed by Jetpad.
121 * DO NOT TOUCH.
122 *
123 * @property groupId library maven group Id
124 * @property artifactId library maven artifact Id
125 * @property version library maven version
126 * @property path local project directory path used for development, rooted at framework/support
127 * @property sha the sha of the latest commit to modify the library (aka a commit that
128 * touches a file within [path])
129 * @property groupIdRequiresSameVersion boolean that determines if all libraries with [groupId]
130 * have the same version
131 * @property dependencies a list of dependencies on other androidx libraries
132 * @property checks arraylist of [Check]s that is used by Jetpad
133 */
134 @Suppress("UNUSED")
135 class LibraryBuildInfoFile {
136 var groupId: String? = null
137 var artifactId: String? = null
138 var version: String? = null
139 var path: String? = null
140 var sha: String? = null
141 var groupIdRequiresSameVersion: Boolean? = null
142 var dependencies: ArrayList<Dependency> = arrayListOf()
143 var checks: ArrayList<Check> = arrayListOf()
144
145 /**
146 * @property isTipOfTree boolean that specifies whether the dependency is tip-of-tree
147 */
148 inner class Dependency {
149 var groupId: String? = null
150 var artifactId: String? = null
151 var version: String? = null
152 var isTipOfTree = false
153 }
154
155 inner class Check {
156 var name: String? = null
157 var passing = false
158 }
159 }