blob: 51710eaa78e768b888903bdb5d96aff94e348528 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.build
import androidx.build.SupportConfig.getSupportRoot
import androidx.build.gitclient.Commit
import androidx.build.gitclient.GitClientImpl
import androidx.build.gitclient.GitCommitRange
import androidx.build.gmaven.GMavenVersionChecker
import androidx.build.jetpad.LibraryBuildInfoFile
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.provider.Property
import com.google.gson.GsonBuilder
import java.io.File
import java.util.ArrayList
/**
* This task generates a library build information file containing the artifactId, groupId, and
* version of public androidx dependencies and release checklist of the library for consumption
* by the Jetpack Release Service (JetPad).
*/
open class CreateLibraryBuildInfoFileTask : DefaultTask() {
init {
group = "Help"
description = "Generates a file containing library build information serialized to json"
}
@OutputFile
val outputFile: Property<File> = project.objects.property(File::class.java)
private fun getLibraryBuildInfoFilename(): String {
return "${project.group}_${project.name}_build_info.txt"
}
/* Returns the local project directory without the full framework/support root directory path
* Note: `project.projectDir.toString().removePrefix(project.rootDir.toString())` does not work
* because the project rootDir is not guaranteed to be a substring of the projectDir
*/
private fun getProjectSpecificDirectory(): String {
return project.projectDir.toString().removePrefix(
getSupportRoot(project).toString())
}
/* Returns whether or not the groupId of the project requires the same version for all
* artifactIds.
*/
private fun requiresSameVersion(): Boolean {
val library =
project.extensions.findByType(AndroidXExtension::class.java)
return library?.mavenGroup?.requireSameVersion ?: false
}
/* For androidx release notes, the most common use case is to track and publish the last sha
* of the build that is released. Thus, we use frameworks/support to get the sha
*/
private fun getFrameworksSupportCommitShaAtHead(): String {
val supportRoot = getSupportRoot(project)
val commitList: List<Commit> = GitClientImpl(supportRoot, logger).getGitLog(
GitCommitRange(
fromExclusive = "",
untilInclusive = "HEAD",
n = 1
),
keepMerges = true,
fullProjectDir = supportRoot
)
if (commitList.size < 1) {
throw RuntimeException("Failed to find git commit for HEAD!")
}
return commitList.first().sha
}
private fun writeJsonToFile(info: LibraryBuildInfoFile) {
if (!project.getBuildInfoDirectory().exists()) {
if (!project.getBuildInfoDirectory().mkdirs()) {
throw RuntimeException("Failed to create " +
"output directory: ${project.getBuildInfoDirectory()}")
}
}
val resolvedOutputFile: File = outputFile.get()
if (!resolvedOutputFile.exists()) {
if (!resolvedOutputFile.createNewFile()) {
throw RuntimeException("Failed to create " +
"output dependency dump file: $outputFile")
}
}
// Create json object from the artifact instance
val gson = GsonBuilder().setPrettyPrinting().create()
val serializedInfo: String = gson.toJson(info)
resolvedOutputFile.writeText(serializedInfo)
}
private fun resolveAndCollectDependencies(): LibraryBuildInfoFile {
val libraryBuildInfoFile = LibraryBuildInfoFile()
libraryBuildInfoFile.artifactId = project.name.toString()
libraryBuildInfoFile.groupId = project.group.toString()
libraryBuildInfoFile.version = project.version.toString()
libraryBuildInfoFile.path = getProjectSpecificDirectory()
libraryBuildInfoFile.sha = getFrameworksSupportCommitShaAtHead()
libraryBuildInfoFile.groupIdRequiresSameVersion = requiresSameVersion()
val libraryDependencies = ArrayList<LibraryBuildInfoFile.Dependency>()
val checks = ArrayList<LibraryBuildInfoFile.Check>()
libraryBuildInfoFile.checks = checks
val publishedProjects = project.getProjectsMap()
val versionChecker = project.property("versionChecker") as GMavenVersionChecker
project.configurations.filter {
/* Ignore test configuration dependencies */
!it.name.contains("test", ignoreCase = true)
}.forEach { configuration ->
configuration.allDependencies.forEach { dep ->
// Only consider androidx dependencies
if (dep.group != null &&
dep.group.toString().startsWith("androidx.") &&
!dep.group.toString().startsWith("androidx.test")
) {
if ((dep is ProjectDependency && publishedProjects
.containsKey("${dep.group}:${dep.name}")) ||
dep is ExternalModuleDependency
) {
val androidXPublishedDependency = LibraryBuildInfoFile().Dependency()
androidXPublishedDependency.artifactId = dep.name.toString()
androidXPublishedDependency.groupId = dep.group.toString()
androidXPublishedDependency.version = dep.version.toString()
androidXPublishedDependency.isTipOfTree = dep is ProjectDependency
// Check GMaven to confirm that the pinned dependency is not an unreleased
// version
if (!androidXPublishedDependency.isTipOfTree &&
!versionChecker.isReleased(
androidXPublishedDependency.groupId,
androidXPublishedDependency.artifactId,
androidXPublishedDependency.version)) {
androidXPublishedDependency.isTipOfTree = true
}
addDependencyToListIfNotAlreadyAdded(
libraryDependencies,
androidXPublishedDependency
)
}
}
}
}
libraryBuildInfoFile.dependencies = libraryDependencies
return libraryBuildInfoFile
}
private fun addDependencyToListIfNotAlreadyAdded(
dependencyList: ArrayList<LibraryBuildInfoFile.Dependency>,
dependency: LibraryBuildInfoFile.Dependency
) {
for (existingDependency in dependencyList) {
if (existingDependency.groupId == dependency.groupId &&
existingDependency.artifactId == dependency.artifactId &&
existingDependency.version == dependency.version &&
existingDependency.isTipOfTree == dependency.isTipOfTree) {
return
}
}
dependencyList.add(dependency)
}
/**
* Task: createLibraryBuildInfoFile
* Iterates through each configuration of the project and builds the set of all dependencies.
* Then adds each dependency to the Artifact class as a project or prebuilt dependency. Finally,
* writes these dependencies to a json file as a json object.
*/
@TaskAction
fun createLibraryBuildInfoFile() {
val resolvedArtifact = resolveAndCollectDependencies()
writeJsonToFile(resolvedArtifact)
}
}