1973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets/* 2973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets * Copyright 2018 The Android Open Source Project 3973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets * 4973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets * Licensed under the Apache License, Version 2.0 (the "License"); 5973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets * you may not use this file except in compliance with the License. 6973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets * You may obtain a copy of the License at 7973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets * 8973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets * http://www.apache.org/licenses/LICENSE-2.0 9973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets * 10973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets * Unless required by applicable law or agreed to in writing, software 11973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets * distributed under the License is distributed on an "AS IS" BASIS, 12973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets * See the License for the specific language governing permissions and 14973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets * limitations under the License. 15973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets */ 16973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets 171503d52153986fdcfe7e744795010708b7410892Ian Lakepackage androidx.navigation.safeargs.gradle 18973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets 1928f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinetsimport androidx.navigation.safe.args.generator.ErrorMessage 201503d52153986fdcfe7e744795010708b7410892Ian Lakeimport androidx.navigation.safe.args.generator.generateSafeArgs 217e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinetsimport com.android.build.gradle.internal.tasks.IncrementalTask 2298971a7d72d56aeaf5d997ab251629e2550f350cSergey Vasilinetsimport com.android.ide.common.resources.FileStatus 237e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinetsimport com.google.gson.Gson 247e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinetsimport com.google.gson.reflect.TypeToken 25973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinetsimport org.gradle.api.GradleException 26973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinetsimport org.gradle.api.tasks.Input 27973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinetsimport org.gradle.api.tasks.InputFiles 28973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinetsimport org.gradle.api.tasks.OutputDirectory 29973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinetsimport java.io.File 30973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets 317e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinetsprivate const val MAPPING_FILE = "file_mappings.json" 32973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets 337e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinetsopen class ArgumentsGenerationTask : IncrementalTask() { 34973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets @get:Input 357e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets lateinit var rFilePackage: String 36fb97caaf48ddb265adca54c03569b2ea7ab5a390Sergey Vasilinets 37fb97caaf48ddb265adca54c03569b2ea7ab5a390Sergey Vasilinets @get:Input 387e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets lateinit var applicationId: String 39973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets 40973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets @get:OutputDirectory 417e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets lateinit var outputDir: File 42973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets 43973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets @get:InputFiles 44973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets var navigationFiles: List<File> = emptyList() 45973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets 467e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets private fun generateArgs(navFiles: Collection<File>, out: File) = navFiles.map { file -> 4728f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets val output = generateSafeArgs(rFilePackage, applicationId, file, out) 4828f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets Mapping(file.relativeTo(project.projectDir).path, output.files) to output.errors 4928f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets }.unzip().let { (mappings, errorLists) -> mappings to errorLists.flatten() } 507e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets 517e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets private fun writeMappings(mappings: List<Mapping>) { 527e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets File(incrementalFolder, MAPPING_FILE).writer().use { Gson().toJson(mappings, it) } 537e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets } 547e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets 557e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets private fun readMappings(): List<Mapping> { 567e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets val type = object : TypeToken<List<Mapping>>() {}.type 577e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets val mappingsFile = File(incrementalFolder, MAPPING_FILE) 587e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets if (mappingsFile.exists()) { 597e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets return mappingsFile.reader().use { Gson().fromJson(it, type) } 607e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets } else { 617e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets return emptyList() 62973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets } 63973a5c08a9d5a6951830f9dc6c0fa875392491e4Sergey Vasilinets } 647e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets 657e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets override fun doFullTaskAction() { 667e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets if (outputDir.exists() && !outputDir.deleteRecursively()) { 677e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets project.logger.warn("Failed to clear directory for navigation arguments") 687e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets } 697e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets if (!outputDir.exists() && !outputDir.mkdirs()) { 707e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets throw GradleException("Failed to create directory for navigation arguments") 717e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets } 7228f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets val (mappings, errors) = generateArgs(navigationFiles, outputDir) 737e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets writeMappings(mappings) 7428f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets failIfErrors(errors) 757e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets } 767e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets 777e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets override fun doIncrementalTaskAction(changedInputs: MutableMap<File, FileStatus>) { 787e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets super.doIncrementalTaskAction(changedInputs) 797e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets val oldMapping = readMappings() 807e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets val navFiles = changedInputs.filter { (_, status) -> status != FileStatus.REMOVED }.keys 8128f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets val (newMapping, errors) = generateArgs(navFiles, outputDir) 827e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets val newJavaFiles = newMapping.flatMap { it.javaFiles }.toSet() 837e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets val (modified, unmodified) = oldMapping.partition { 847e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets File(project.projectDir, it.navFile) in changedInputs 857e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets } 867e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets modified.flatMap { it.javaFiles } 877e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets .filter { name -> name !in newJavaFiles } 887e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets .forEach { javaName -> 897e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets val fileName = "${javaName.replace('.', File.separatorChar)}.java" 907e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets val file = File(outputDir, fileName) 917e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets if (file.exists()) { 927e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets file.delete() 937e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets } 947e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets } 957e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets writeMappings(unmodified + newMapping) 9628f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets failIfErrors(errors) 9728f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets } 9828f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets 9928f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets private fun failIfErrors(errors: List<ErrorMessage>) { 10028f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets if (errors.isNotEmpty()) { 10128f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets val errString = errors.joinToString("\n") { it.toClickableText() } 10228f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets throw GradleException( 10328f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets "androidx.navigation.safeargs plugin failed.\n " + 10428f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets "Following errors found: \n$errString") 10528f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets } 1067e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets } 1077e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets 1087e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets override fun isIncremental() = true 1097e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets} 1107e7dfaccd4c66e8eec029590c334b3839c3d09b7Sergey Vasilinets 11128f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinetsprivate fun ErrorMessage.toClickableText() = "$path:$line:$column: error: $message" 11228f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets 11398971a7d72d56aeaf5d997ab251629e2550f350cSergey Vasilinetsprivate data class Mapping(val navFile: String, val javaFiles: List<String>) 114