1 /*
<lambda>null2  * Copyright 2022 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 androidx.build.dackka
18 
19 import com.google.gson.Gson
20 import com.google.gson.GsonBuilder
21 import java.io.File
22 import java.io.FileWriter
23 import java.util.zip.ZipFile
24 import org.gradle.api.DefaultTask
25 import org.gradle.api.artifacts.component.ComponentArtifactIdentifier
26 import org.gradle.api.artifacts.component.ModuleComponentIdentifier
27 import org.gradle.api.file.RegularFileProperty
28 import org.gradle.api.provider.ListProperty
29 import org.gradle.api.tasks.CacheableTask
30 import org.gradle.api.tasks.Input
31 import org.gradle.api.tasks.InputFiles
32 import org.gradle.api.tasks.OutputFile
33 import org.gradle.api.tasks.PathSensitive
34 import org.gradle.api.tasks.PathSensitivity
35 import org.gradle.api.tasks.TaskAction
36 import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
37 
38 @CacheableTask
39 abstract class GenerateMetadataTask : DefaultTask() {
40 
41     /** List of artifacts to convert to JSON */
42     @Input abstract fun getArtifactIds(): ListProperty<ComponentArtifactIdentifier>
43 
44     /** List of files corresponding to artifacts in [getArtifactIds] */
45     @InputFiles
46     @PathSensitive(PathSensitivity.NONE)
47     abstract fun getArtifactFiles(): ListProperty<File>
48 
49     /** List of multiplatform artifacts to convert to JSON */
50     @Input abstract fun getMultiplatformArtifactIds(): ListProperty<ComponentArtifactIdentifier>
51 
52     /** List of files corresponding to artifacts in [getMultiplatformArtifactIds] */
53     @InputFiles
54     @PathSensitive(PathSensitivity.NONE)
55     abstract fun getMultiplatformArtifactFiles(): ListProperty<File>
56 
57     /** Location of the generated JSON file */
58     @get:OutputFile abstract val destinationFile: RegularFileProperty
59 
60     @TaskAction
61     fun generate() {
62         val entries =
63             createEntries(getArtifactIds().get(), getArtifactFiles().get(), multiplatform = false) +
64                 createEntries(
65                     getMultiplatformArtifactIds().get(),
66                     getMultiplatformArtifactFiles().get(),
67                     multiplatform = true
68                 )
69 
70         val gson =
71             if (DEBUG) {
72                 GsonBuilder().setPrettyPrinting().create()
73             } else {
74                 Gson()
75             }
76         val writer = FileWriter(destinationFile.get().toString())
77         gson.toJson(entries, writer)
78         writer.close()
79     }
80 
81     private fun createEntries(
82         ids: List<ComponentArtifactIdentifier>,
83         artifacts: List<File>,
84         multiplatform: Boolean
85     ): List<MetadataEntry> =
86         ids.indices.mapNotNull { i ->
87             val id = ids[i]
88             val file = artifacts[i]
89             // Only process artifact if it can be cast to ModuleComponentIdentifier.
90             //
91             // In practice, metadata is generated only for docs-public and not docs-tip-of-tree
92             // (where id.componentIdentifier is DefaultProjectComponentIdentifier).
93             if (id.componentIdentifier !is DefaultModuleComponentIdentifier) return@mapNotNull null
94 
95             // Created https://github.com/gradle/gradle/issues/21415 to track surfacing
96             // group / module / version in ComponentIdentifier
97             val componentId = (id.componentIdentifier as ModuleComponentIdentifier)
98 
99             // Fetch the list of files contained in the .jar file
100             val fileList =
101                 ZipFile(file).entries().toList().map {
102                     if (multiplatform) {
103                         // Paths for multiplatform will start with a directory for the platform
104                         // (e.g.
105                         // "commonMain"), while Dackka only sees the part of the path after this.
106                         it.name.substringAfter("/")
107                     } else {
108                         it.name
109                     }
110                 }
111 
112             MetadataEntry(
113                 groupId = componentId.group,
114                 artifactId = componentId.module,
115                 releaseNotesUrl = generateReleaseNotesUrl(componentId.group),
116                 jarContents = fileList
117             )
118         }
119 
120     private fun generateReleaseNotesUrl(groupId: String): String {
121         val library = groupId.removePrefix("androidx.").replace(".", "-")
122         return "/jetpack/androidx/releases/$library"
123     }
124 
125     companion object {
126         private const val DEBUG = false
127     }
128 }
129