mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 06:50:54 +07:00
[run configuration] fix locating log files by pattern in subdirectories (IJPL-148359)
Before patterns were supported in the file name part only, now they are supported in directory names as well. GitOrigin-RevId: 607e921695b6c84d3503f1c2c921beaf0ad61d80
This commit is contained in:
committed by
intellij-monorepo-bot
parent
2338c10c80
commit
5ced3727dd
@@ -90,6 +90,7 @@ a:com.intellij.diagnostic.logging.LogConsoleManagerBase
|
||||
- pa:getUi():com.intellij.execution.ui.RunnerLayoutUi
|
||||
- removeAdditionalTabComponent(com.intellij.diagnostic.logging.AdditionalTabComponent):V
|
||||
- removeLogConsole(java.lang.String):V
|
||||
f:com.intellij.diagnostic.logging.LogFilesCollectorKt
|
||||
f:com.intellij.diagnostic.logging.LogFilesManager
|
||||
- <init>(com.intellij.openapi.project.Project,com.intellij.diagnostic.logging.LogConsoleManager,com.intellij.openapi.Disposable):V
|
||||
- addLogConsoles(com.intellij.execution.configurations.RunConfigurationBase,com.intellij.execution.process.ProcessHandler):V
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.diagnostic.logging
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.FileVisitResult
|
||||
import java.nio.file.FileVisitor
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.io.path.*
|
||||
|
||||
/**
|
||||
* Return paths to files which matches the given [pathPattern] in Ant format.
|
||||
* @param includeAll if `true`, all matching files will be returned, otherwise only the last modified one.
|
||||
*/
|
||||
@RequiresBackgroundThread
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
@VisibleForTesting
|
||||
@ApiStatus.Internal
|
||||
fun collectLogPaths(pathPattern: String?, includeAll: Boolean): Set<String> {
|
||||
if (pathPattern == null) {
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
val logFile = File(pathPattern)
|
||||
if (logFile.exists()) {
|
||||
return setOf(pathPattern)
|
||||
}
|
||||
|
||||
var depth = 0
|
||||
var root: File? = logFile.parentFile
|
||||
var patternString = logFile.name
|
||||
while (root != null && !root.exists() && depth < 5) {
|
||||
patternString = "${root.name}/$patternString"
|
||||
root = root.parentFile
|
||||
depth++
|
||||
}
|
||||
if (root == null || !root.exists()) {
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
val pattern = Pattern.compile(FileUtil.convertAntToRegexp(patternString))
|
||||
val matchingPaths = ArrayList<Path>()
|
||||
root.toPath().visitFileTree(MatchingPathsCollector(pattern, matchingPaths))
|
||||
if (matchingPaths.isEmpty()) {
|
||||
return emptySet()
|
||||
}
|
||||
if (includeAll) {
|
||||
return matchingPaths.mapTo(LinkedHashSet(matchingPaths.size)) { it.pathString }
|
||||
}
|
||||
else {
|
||||
return setOfNotNull(matchingPaths.maxByOrNull { it.getLastModifiedTime() }?.pathString)
|
||||
}
|
||||
}
|
||||
|
||||
private class MatchingPathsCollector(private val pattern: Pattern, private val result: ArrayList<Path>) : FileVisitor<Path> {
|
||||
var relativePath = ""
|
||||
var initialDir = true
|
||||
|
||||
override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult {
|
||||
if (!initialDir) {
|
||||
relativePath = "$relativePath${dir.name}/"
|
||||
}
|
||||
else {
|
||||
initialDir = false
|
||||
}
|
||||
return FileVisitResult.CONTINUE
|
||||
}
|
||||
|
||||
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
|
||||
if (pattern.matcher(relativePath + file.name).matches()) {
|
||||
result.add(file)
|
||||
if (result.size > 100) {
|
||||
return FileVisitResult.TERMINATE
|
||||
}
|
||||
}
|
||||
return FileVisitResult.CONTINUE
|
||||
}
|
||||
|
||||
override fun visitFileFailed(file: Path, exc: IOException?): FileVisitResult {
|
||||
return FileVisitResult.CONTINUE
|
||||
}
|
||||
|
||||
override fun postVisitDirectory(dir: Path, exc: IOException?): FileVisitResult {
|
||||
relativePath = relativePath.removeSuffix("${dir.name}/")
|
||||
return FileVisitResult.CONTINUE
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,8 @@ public final class LogFilesManager {
|
||||
}
|
||||
|
||||
final Set<String> oldPaths = logFile.getPaths();
|
||||
final Set<String> newPaths = logFile.getOptions().getPaths(); // should not be called in UI thread
|
||||
LogFileOptions options = logFile.getOptions();
|
||||
final Set<String> newPaths = LogFilesCollectorKt.collectLogPaths(options.getPathPattern(), options.isShowAll()); // should not be called in UI thread
|
||||
logFile.setPaths(newPaths);
|
||||
|
||||
final Set<String> obsoletePaths = new HashSet<>(oldPaths);
|
||||
@@ -52,7 +53,7 @@ public final class LogFilesManager {
|
||||
return;
|
||||
}
|
||||
|
||||
addConfigurationConsoles(logFile.getOptions(), file -> !oldPaths.contains(file), newPaths, logFile.getConfiguration());
|
||||
addConfigurationConsoles(options, file -> !oldPaths.contains(file), newPaths, logFile.getConfiguration());
|
||||
for (String each : obsoletePaths) {
|
||||
myManager.removeLogConsole(each);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.diagnostic.logging
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.util.io.directoryContent
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
class LogFilesCollectorTest {
|
||||
@Test
|
||||
fun `collect files in root`() {
|
||||
val logDir = directoryContent {
|
||||
file("main.log")
|
||||
file("main2.log")
|
||||
}.generateInTempDir()
|
||||
val root = logDir.pathString
|
||||
|
||||
assertLogs("*")
|
||||
assertLogs("**/**")
|
||||
assertLogs("$root/x.log")
|
||||
|
||||
assertLogs("$root/main.log",
|
||||
"$root/main.log")
|
||||
assertLogs("$root/*.log",
|
||||
"$root/main.log",
|
||||
"$root/main2.log")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `collect files in subdirectories`() {
|
||||
val logDir = directoryContent {
|
||||
dir("subDir1") {
|
||||
dir("deepSubDir") {
|
||||
file("file1.log")
|
||||
file("file2.log")
|
||||
}
|
||||
file("file1.log")
|
||||
}
|
||||
dir("subDir2") {
|
||||
file("file1.log")
|
||||
file("file2.log")
|
||||
}
|
||||
}.generateInTempDir()
|
||||
val root = logDir.pathString
|
||||
|
||||
assertLogs("$root/*/*.log",
|
||||
"$root/subDir1/file1.log",
|
||||
"$root/subDir2/file1.log",
|
||||
"$root/subDir2/file2.log")
|
||||
assertLogs("$root/*/file1.log",
|
||||
"$root/subDir1/file1.log",
|
||||
"$root/subDir2/file1.log")
|
||||
assertLogs("$root/**/file1.log",
|
||||
"$root/subDir1/deepSubDir/file1.log",
|
||||
"$root/subDir1/file1.log",
|
||||
"$root/subDir2/file1.log")
|
||||
}
|
||||
|
||||
private fun assertLogs(pathPattern: String, vararg expected: String) {
|
||||
val actual = collectLogPaths(FileUtil.toSystemDependentName(pathPattern), true).toList().sorted()
|
||||
Assertions.assertThatList(actual).isEqualTo(expected.toList())
|
||||
}
|
||||
}
|
||||
@@ -552,11 +552,9 @@ f:com.intellij.execution.configurations.LogFileOptions
|
||||
- <init>(java.lang.String,java.lang.String,Z,Z,Z):V
|
||||
- b:<init>(java.lang.String,java.lang.String,Z,Z,Z,I,kotlin.jvm.internal.DefaultConstructorMarker):V
|
||||
- sf:areEqual(com.intellij.execution.configurations.LogFileOptions,com.intellij.execution.configurations.LogFileOptions):Z
|
||||
- sf:collectMatchedFiles(java.io.File,java.util.regex.Pattern,java.util.List):V
|
||||
- f:getCharset():java.nio.charset.Charset
|
||||
- f:getName():java.lang.String
|
||||
- f:getPathPattern():java.lang.String
|
||||
- f:getPaths():java.util.Set
|
||||
- f:isEnabled():Z
|
||||
- f:isShowAll():Z
|
||||
- f:isSkipContent():Z
|
||||
@@ -569,7 +567,6 @@ f:com.intellij.execution.configurations.LogFileOptions
|
||||
- f:setSkipContent(Z):V
|
||||
f:com.intellij.execution.configurations.LogFileOptions$Companion
|
||||
- f:areEqual(com.intellij.execution.configurations.LogFileOptions,com.intellij.execution.configurations.LogFileOptions):Z
|
||||
- f:collectMatchedFiles(java.io.File,java.util.regex.Pattern,java.util.List):V
|
||||
a:com.intellij.execution.configurations.ModuleBasedConfiguration
|
||||
- com.intellij.execution.configurations.LocatableConfigurationBase
|
||||
- com.intellij.execution.configurations.ModuleRunConfiguration
|
||||
|
||||
@@ -3,15 +3,11 @@ package com.intellij.execution.configurations
|
||||
|
||||
import com.intellij.openapi.components.BaseState
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.xmlb.Converter
|
||||
import com.intellij.util.xmlb.annotations.Attribute
|
||||
import com.intellij.util.xmlb.annotations.Tag
|
||||
import java.io.File
|
||||
import java.nio.charset.Charset
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* The information about a single log file displayed in the console when the configuration
|
||||
@@ -20,12 +16,6 @@ import java.util.regex.Pattern
|
||||
@Tag("log_file")
|
||||
class LogFileOptions : BaseState {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun collectMatchedFiles(root: File, pattern: Pattern, files: MutableList<File>) {
|
||||
val dirs = root.listFiles() ?: return
|
||||
dirs.filterTo(files) { pattern.matcher(it.name).matches() && it.isFile }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun areEqual(options1: LogFileOptions?, options2: LogFileOptions?): Boolean {
|
||||
return if (options1 == null || options2 == null) {
|
||||
@@ -60,46 +50,6 @@ class LogFileOptions : BaseState {
|
||||
@get:Attribute(value = "charset", converter = CharsetConverter::class)
|
||||
var charset: Charset by property(Charset.defaultCharset())
|
||||
|
||||
fun getPaths(): Set<String> {
|
||||
val logFile = File(pathPattern!!)
|
||||
if (logFile.exists()) {
|
||||
return setOf(pathPattern!!)
|
||||
}
|
||||
|
||||
val dirIndex = pathPattern!!.lastIndexOf(File.separator)
|
||||
if (dirIndex == -1) {
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
val files = SmartList<File>()
|
||||
collectMatchedFiles(File(pathPattern!!.substring(0, dirIndex)),
|
||||
Pattern.compile(FileUtil.convertAntToRegexp(pathPattern!!.substring(dirIndex + File.separator.length))), files)
|
||||
if (files.isEmpty()) {
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
if (isShowAll) {
|
||||
val result = HashSet<String>(files.size)
|
||||
files.mapTo(result) { it.path }
|
||||
return result
|
||||
}
|
||||
else {
|
||||
var lastFile: File? = null
|
||||
for (file in files) {
|
||||
if (lastFile != null) {
|
||||
if (file.lastModified() > lastFile.lastModified()) {
|
||||
lastFile = file
|
||||
}
|
||||
}
|
||||
else {
|
||||
lastFile = file
|
||||
}
|
||||
}
|
||||
assert(lastFile != null)
|
||||
return setOf(lastFile!!.path)
|
||||
}
|
||||
}
|
||||
|
||||
//read external
|
||||
constructor()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user