1 /* <lambda>null2 * Copyright 2024 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 package bench.flame.diff.command 17 18 import bench.flame.diff.config.Paths 19 import bench.flame.diff.interop.isValidFileName 20 import bench.flame.diff.ui.promptOverwriteFile 21 import bench.flame.diff.ui.promptProvideFile 22 import com.github.ajalt.clikt.core.CliktCommand 23 import com.github.ajalt.clikt.core.terminal 24 import com.github.ajalt.clikt.parameters.groups.mutuallyExclusiveOptions 25 import com.github.ajalt.clikt.parameters.groups.single 26 import com.github.ajalt.clikt.parameters.options.check 27 import com.github.ajalt.clikt.parameters.options.default 28 import com.github.ajalt.clikt.parameters.options.option 29 import com.github.ajalt.clikt.parameters.types.file 30 import com.github.ajalt.mordant.terminal.ConversionResult 31 import java.io.File 32 33 class Save : CliktCommand(help = "Save a trace file for future comparison.") { 34 private val src by mutuallyExclusiveOptions( 35 option("--src-file", help = "Path to a trace file.") 36 .file(mustExist = true, canBeDir = false), 37 option("--src-dir", help = "Path to a directory containing trace files.") 38 .file(mustExist = true, canBeFile = false), 39 name = "Trace source", 40 ).single() 41 42 private val dst by option(help = "Name for the saved trace file.") 43 .check { it.isValidFileName() } 44 45 private val pattern by option("--pattern", help = "Trace file name regex.") 46 .default(Paths.traceFileNamePattern) 47 48 override fun run() { 49 // determine the source trace file location 50 val src = src // allows for smart casts 51 val srcTraceFile: File = when { 52 src != null && src.isFile -> src 53 else -> promptProvideFile( 54 "Provide trace source", pattern, 55 excludePattern = null, src, Paths.outDir.toFile() 56 ) 57 } 58 check(srcTraceFile.exists() && srcTraceFile.isFile) 59 60 // copy the source trace file to the destination 61 val dstTraceFile: File = 62 Paths.savedTracesDir.resolve(dst ?: promptDestinationName(srcTraceFile.name)).toFile() 63 if (dstTraceFile.exists()) promptOverwriteFile(dstTraceFile).let { overwrite -> 64 if (!overwrite) return 65 } 66 dstTraceFile.parentFile.mkdirs() // ensure destination dir is present 67 srcTraceFile.copyTo(dstTraceFile, overwrite = true) 68 69 // Determine the test result file corresponding to the trace (if present). JSON file will 70 // have the trace name in profileResults section. Trace files are guaranteed unique. 71 val srcTestResultFile = srcTraceFile.parentFile.walkTopDown().filter { 72 it.name.lowercase().endsWith("json") && it.readText().contains(srcTraceFile.name) 73 }.singleOrNull() 74 75 // copy the test result file to the destination 76 srcTestResultFile?.let { 77 // adding the extension (.json) helps us avoid a conflict with the trace file name 78 val dstTestResultFileName = dstTraceFile.nameWithoutExtension + ".json" 79 val dstTestResultFile = dstTraceFile.parentFile.resolve(dstTestResultFileName) 80 // If we are changing the name of the trace file, we also need to reflect that in JSON. 81 val updatedJsonFile = it.readText().replace(srcTraceFile.name, dstTraceFile.name) 82 dstTestResultFile.writeText(updatedJsonFile) 83 } 84 } 85 86 private fun promptDestinationName(default: String? = null): String = 87 terminal.prompt("Provide destination file name", default) { 88 when { 89 it.isValidFileName() -> ConversionResult.Valid(it) 90 else -> ConversionResult.Invalid("Invalid value") 91 } 92 }!! 93 } 94