Skip to content

Commit 546a52e

Browse files
joke1196sonartech
authored andcommitted
SONARPY-3837 Reorder package root resolution and add src as default package when poetry backend is used (#889)
GitOrigin-RevId: c88192e0365cf2d16653419f7d9aa45a3a8a7997
1 parent 61220c2 commit 546a52e

3 files changed

Lines changed: 116 additions & 15 deletions

File tree

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,16 @@ public static List<String> resolve(List<String> extractedRoots, Configuration co
7070
* @return list of fallback package root absolute paths
7171
*/
7272
static List<String> resolveFallback(Configuration config, File baseDir) {
73-
String[] sonarSources = config.getStringArray(SONAR_SOURCES_KEY);
74-
if (sonarSources.length > 0) {
75-
return toAbsolutePaths(Arrays.asList(sonarSources), baseDir);
76-
}
77-
7873
List<String> conventionalFolders = findConventionalFolders(baseDir);
7974
if (!conventionalFolders.isEmpty()) {
8075
return toAbsolutePaths(conventionalFolders, baseDir);
8176
}
8277

78+
String[] sonarSources = config.getStringArray(SONAR_SOURCES_KEY);
79+
if (sonarSources.length > 0) {
80+
return toAbsolutePaths(Arrays.asList(sonarSources), baseDir);
81+
}
82+
8383
return List.of(baseDir.getAbsolutePath());
8484
}
8585

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

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
* <p>Supports the following build systems:
4040
* <ul>
4141
* <li>setuptools: {@code [tool.setuptools.packages.find] where = ["src"]}</li>
42-
* <li>Poetry: {@code [tool.poetry] packages = [{from = "src", include = "pkg"}]}</li>
42+
* <li>Poetry: {@code [tool.poetry] packages = [{from = "src", include = "pkg"}]}
43+
* or auto-detects src/ layout when build-backend is poetry.core.masonry.api</li>
4344
* <li>Hatchling: {@code [tool.hatch.build.targets.wheel] sources = ["src"]}</li>
4445
* <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>
4546
* <li>PDM: {@code [tool.pdm] package-dir = "src"}</li>
@@ -130,7 +131,7 @@ private static PyProjectExtractionResult extractFromConfig(PyProjectConfig confi
130131
detectedBuildSystems.add(PackageResolutionResult.BuildSystem.SETUPTOOLS);
131132
}
132133

133-
List<String> poetryRoots = extractFromPoetry(configTool.poetry());
134+
List<String> poetryRoots = extractFromPoetry(configTool.poetry(), config.buildSystem());
134135
if (!poetryRoots.isEmpty()) {
135136
sourceRoots.addAll(poetryRoots);
136137
detectedBuildSystems.add(PackageResolutionResult.BuildSystem.POETRY);
@@ -199,6 +200,16 @@ private static List<String> extractFromUVBuildSystem(@Nullable BuildSystem build
199200
return List.of();
200201
}
201202

203+
/**
204+
* Checks if the build backend is Poetry (e.g., poetry.core.masonry.api).
205+
*/
206+
private static boolean isPoetryBuildBackend(@Nullable BuildSystem buildSystem) {
207+
if (buildSystem == null || buildSystem.buildBackend() == null) {
208+
return false;
209+
}
210+
return buildSystem.buildBackend().contains("poetry");
211+
}
212+
202213
// === Setuptools ===
203214
// [tool.setuptools.packages.find]
204215
// where = ["src"]
@@ -213,16 +224,30 @@ private static List<String> extractFromSetuptools(@Nullable Setuptools setuptool
213224
// === Poetry ===
214225
// [tool.poetry]
215226
// packages = [{ include = "mypackage", from = "src" }]
227+
// Or auto-detects src/ layout when build-backend is poetry.core.masonry.api
228+
229+
private static List<String> extractFromPoetry(@Nullable Poetry poetry, @Nullable BuildSystem buildSystem) {
230+
// First try to get explicit "from" paths from packages
231+
if (poetry != null) {
232+
List<String> explicitRoots = poetry.packages().stream()
233+
.map(PoetryPackage::from)
234+
.filter(from -> from != null && !from.isEmpty())
235+
.distinct()
236+
.toList();
216237

217-
private static List<String> extractFromPoetry(@Nullable Poetry poetry) {
218-
if (poetry == null) {
219-
return List.of();
238+
if (!explicitRoots.isEmpty()) {
239+
return explicitRoots;
240+
}
220241
}
221-
return poetry.packages().stream()
222-
.map(PoetryPackage::from)
223-
.filter(from -> from != null && !from.isEmpty())
224-
.distinct()
225-
.toList();
242+
243+
// If Poetry is the build backend but no explicit paths, use src-layout default
244+
// Poetry auto-detects packages in src/ or project root; we default to src/
245+
// and rely on legacy fallback for flat-layout projects
246+
if (isPoetryBuildBackend(buildSystem)) {
247+
return List.of("src");
248+
}
249+
250+
return List.of();
226251
}
227252

228253
// === Hatchling ===

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

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,66 @@ void extract_poetry_onlyDependencies() {
193193
""")).isEmpty();
194194
}
195195

196+
// === Poetry Auto-Detection ===
197+
198+
@Test
199+
void extract_poetry_autoDetect_withBuildBackend_returnsSrc() {
200+
assertThat(extract("""
201+
[build-system]
202+
build-backend = "poetry.core.masonry.api"
203+
requires = ["poetry-core"]
204+
205+
[tool.poetry]
206+
name = "mypackage"
207+
version = "0.1.0"
208+
""")).containsExactly("src");
209+
}
210+
211+
@Test
212+
void extract_poetry_autoDetect_emptyPackages_returnsSrc() {
213+
assertThat(extract("""
214+
[build-system]
215+
build-backend = "poetry.core.masonry.api"
216+
requires = ["poetry-core"]
217+
218+
[tool.poetry]
219+
packages = []
220+
""")).containsExactly("src");
221+
}
222+
223+
@Test
224+
void extract_poetry_autoDetect_packagesWithoutFrom_returnsSrc() {
225+
assertThat(extract("""
226+
[build-system]
227+
build-backend = "poetry.core.masonry.api"
228+
requires = ["poetry-core"]
229+
230+
[tool.poetry]
231+
packages = [{ include = "mypackage" }]
232+
""")).containsExactly("src");
233+
}
234+
235+
@Test
236+
void extract_poetry_explicitFrom_overridesAutoDetect() {
237+
assertThat(extract("""
238+
[build-system]
239+
build-backend = "poetry.core.masonry.api"
240+
requires = ["poetry-core"]
241+
242+
[tool.poetry]
243+
packages = [{ include = "mypackage", from = "lib" }]
244+
""")).containsExactly("lib");
245+
}
246+
247+
@Test
248+
void extract_poetry_noBuildBackend_noPackages_returnsEmpty() {
249+
assertThat(extract("""
250+
[tool.poetry]
251+
name = "mypackage"
252+
version = "0.1.0"
253+
""")).isEmpty();
254+
}
255+
196256
// === Hatchling ===
197257

198258
@Test
@@ -550,6 +610,22 @@ void extractWithBuildSystem_poetry_detectsBuildSystem() {
550610
assertThat(result.buildSystem()).isEqualTo(PackageResolutionResult.BuildSystem.POETRY);
551611
}
552612

613+
@Test
614+
void extractWithBuildSystem_poetry_autoDetect_detectsBuildSystem() {
615+
var result = extractWithBuildSystem("""
616+
[build-system]
617+
build-backend = "poetry.core.masonry.api"
618+
requires = ["poetry-core"]
619+
620+
[tool.poetry]
621+
name = "mypackage"
622+
version = "0.1.0"
623+
""");
624+
625+
assertThat(result.relativeRoots()).containsExactly("src");
626+
assertThat(result.buildSystem()).isEqualTo(PackageResolutionResult.BuildSystem.POETRY);
627+
}
628+
553629
@Test
554630
void extractWithBuildSystem_hatchling_detectsBuildSystem() {
555631
var result = extractWithBuildSystem("""

0 commit comments

Comments
 (0)