1 /*
<lambda>null2  * Copyright 2018 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.navigation.safeargs.gradle
18 
19 import androidx.navigation.safe.args.generator.ErrorMessage
20 import androidx.navigation.safe.args.generator.generateSafeArgs
21 import com.android.build.gradle.internal.tasks.IncrementalTask
22 import com.android.ide.common.resources.FileStatus
23 import com.google.gson.Gson
24 import com.google.gson.reflect.TypeToken
25 import org.gradle.api.GradleException
26 import org.gradle.api.tasks.Input
27 import org.gradle.api.tasks.InputFiles
28 import org.gradle.api.tasks.OutputDirectory
29 import java.io.File
30 
31 private const val MAPPING_FILE = "file_mappings.json"
32 
33 open class ArgumentsGenerationTask : IncrementalTask() {
34     @get:Input
35     lateinit var rFilePackage: String
36 
37     @get:Input
38     lateinit var applicationId: String
39 
40     @get:OutputDirectory
41     lateinit var outputDir: File
42 
43     @get:InputFiles
44     var navigationFiles: List<File> = emptyList()
45 
46     private fun generateArgs(navFiles: Collection<File>, out: File) = navFiles.map { file ->
47         val output = generateSafeArgs(rFilePackage, applicationId, file, out)
48         Mapping(file.relativeTo(project.projectDir).path, output.files) to output.errors
49     }.unzip().let { (mappings, errorLists) -> mappings to errorLists.flatten() }
50 
51     private fun writeMappings(mappings: List<Mapping>) {
52         File(incrementalFolder, MAPPING_FILE).writer().use { Gson().toJson(mappings, it) }
53     }
54 
55     private fun readMappings(): List<Mapping> {
56         val type = object : TypeToken<List<Mapping>>() {}.type
57         val mappingsFile = File(incrementalFolder, MAPPING_FILE)
58         if (mappingsFile.exists()) {
59             return mappingsFile.reader().use { Gson().fromJson(it, type) }
60         } else {
61             return emptyList()
62         }
63     }
64 
65     override fun doFullTaskAction() {
66         if (outputDir.exists() && !outputDir.deleteRecursively()) {
67             project.logger.warn("Failed to clear directory for navigation arguments")
68         }
69         if (!outputDir.exists() && !outputDir.mkdirs()) {
70             throw GradleException("Failed to create directory for navigation arguments")
71         }
72         val (mappings, errors) = generateArgs(navigationFiles, outputDir)
73         writeMappings(mappings)
74         failIfErrors(errors)
75     }
76 
77     override fun doIncrementalTaskAction(changedInputs: MutableMap<File, FileStatus>) {
78         super.doIncrementalTaskAction(changedInputs)
79         val oldMapping = readMappings()
80         val navFiles = changedInputs.filter { (_, status) -> status != FileStatus.REMOVED }.keys
81         val (newMapping, errors) = generateArgs(navFiles, outputDir)
82         val newJavaFiles = newMapping.flatMap { it.javaFiles }.toSet()
83         val (modified, unmodified) = oldMapping.partition {
84             File(project.projectDir, it.navFile) in changedInputs
85         }
86         modified.flatMap { it.javaFiles }
87                 .filter { name -> name !in newJavaFiles }
88                 .forEach { javaName ->
89                     val fileName = "${javaName.replace('.', File.separatorChar)}.java"
90                     val file = File(outputDir, fileName)
91                     if (file.exists()) {
92                         file.delete()
93                     }
94                 }
95         writeMappings(unmodified + newMapping)
96         failIfErrors(errors)
97     }
98 
99     private fun failIfErrors(errors: List<ErrorMessage>) {
100         if (errors.isNotEmpty()) {
101             val errString = errors.joinToString("\n") { it.toClickableText() }
102             throw GradleException(
103                     "androidx.navigation.safeargs plugin failed.\n " +
104                             "Following errors found: \n$errString")
105         }
106     }
107 
108     override fun isIncremental() = true
109 }
110 
ErrorMessagenull111 private fun ErrorMessage.toClickableText() = "$path:$line:$column: error: $message"
112 
113 private data class Mapping(val navFile: String, val javaFiles: List<String>)
114