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