1/* 2 * 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 17package androidx.navigation.safeargs.gradle 18 19import androidx.navigation.safe.args.generator.ErrorMessage 20import androidx.navigation.safe.args.generator.generateSafeArgs 21import com.android.build.gradle.internal.tasks.IncrementalTask 22import com.android.ide.common.resources.FileStatus 23import com.google.gson.Gson 24import com.google.gson.reflect.TypeToken 25import org.gradle.api.GradleException 26import org.gradle.api.tasks.Input 27import org.gradle.api.tasks.InputFiles 28import org.gradle.api.tasks.OutputDirectory 29import java.io.File 30 31private const val MAPPING_FILE = "file_mappings.json" 32 33open 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 111private fun ErrorMessage.toClickableText() = "$path:$line:$column: error: $message" 112 113private data class Mapping(val navFile: String, val javaFiles: List<String>) 114