Skip to content

Commit ec66f39

Browse files
joke1196thomas-serre-sonarsourceGabrielFleischer
authored andcommitted
SONARPY-3766: Use the package root to determine the FQN during package name creation/visit (#848)
Co-authored-by: Thomas Serre <thomas.serre@sonarsource.com> Co-authored-by: Gabriel Fleischer <gabriel.fleischer@sonarsource.com> GitOrigin-RevId: 889c81d20bafb07e7b0779d2c132c68e0589745c
1 parent e15fa21 commit ec66f39

17 files changed

Lines changed: 1015 additions & 72 deletions

File tree

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
* (e.g., pyproject.toml) and provides fallback resolution when no roots are configured.
3030
*/
3131
public class PackageRootResolver {
32-
3332
static final String SONAR_SOURCES_KEY = "sonar.sources";
3433
static final List<String> CONVENTIONAL_FOLDERS = List.of("src", "lib");
3534

@@ -43,8 +42,8 @@ private PackageRootResolver() {
4342
* Otherwise, applies a fallback chain to determine appropriate roots.
4443
*
4544
* @param extractedRoots roots extracted from build system config (e.g., from BuildSystemSourceRoots.extract())
46-
* @param config the Sonar configuration to read sonar.sources property
47-
* @param baseDir the project base directory
45+
* @param config the Sonar configuration to read sonar.sources property
46+
* @param baseDir the project base directory
4847
* @return list of resolved package root absolute paths
4948
*/
5049
public static List<String> resolve(List<String> extractedRoots, Configuration config, File baseDir) {
@@ -64,7 +63,7 @@ public static List<String> resolve(List<String> extractedRoots, Configuration co
6463
* <li>Project base directory absolute path as last resort</li>
6564
* </ol>
6665
*
67-
* @param config the Sonar configuration
66+
* @param config the Sonar configuration
6867
* @param baseDir the project base directory
6968
* @return list of fallback package root absolute paths
7069
*/

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

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@
2121
import com.fasterxml.jackson.annotation.Nulls;
2222
import com.fasterxml.jackson.databind.DeserializationFeature;
2323
import com.fasterxml.jackson.dataformat.toml.TomlMapper;
24+
import java.io.File;
2425
import java.io.IOException;
26+
import java.nio.file.Files;
2527
import java.util.ArrayList;
2628
import java.util.LinkedHashSet;
2729
import java.util.List;
2830
import java.util.Map;
2931
import java.util.Set;
3032
import javax.annotation.Nonnull;
3133
import javax.annotation.Nullable;
32-
import org.sonar.api.batch.fs.InputFile;
3334

3435
/**
3536
* Extracts source root directories from pyproject.toml build system configurations.
@@ -39,7 +40,7 @@
3940
* <li>setuptools: {@code [tool.setuptools.packages.find] where = ["src"]}</li>
4041
* <li>Poetry: {@code [tool.poetry] packages = [{from = "src", include = "pkg"}]}</li>
4142
* <li>Hatchling: {@code [tool.hatch.build.targets.wheel] sources = ["src"]}</li>
42-
* <li>uv_build: {@code [tool.uv.build-backend] module-root = "src"} and by default detect src by convention</li>
43+
* <li>uv_build: {@code [build-system] build-backend = "uv_build"} or {@code [tool.uv.build-backend] module-root = "src"} - auto-detects src/ layout by convention</li>
4344
* <li>PDM: {@code [tool.pdm] package-dir = "src"}</li>
4445
* <li>Flit: auto-detects src/ layout by convention</li>
4546
* </ul>
@@ -71,37 +72,53 @@ public static List<String> extract(String tomlContent) {
7172
}
7273

7374
/**
74-
* Extracts source root directories from a pyproject.toml InputFile.
75+
* Extracts source root directories from a pyproject.toml File.
7576
*
76-
* @param inputFile the pyproject.toml file
77+
* @param file the pyproject.toml file
7778
* @return list of source root paths (relative), empty if none found or on parse error
7879
*/
79-
public static List<String> extract(InputFile inputFile) {
80+
public static List<String> extract(File file) {
8081
try {
81-
return extract(inputFile.contents());
82+
return extract(Files.readString(file.toPath()));
8283
} catch (IOException e) {
8384
return List.of();
8485
}
8586
}
8687

8788
private static List<String> extractFromConfig(PyProjectConfig config) {
88-
if (config.tool() == null) {
89-
return List.of();
90-
}
91-
9289
Set<String> sourceRoots = new LinkedHashSet<>();
93-
Tool tool = config.tool();
9490

95-
sourceRoots.addAll(extractFromSetuptools(tool.setuptools()));
96-
sourceRoots.addAll(extractFromPoetry(tool.poetry()));
97-
sourceRoots.addAll(extractFromHatchling(tool.hatch()));
98-
sourceRoots.addAll(extractFromUvBuild(tool.uv()));
99-
sourceRoots.addAll(extractFromPdm(tool.pdm()));
100-
sourceRoots.addAll(extractFromFlit(tool.flit()));
91+
// Check build-system.build-backend for uv_build
92+
sourceRoots.addAll(extractFromBuildSystem(config.buildSystem()));
93+
94+
if (config.tool() != null) {
95+
Tool tool = config.tool();
96+
sourceRoots.addAll(extractFromSetuptools(tool.setuptools()));
97+
sourceRoots.addAll(extractFromPoetry(tool.poetry()));
98+
sourceRoots.addAll(extractFromHatchling(tool.hatch()));
99+
sourceRoots.addAll(extractFromUvBuild(tool.uv()));
100+
sourceRoots.addAll(extractFromPdm(tool.pdm()));
101+
sourceRoots.addAll(extractFromFlit(tool.flit()));
102+
}
101103

102104
return new ArrayList<>(sourceRoots);
103105
}
104106

107+
// === Build System ===
108+
// [build-system]
109+
// build-backend = "uv_build"
110+
111+
private static List<String> extractFromBuildSystem(@Nullable BuildSystem buildSystem) {
112+
if (buildSystem == null || buildSystem.buildBackend() == null) {
113+
return List.of();
114+
}
115+
// uv_build auto-detects src/ layout by convention
116+
if ("uv_build".equals(buildSystem.buildBackend())) {
117+
return List.of("src");
118+
}
119+
return List.of();
120+
}
121+
105122
// === Setuptools ===
106123
// [tool.setuptools.packages.find]
107124
// where = ["src"]
@@ -135,7 +152,7 @@ private static List<String> extractFromPoetry(@Nullable Poetry poetry) {
135152

136153
private static List<String> extractFromHatchling(@Nullable Hatch hatch) {
137154
if (hatch == null || hatch.build() == null || hatch.build().targets() == null
138-
|| hatch.build().targets().wheel() == null) {
155+
|| hatch.build().targets().wheel() == null) {
139156
return List.of();
140157
}
141158

@@ -220,7 +237,8 @@ private static List<String> extractFromFlit(@Nullable Flit flit) {
220237
private record PyProjectConfig(
221238
@JsonProperty("build-system") @Nullable BuildSystem buildSystem,
222239
@Nullable Tool tool
223-
) {}
240+
) {
241+
}
224242

225243
private record BuildSystem(
226244
@JsonProperty("build-backend") @Nullable String buildBackend,
@@ -238,16 +256,19 @@ private record Tool(
238256
@Nullable Uv uv,
239257
@Nullable Pdm pdm,
240258
@Nullable Flit flit
241-
) {}
259+
) {
260+
}
242261

243262
// Setuptools records
244263
private record Setuptools(
245264
@Nullable SetuptoolsPackages packages
246-
) {}
265+
) {
266+
}
247267

248268
private record SetuptoolsPackages(
249269
@Nullable SetuptoolsFind find
250-
) {}
270+
) {
271+
}
251272

252273
private record SetuptoolsFind(
253274
@JsonSetter(nulls = Nulls.AS_EMPTY) @Nonnull List<String> where
@@ -271,20 +292,24 @@ private record Poetry(
271292
private record PoetryPackage(
272293
@Nullable String include,
273294
@Nullable String from
274-
) {}
295+
) {
296+
}
275297

276298
// Hatchling records
277299
private record Hatch(
278300
@Nullable HatchBuild build
279-
) {}
301+
) {
302+
}
280303

281304
private record HatchBuild(
282305
@Nullable HatchTargets targets
283-
) {}
306+
) {
307+
}
284308

285309
private record HatchTargets(
286310
@Nullable HatchWheel wheel
287-
) {}
311+
) {
312+
}
288313

289314
private record HatchWheel(
290315
@JsonSetter(nulls = Nulls.AS_EMPTY) @Nonnull List<String> sources,
@@ -299,24 +324,29 @@ private record HatchWheel(
299324
// uv_build records
300325
private record Uv(
301326
@JsonProperty("build-backend") @Nullable UvBuildBackend buildBackend
302-
) {}
327+
) {
328+
}
303329

304330
private record UvBuildBackend(
305331
@JsonProperty("module-root") @Nullable String moduleRoot
306-
) {}
332+
) {
333+
}
307334

308335
// PDM records
309336
private record Pdm(
310337
@JsonProperty("package-dir") @Nullable String packageDir
311-
) {}
338+
) {
339+
}
312340

313341
// Flit records
314342
private record Flit(
315343
@Nullable FlitModule module
316-
) {}
344+
) {
345+
}
317346

318347
private record FlitModule(
319348
@Nullable String name
320-
) {}
349+
) {
350+
}
321351
}
322352

0 commit comments

Comments
 (0)