Skip to content

Commit 8e896fc

Browse files
joke1196sonartech
authored andcommitted
SONARPY-4124 Fixing absolute path resolution on windows during package root resolution (#1084)
GitOrigin-RevId: ee21e5d2291dbf903c193e0d3e84c2566ed96724
1 parent 07b1c7d commit 8e896fc

2 files changed

Lines changed: 77 additions & 4 deletions

File tree

python-commons/src/main/java/org/sonar/plugins/python/indexer/PackageRootResolver.java

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,18 +160,36 @@ private static Optional<PackageResolutionResult> trySonarSources(Configuration c
160160
}
161161

162162
/**
163-
* Converts relative path strings to normalized absolute paths under the given base directory.
163+
* Converts path strings to normalized absolute paths.
164+
*
165+
* <p>Relative paths are resolved against the given base directory. Windows-style absolute paths
166+
* (e.g. {@code C:\path\to\src} or {@code C:/path/to/src}) are used as-is without prepending the
167+
* base directory, which would otherwise produce a nonsensical path on non-Windows systems.
164168
*
165169
* <p>Uses {@link Path#normalize()} to resolve {@code .} and {@code ..} components without
166170
* performing any I/O, so that {@code sonar.sources=.} correctly resolves to the base directory
167171
* rather than producing an un-normalized path like {@code /project/.}.
168172
*/
169173
static List<String> toAbsolutePaths(List<String> paths, File baseDir) {
170174
return paths.stream()
171-
.map(path -> new File(baseDir, path).toPath().normalize().toString())
175+
.map(path -> {
176+
File file = isWindowsAbsolutePath(path) ? new File(path) : new File(baseDir, path);
177+
return file.toPath().normalize().toString();
178+
})
172179
.toList();
173180
}
174181

182+
/**
183+
* Returns {@code true} if the given path is a Windows-style absolute path (e.g. {@code C:\...}
184+
* or {@code C:/...}), regardless of the current operating system.
185+
*/
186+
private static boolean isWindowsAbsolutePath(String path) {
187+
return path.length() >= 3
188+
&& Character.isLetter(path.charAt(0))
189+
&& path.charAt(1) == ':'
190+
&& (path.charAt(2) == '\\' || path.charAt(2) == '/');
191+
}
192+
175193
private static List<String> findConventionalFolders(File baseDir) {
176194
List<String> folders = new ArrayList<>();
177195
for (String folderName : CONVENTIONAL_FOLDERS) {
@@ -186,8 +204,19 @@ private static List<String> findConventionalFolders(File baseDir) {
186204
private static List<String> adjustRoots(List<String> roots, File baseDir) {
187205
return roots.stream()
188206
.map(root -> {
189-
File rootFile = new File(root).isAbsolute() ? new File(root) : new File(baseDir, root);
190-
return adjustPackageRoot(rootFile, baseDir);
207+
File rootAsFile = new File(root);
208+
if (rootAsFile.isAbsolute()) {
209+
// Native absolute path (works on any OS, including Windows running on Windows).
210+
return adjustPackageRoot(rootAsFile, baseDir);
211+
}
212+
if (isWindowsAbsolutePath(root)) {
213+
// Windows-style absolute path (e.g. C:\src) on a non-Windows system: File.isAbsolute()
214+
// returns false, so we must not pass it to new File(baseDir, root) or getAbsolutePath()
215+
// would prepend the JVM working directory. Return as-is; __init__.py traversal is not
216+
// meaningful for a foreign-OS path.
217+
return root;
218+
}
219+
return adjustPackageRoot(new File(baseDir, root), baseDir);
191220
})
192221
.distinct()
193222
.toList();

python-commons/src/test/java/org/sonar/plugins/python/indexer/PackageRootResolverTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,50 @@ void toAbsolutePaths_dotPath_normalizedToBaseDir() {
152152
assertThat(result.get(0)).doesNotEndWith("\\.");
153153
}
154154

155+
// ─── toAbsolutePaths() — Windows path handling ───────────────────────────
156+
157+
@Test
158+
void toAbsolutePaths_windowsAbsolutePath_notPrependedWithBaseDir() {
159+
// When sonar.sources contains a Windows-style absolute path (e.g. from a Windows scanner),
160+
// it must NOT be prepended with the base directory on a non-Windows system.
161+
File baseDir = new File("/project/base");
162+
String windowsAbsPath = "C:\\Users\\user\\mixed-language-build";
163+
164+
List<String> result = PackageRootResolver.toAbsolutePaths(List.of(windowsAbsPath), baseDir);
165+
166+
// Must not produce "/project/base/" prepended to the Windows path
167+
assertThat(result).containsExactly("C:\\Users\\user\\mixed-language-build");
168+
}
169+
170+
@Test
171+
void toAbsolutePaths_windowsAbsolutePathWithForwardSlashes_notPrependedWithBaseDir() {
172+
// Windows paths may also use forward slashes; they are normalised by Path.normalize()
173+
File baseDir = new File("/project/base");
174+
String windowsAbsPath = "C:/Users/user/mixed-language-build";
175+
176+
List<String> result = PackageRootResolver.toAbsolutePaths(List.of(windowsAbsPath), baseDir);
177+
178+
// Must not produce "/project/base/" prepended to the Windows path
179+
// Forward slashes are preserved as-is by Path.normalize() on non-Windows systems
180+
assertThat(result).hasSize(1);
181+
assertThat(result.get(0)).isIn("C:\\Users\\user\\mixed-language-build","C:/Users/user/mixed-language-build");
182+
}
183+
184+
@Test
185+
void trySonarSources_windowsAbsolutePath_usedDirectlyNotPrependedWithBaseDir() throws IOException {
186+
// Full integration: build file exists, sonar.sources has a Windows absolute path.
187+
// The resolved root should be derived from the Windows path, not baseDir + Windows path.
188+
Files.writeString(tempDir.resolve("pyproject.toml"), "[project]\nname = \"myproject\"\n");
189+
File baseDir = tempDir.toFile();
190+
String windowsAbsPath = "C:\\Users\\user\\mixed-language-build";
191+
192+
PackageResolutionResult result = PackageRootResolver.resolve(mockFileSystem(baseDir), sonarSources(windowsAbsPath));
193+
194+
assertThat(result.method()).isEqualTo(PackageResolutionResult.ResolutionMethod.SONAR_SOURCES);
195+
assertThat(result.roots()).hasSize(1);
196+
assertThat(result.roots()).containsExactly("C:\\Users\\user\\mixed-language-build");
197+
}
198+
155199
// ─── adjustPackageRoot() — migrated from PythonIndexerTest ────────────────
156200

157201
@Test

0 commit comments

Comments
 (0)