From c898805ac507b3bc7d0986c0b4370bb1fb6761bd Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Mon, 19 Apr 2021 13:07:01 +0200 Subject: [PATCH] CLI: improve path relativization for compiler messages On Windows, the absolute file for the root of the drive is `"C:\"`, which differs from the absolute file for any other directory, which doesn't end with `\`. This resulted in incorrect trimming of the first character of the path name in `descendantRelativeTo`. Also, do not use canonicalPath because there is no point in expanding symbolic links here. #KT-40979 Fixed --- .../integration/FilePathNormalizationTest.kt | 75 +++++++++++++++++++ .../org/jetbrains/kotlin/utils/fileUtils.kt | 17 ++--- 2 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 compiler/tests/org/jetbrains/kotlin/integration/FilePathNormalizationTest.kt diff --git a/compiler/tests/org/jetbrains/kotlin/integration/FilePathNormalizationTest.kt b/compiler/tests/org/jetbrains/kotlin/integration/FilePathNormalizationTest.kt new file mode 100644 index 00000000000..c774f455ddf --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/integration/FilePathNormalizationTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.integration + +import com.intellij.openapi.util.SystemInfo +import org.jetbrains.kotlin.utils.fileUtils.descendantRelativeTo +import java.io.File +import kotlin.io.path.createSymbolicLinkPointingTo + +class FilePathNormalizationTest : KotlinIntegrationTestBase() { + // This test checks that path normalization logic used in MessageRenderer.PLAIN_RELATIVE_PATHS works correctly. + // It compiles and runs a program in a separate process to be able to test how it works from different working directories. + // (It could be tested in the same process by changing the user.dir manually, but that could change behavior + // of other tests run in parallel.) + fun test() { + val descendantRelativeTo = File::descendantRelativeTo.name + val program = ProgramWithDependencyOnCompiler( + tmpdir, """ + import org.jetbrains.kotlin.utils.fileUtils.$descendantRelativeTo + import java.io.File + + fun main(args: Array) { + println(File(args[0]).$descendantRelativeTo(File(".").absoluteFile).path) + } + """.trimIndent() + ) + + program.compile() + + fun doTest(cwd: File, filePath: String, expectedWithForwardSlash: String) { + // We use "/" below for simplicity, but the actual paths in compiler messages use the system separator. + val expected = expectedWithForwardSlash.replace("/", File.separator) + val actual = program.run(cwd, filePath) + assertEquals("cwd: $cwd\nfilePath: $filePath\n", expected, actual) + } + + doTest(tmpdir, "a", "a") + doTest(tmpdir, "./a", "a") + doTest(tmpdir, "a/../b", "b") + doTest(tmpdir, "..", "..") + doTest(tmpdir, "../a", "../a") + doTest(tmpdir, tmpdir.path + "/a", "a") + doTest(tmpdir, tmpdir.path + "/./a", "a") + + val root = File("/") + doTest(root, "test", "test") + + doTest(root, tmpdir.path + "/a", (tmpdir.path + "/a").removePrefix(root.absolutePath)) + + doTest( + root, "./test", + if (SystemInfo.isWindows) "./test" else "test" + ) + + // Check symbolic links, but skip file systems which don't support them (e.g. Windows). + fun doSymbolicLinkTest(cwd: File, source: File, target: File, expected: String) { + val link = try { + source.toPath().createSymbolicLinkPointingTo(target.toPath()).toFile() + } catch (e: Throwable) { + null + } + if (link != null) { + doTest(cwd, link.path, expected) + } + } + + doSymbolicLinkTest(tmpdir, tmpdir / "a", tmpdir / "b", "a") + doSymbolicLinkTest(tmpdir / "unrelated", tmpdir / "a", tmpdir / "../b", tmpdir.path + "/a") + } + + private operator fun File.div(x: String): File = File(this, x) +} diff --git a/compiler/util/src/org/jetbrains/kotlin/utils/fileUtils.kt b/compiler/util/src/org/jetbrains/kotlin/utils/fileUtils.kt index a0053ebf365..0bb40e446b4 100644 --- a/compiler/util/src/org/jetbrains/kotlin/utils/fileUtils.kt +++ b/compiler/util/src/org/jetbrains/kotlin/utils/fileUtils.kt @@ -37,14 +37,9 @@ fun File.withReplacedExtensionOrNull(oldExt: String, newExt: String): File? { * If this file does not belong to the [base] directory, it is returned unchanged. */ fun File.descendantRelativeTo(base: File): File { - val prefix = base.canonicalPath - val answer = this.canonicalPath - return if (answer.startsWith(prefix)) { - val prefixSize = prefix.length - if (answer.length > prefixSize) { - File(answer.substring(prefixSize + 1)) - } else File("") - } else { - this - } -} \ No newline at end of file + assert(base.isAbsolute) { "$base" } + assert(base.isDirectory) { "$base" } + val cwd = base.normalize() + val filePath = this.absoluteFile.normalize() + return if (filePath.startsWith(cwd)) filePath.relativeTo(cwd) else this +}