mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-90798 Support Hatch context-formatted path dependencies
Resolve {root[:parent...]:uri} references relative to the owning
pyproject.toml to properly resolve dependencies.
ALso, support Hatch-specific grouped dependency arrays
(tool.hatch.envs.*.dependencies)
GitOrigin-RevId: 95da13cf9f4c023cd16681b489cffd94294b56b8
This commit is contained in:
committed by
intellij-monorepo-bot
parent
062627ad15
commit
95b9bde0e9
@@ -32,5 +32,7 @@ internal class HatchPyProjectManager : PyProjectManager {
|
||||
rootIndex: Map<Directory, ProjectName>,
|
||||
): ProjectStructureInfo? = null
|
||||
|
||||
override fun getTomlDependencySpecifications(): List<TomlDependencySpecification> = emptyList()
|
||||
override fun getTomlDependencySpecifications(): List<TomlDependencySpecification> = listOf(
|
||||
TomlDependencySpecification.GroupPep621Dependency("tool.hatch.envs", "dependencies"),
|
||||
)
|
||||
}
|
||||
|
||||
+45
-14
@@ -7,6 +7,7 @@ import com.intellij.python.pyproject.model.spi.ProjectDependencies
|
||||
import com.intellij.python.pyproject.model.spi.ProjectName
|
||||
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
|
||||
import com.intellij.python.pyproject.model.spi.TomlDependencySpecification
|
||||
import com.intellij.python.pyproject.safeGet
|
||||
import com.intellij.python.pyproject.safeGetArr
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.Result
|
||||
@@ -150,8 +151,8 @@ private fun processDependenciesWithRootIndex(dependencies: Sequence<Directory>,
|
||||
private fun collectAllDependencies(
|
||||
entry: PyProjectTomlProject, tomlDependencySpecifications: List<TomlDependencySpecification>,
|
||||
): Sequence<Directory> = sequence {
|
||||
yieldAll(getDependenciesFromProject(entry.pyProjectToml))
|
||||
yieldAll(getDependenciesFromPep735Groups(entry.pyProjectToml))
|
||||
yieldAll(getDependenciesFromProject(entry.root, entry.pyProjectToml))
|
||||
yieldAll(getDependenciesFromPep735Groups(entry.root, entry.pyProjectToml))
|
||||
yieldAll(getToolSpecificDependencies(entry.root, entry.pyProjectToml.toml, tomlDependencySpecifications))
|
||||
}
|
||||
|
||||
@@ -164,10 +165,7 @@ private fun getToolSpecificDependencies(
|
||||
is TomlDependencySpecification.PathDependency -> tomlTable.getTable(specification.tomlKey)?.let {
|
||||
getToolSpecificDependenciesFromTomlTable(root, it)
|
||||
} ?: emptySet()
|
||||
is TomlDependencySpecification.Pep621Dependency -> {
|
||||
val deps = tomlTable.safeGetArr<String>(specification.tomlKey, unquotedDottedKey = true).successOrNull ?: emptyList()
|
||||
deps.asSequence().mapNotNull(::parsePep621Dependency).toSet()
|
||||
}
|
||||
is TomlDependencySpecification.Pep621Dependency -> getPep621Dependencies(root, tomlTable, specification.tomlKey).toSet()
|
||||
is TomlDependencySpecification.GroupPathDependency -> {
|
||||
val groups = tomlTable.getTable(specification.tomlKeyToGroup) ?: return@flatMap emptySet()
|
||||
groups.keySet().flatMap { group ->
|
||||
@@ -176,10 +174,22 @@ private fun getToolSpecificDependencies(
|
||||
} ?: emptySet()
|
||||
}
|
||||
}
|
||||
is TomlDependencySpecification.GroupPep621Dependency -> {
|
||||
val groups = tomlTable.safeGet<TomlTable>(specification.tomlKeyToGroup, unquotedDottedKey = true).successOrNull ?: return@flatMap emptySet()
|
||||
groups.keySet().flatMap { group ->
|
||||
getPep621Dependencies(root, groups, "${group}.${specification.tomlKeyFromGroupToDependencies}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun getPep621Dependencies(root: Path, tomlTable: TomlTable, tomlKeyToDependencies: String): Set<Path> {
|
||||
val deps = tomlTable.safeGetArr<String>(tomlKeyToDependencies, unquotedDottedKey = true).successOrNull ?: return emptySet()
|
||||
return deps.asSequence().mapNotNull { parsePep621Dependency(root, it) }.toSet()
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun getToolSpecificDependenciesFromTomlTable(root: Path, tomlTable: TomlTable): Set<Directory> {
|
||||
return tomlTable.keySet().asSequence().mapNotNull {
|
||||
@@ -188,24 +198,45 @@ private fun getToolSpecificDependenciesFromTomlTable(root: Path, tomlTable: Toml
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun getDependenciesFromPep735Groups(tomlTable: PyProjectToml): Sequence<Directory> =
|
||||
tomlTable.project.dependencies.allDepsFromGroups.asSequence().mapNotNull(::parsePep621Dependency)
|
||||
private fun getDependenciesFromPep735Groups(root: Path, tomlTable: PyProjectToml): Sequence<Directory> =
|
||||
tomlTable.project.dependencies.allDepsFromGroups.asSequence().mapNotNull { parsePep621Dependency(root, it) }
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun getDependenciesFromProject(projectToml: PyProjectToml): Sequence<Directory> {
|
||||
private fun getDependenciesFromProject(root: Path, projectToml: PyProjectToml): Sequence<Directory> {
|
||||
val depsFromFile = projectToml.project.dependencies.project
|
||||
return depsFromFile.asSequence().mapNotNull(::parsePep621Dependency)
|
||||
return depsFromFile.asSequence().mapNotNull { parsePep621Dependency(root, it) }
|
||||
}
|
||||
|
||||
private fun parsePep621Dependency(depSpec: String): Path? {
|
||||
private fun parsePep621Dependency(root: Path, depSpec: String): Path? {
|
||||
val match = PEP_621_PATH_DEPENDENCY.matchEntire(depSpec) ?: return null
|
||||
val (_, depUri) = match.destructured
|
||||
return parseDepUri(depUri)
|
||||
val (_, directReference) = match.destructured
|
||||
return when {
|
||||
directReference.startsWith("file:") -> parseDepUri(directReference)
|
||||
else -> parseHatchContextFormattedPath(root, directReference)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// e.g. "lib @ file:///home/user/projects/main/lib"
|
||||
private val PEP_621_PATH_DEPENDENCY = """([\w-]+) @ (file:.*)""".toRegex()
|
||||
private val PEP_621_PATH_DEPENDENCY = """([\w-]+) @ (.*)""".toRegex()
|
||||
// e.g. "{root:parent:uri}/lib"
|
||||
private val HATCH_ROOT_URI = """\{root((?::parent)*):uri}(/.*)?""".toRegex()
|
||||
|
||||
private fun parseHatchContextFormattedPath(root: Path, directReference: String): Path? {
|
||||
val match = HATCH_ROOT_URI.matchEntire(directReference) ?: return null
|
||||
val (parentModifiers, relativePath) = match.destructured
|
||||
val parentCount = parentModifiers.split(':').count { it == "parent" }
|
||||
val formattedRoot = root.nthParent(parentCount) ?: return null
|
||||
return parseDepFromPathString(formattedRoot, relativePath.removePrefix("/"))
|
||||
}
|
||||
|
||||
private fun Path.nthParent(count: Int): Path? {
|
||||
var current: Path? = this
|
||||
for (i in 0 until count) {
|
||||
current = current?.parent ?: return null
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
private fun parseDepUri(depUri: String): Path? =
|
||||
try {
|
||||
|
||||
+2
-1
@@ -4,4 +4,5 @@ sealed interface TomlDependencySpecification {
|
||||
data class PathDependency(val tomlKey: String) : TomlDependencySpecification
|
||||
data class Pep621Dependency(val tomlKey: String) : TomlDependencySpecification
|
||||
data class GroupPathDependency(val tomlKeyToGroup: String, val tomlKeyFromGroupToPath: String) : TomlDependencySpecification
|
||||
}
|
||||
data class GroupPep621Dependency(val tomlKeyToGroup: String, val tomlKeyFromGroupToDependencies: String) : TomlDependencySpecification
|
||||
}
|
||||
|
||||
+62
@@ -1,5 +1,6 @@
|
||||
package com.intellij.python.junit5Tests.unit.alsoWin.pyproject
|
||||
|
||||
import com.intellij.idea.TestFor
|
||||
import com.intellij.python.pyproject.PyProjectToml
|
||||
import com.intellij.python.pyproject.model.internal.pyProjectToml.getDependenciesFromToml
|
||||
import com.intellij.python.pyproject.model.spi.ProjectName
|
||||
@@ -42,6 +43,38 @@ internal class GetDependenciesFromTomlTest {
|
||||
assertThat(result.map[mainName]).containsExactly(libName)
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestFor(issues = ["PY-90798"])
|
||||
fun `hatch context formatted project dependencies`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
|
||||
val workspaceDir = tempDir.resolve("workspace")
|
||||
val packagesDir = workspaceDir.resolve("packages")
|
||||
val mainDir = packagesDir.resolve("main").createDirectories()
|
||||
val childDir = mainDir.resolve("child").createDirectories()
|
||||
val siblingDir = packagesDir.resolve("sibling").createDirectories()
|
||||
val sharedDir = workspaceDir.resolve("shared").createDirectories()
|
||||
|
||||
val toml = PyProjectToml.parse("""
|
||||
[project]
|
||||
name = "main"
|
||||
version = "1.0"
|
||||
dependencies = [
|
||||
"child @ {root:uri}/child",
|
||||
"sibling @ {root:parent:uri}/sibling",
|
||||
"shared @ {root:parent:parent:uri}/shared",
|
||||
]
|
||||
""".trimIndent())!!
|
||||
|
||||
val mainName = ProjectName("main")
|
||||
val childName = ProjectName("child")
|
||||
val siblingName = ProjectName("sibling")
|
||||
val sharedName = ProjectName("shared")
|
||||
val entries = mapOf(mainName to TestProject(toml, mainDir))
|
||||
val rootIndex = mapOf(childDir to childName, siblingDir to siblingName, sharedDir to sharedName)
|
||||
|
||||
val result = getDependenciesFromToml(entries, rootIndex, emptyList())
|
||||
assertThat(result.map[mainName]).containsExactlyInAnyOrder(childName, siblingName, sharedName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `pep 735 dependency groups`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
|
||||
val mainDir = tempDir.resolve("main").createDirectories()
|
||||
@@ -147,6 +180,35 @@ internal class GetDependenciesFromTomlTest {
|
||||
assertThat(result.map[mainName]).containsExactly(libName)
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestFor(issues = ["PY-90798"])
|
||||
fun `tool GroupPep621Dependency`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
|
||||
val workspaceDir = tempDir.resolve("workspace")
|
||||
val mainDir = workspaceDir.resolve("main").createDirectories()
|
||||
val libDir = workspaceDir.resolve("lib").createDirectories()
|
||||
|
||||
val toml = PyProjectToml.parse("""
|
||||
[project]
|
||||
name = "main"
|
||||
version = "1.0"
|
||||
|
||||
[tool.hatch.envs.default]
|
||||
dependencies = [
|
||||
"pytest>=8",
|
||||
"lib @ {root:parent:uri}/lib",
|
||||
]
|
||||
""".trimIndent())!!
|
||||
|
||||
val mainName = ProjectName("main")
|
||||
val libName = ProjectName("lib")
|
||||
val entries = mapOf(mainName to TestProject(toml, mainDir))
|
||||
val rootIndex = mapOf(libDir to libName)
|
||||
val specs = listOf(TomlDependencySpecification.GroupPep621Dependency("tool.hatch.envs", "dependencies"))
|
||||
|
||||
val result = getDependenciesFromToml(entries, rootIndex, specs)
|
||||
assertThat(result.map[mainName]).containsExactly(libName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `non-path dependencies are ignored`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
|
||||
val mainDir = tempDir.resolve("main").createDirectories()
|
||||
|
||||
+8
-10
@@ -1,8 +1,10 @@
|
||||
// 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.idea.TestFor
|
||||
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
|
||||
@@ -10,7 +12,6 @@ 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
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
|
||||
@PyDefaultTestApplication
|
||||
@TestClassInfo(contentRootPath ="python-pyproject/test")
|
||||
@@ -23,16 +24,13 @@ internal class HatchWorkspacePathDependencyTest {
|
||||
private val f by pyProjectTomlSyncFixture(projectFixture, tempDirFixture)
|
||||
|
||||
@Test
|
||||
@TestFor(issues = ["PY-90798"])
|
||||
fun sanity(): Unit = timeoutRunBlocking {
|
||||
f.reloadProject()
|
||||
// PY-87339 Hatch monorepo local dependencies are not show in settings
|
||||
// PY-86924 Hatch workspace members are not handled correctly
|
||||
assertThrows<AssertionError> {
|
||||
f.assertProjectStructure(
|
||||
ExpectedModule("my-hatch-monorepo", contentRoot = ".", deps = listOf("package_a", "package_b")),
|
||||
ExpectedModule("package_a", contentRoot = "packages/package_a"),
|
||||
ExpectedModule("package_b", contentRoot = "packages/package_b", deps = listOf("package_a")),
|
||||
)
|
||||
}
|
||||
f.assertProjectStructure(
|
||||
ExpectedModule("my-hatch-monorepo", contentRoot = ".", deps = listOf("package_a", "package_b"), sourceRoots = listOf(".")),
|
||||
ExpectedModule("package_a", contentRoot = "packages" / "package_a"),
|
||||
ExpectedModule("package_b", contentRoot = "packages" / "package_b", deps = listOf("package_a")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user