mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-91195 uv workspace: per-dependency [tool.uv.sources] arrays (multiple sources by marker) are not resolved as module dependencies
Merge-request: IJ-MR-215299 Merged-by: Ilya Kazakevich <ilya.kazakevich@jetbrains.com> GitOrigin-RevId: 91cd1b8caeb075e0802acef3b48cc022062afd0e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
51ef102a77
commit
689fa89daa
+39
@@ -0,0 +1,39 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.python.junit5Tests.unit.alsoWin.pyproject.model.testplan
|
||||
|
||||
import com.intellij.python.junit5Tests.framework.PyDefaultTestApplication
|
||||
import com.intellij.python.junit5Tests.framework.metaInfo.TestClassInfo
|
||||
import com.intellij.python.junit5Tests.unit.alsoWin.pyproject.div
|
||||
import com.intellij.python.junit5Tests.unit.alsoWin.pyproject.model.ExpectedModule
|
||||
import com.intellij.python.junit5Tests.unit.alsoWin.pyproject.model.pyProjectTomlSyncFixture
|
||||
import com.intellij.testFramework.TestDataPath
|
||||
import com.intellij.testFramework.common.timeoutRunBlocking
|
||||
import com.intellij.testFramework.junit5.fixture.projectFixture
|
||||
import com.intellij.testFramework.junit5.fixture.tempPathFixture
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* PY-91195 follow-up: each `[tool.uv.sources]` entry is an array of tables (multiple sources selected
|
||||
* by platform marker — a valid uv feature), and the workspace is declared with the inline-table form
|
||||
* `[tool.uv] workspace = { members = [...] }`. All members must be discovered as modules.
|
||||
*/
|
||||
@PyDefaultTestApplication
|
||||
@TestClassInfo(contentRootPath = "python-pyproject/test")
|
||||
@TestDataPath($$"$CONTENT_ROOT/../testData/monorepo/PY-91195-uv-sources-marker-array")
|
||||
internal class UvSourcesMarkerArrayTest {
|
||||
companion object {
|
||||
private val tempDirFixture = tempPathFixture()
|
||||
private val projectFixture = projectFixture(pathFixture = tempDirFixture)
|
||||
}
|
||||
private val f by pyProjectTomlSyncFixture(projectFixture, tempDirFixture)
|
||||
|
||||
@Test
|
||||
fun sanity(): Unit = timeoutRunBlocking {
|
||||
f.reloadProject()
|
||||
f.assertProjectStructure(
|
||||
ExpectedModule("pythonproject", contentRoot = ".", deps = listOf("sub-project-a", "sub-project-b"), sourceRoots = listOf(".")),
|
||||
ExpectedModule("sub-project-a", contentRoot = "sub-projects" / "sub-project-a", sourceRoots = listOf("sub-projects" / "sub-project-a" / "src")),
|
||||
ExpectedModule("sub-project-b", contentRoot = "sub-projects" / "sub-project-b", sourceRoots = listOf("sub-projects" / "sub-project-b" / "src")),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# pythonproject
|
||||
|
||||
PY-91195 follow-up: a uv workspace where each `[tool.uv.sources]` entry is an array of tables
|
||||
(multiple sources selected by platform marker — a valid uv feature). The members must still be
|
||||
discovered as modules.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
[project]
|
||||
name = "pythonproject"
|
||||
version = "0.1.0"
|
||||
description = "Root project"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"sub-project-a",
|
||||
"sub-project-b",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
workspace = { members = ["sub-projects/*"] }
|
||||
|
||||
[tool.uv.sources]
|
||||
sub-project-a = [
|
||||
{ workspace = true, marker = "sys_platform == 'linux'" },
|
||||
{ workspace = true, marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sub-project-b = [
|
||||
{ workspace = true, marker = "sys_platform == 'linux'" },
|
||||
{ workspace = true, marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
[project]
|
||||
name = "sub-project-a"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/sub_project_a"]
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
def ping() -> str:
|
||||
return "sub-project-a"
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
[project]
|
||||
name = "sub-project-b"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/sub_project_b"]
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
def ping() -> str:
|
||||
return "sub-project-b"
|
||||
@@ -203,26 +203,40 @@ private fun getUvDependencies(
|
||||
val workspaceDeps = mutableListOf<ProjectName>()
|
||||
val pathDeps = hashSetOf<Path>()
|
||||
for ((sourcesTable, ownerRoot) in sourcesTablesWithRoots) {
|
||||
for ((sourceKey, depTable) in sourcesTable.toMap().entries) {
|
||||
for ((sourceKey, sourceValue) in sourcesTable.toMap().entries) {
|
||||
val normalizedKey = PyPackageName.normalizeProjectName(sourceKey)
|
||||
val depName = depByNormalizedName[normalizedKey] ?: continue
|
||||
val table = depTable as? TomlTable ?: continue
|
||||
|
||||
// PY-91089: safeGet instead of getBoolean/getString, which throw when the value has an unexpected type.
|
||||
if (table.safeGet<Boolean>("workspace").successOrNull == true) {
|
||||
workspaceDeps.add(ProjectName(depName))
|
||||
depByNormalizedName.remove(normalizedKey)
|
||||
// A source entry is either a single table or an array of tables (multiple sources selected by
|
||||
// platform marker — a valid uv feature); resolve every table it contains (PY-91195).
|
||||
val depTables = when (sourceValue) {
|
||||
is TomlTable -> listOf(sourceValue)
|
||||
is TomlArray -> sourceValue.toList().filterIsInstance<TomlTable>()
|
||||
else -> continue
|
||||
}
|
||||
else {
|
||||
val path = table.safeGet<String>("path").successOrNull ?: continue
|
||||
try {
|
||||
pathDeps.add(ownerRoot.resolve(path).normalize())
|
||||
depByNormalizedName.remove(normalizedKey)
|
||||
|
||||
var resolved = false
|
||||
for (table in depTables) {
|
||||
// PY-91089: safeGet instead of getBoolean/getString, which throw when the value has an unexpected type.
|
||||
if (table.safeGet<Boolean>("workspace").successOrNull == true) {
|
||||
workspaceDeps.add(ProjectName(depName))
|
||||
resolved = true
|
||||
}
|
||||
catch (e: InvalidPathException) {
|
||||
logger.info("Can't resolve $path against $ownerRoot", e)
|
||||
else {
|
||||
val path = table.safeGet<String>("path").successOrNull ?: continue
|
||||
try {
|
||||
pathDeps.add(ownerRoot.resolve(path).normalize())
|
||||
resolved = true
|
||||
}
|
||||
catch (e: InvalidPathException) {
|
||||
logger.info("Can't resolve $path against $ownerRoot", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Once resolved by a higher-priority table, don't let parent workspace tables override it.
|
||||
if (resolved) {
|
||||
depByNormalizedName.remove(normalizedKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
return DependencyInfo(workspaceDeps = workspaceDeps.toSet(), pathDeps = pathDeps)
|
||||
|
||||
Reference in New Issue
Block a user