mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
PY-87290 Collect dependencies from groups and tool specific keys
These changes support collecting python project dependencies from PEP 621 standard pyproject.toml locations (including dependency groups). Also a support for tool specific keys to collect their dependencies was added as well. (cherry picked from commit cc376c1696b3ddd435e113cfa39b547d60f0f791) IJ-MR-193200 GitOrigin-RevId: 6d87d4983755525322ca0a4f2c1aedc4510a817b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
03f5e63efe
commit
e81dfd761d
@@ -4,6 +4,7 @@ package com.intellij.python.hatch.impl
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.python.common.tools.ToolId
|
||||
import com.intellij.python.hatch.icons.PythonHatchIcons
|
||||
import com.intellij.python.pyproject.model.internal.pyProjectToml.TomlDependencySpecification
|
||||
import com.intellij.python.pyproject.model.spi.ProjectName
|
||||
import com.intellij.python.pyproject.model.spi.ProjectStructureInfo
|
||||
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
|
||||
@@ -26,5 +27,10 @@ internal class HatchTool : Tool {
|
||||
|
||||
override suspend fun getProjectName(projectToml: TomlTable): @NlsSafe String? = null
|
||||
|
||||
override suspend fun getProjectStructure(entries: Map<ProjectName, PyProjectTomlProject>, rootIndex: Map<Directory, ProjectName>): ProjectStructureInfo? = null
|
||||
override suspend fun getProjectStructure(
|
||||
entries: Map<ProjectName, PyProjectTomlProject>,
|
||||
rootIndex: Map<Directory, ProjectName>,
|
||||
): ProjectStructureInfo? = null
|
||||
|
||||
override fun getTomlDependencySpecifications(): List<TomlDependencySpecification> = emptyList()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.python.common.tools.ToolId
|
||||
import com.intellij.python.community.impl.poetry.common.POETRY_TOOL_ID
|
||||
import com.intellij.python.community.impl.poetry.common.POETRY_UI_INFO
|
||||
import com.intellij.python.pyproject.model.internal.pyProjectToml.TomlDependencySpecification
|
||||
import com.intellij.python.pyproject.model.spi.ProjectName
|
||||
import com.intellij.python.pyproject.model.spi.ProjectStructureInfo
|
||||
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
|
||||
@@ -24,6 +25,14 @@ internal class PoetryTool : Tool {
|
||||
override suspend fun getProjectName(projectToml: TomlTable): @NlsSafe String? =
|
||||
projectToml.getString("tool.poetry.name")
|
||||
|
||||
override suspend fun getProjectStructure(entries: Map<ProjectName, PyProjectTomlProject>, rootIndex: Map<Directory, ProjectName>): ProjectStructureInfo? = null
|
||||
override suspend fun getProjectStructure(
|
||||
entries: Map<ProjectName, PyProjectTomlProject>,
|
||||
rootIndex: Map<Directory, ProjectName>,
|
||||
): ProjectStructureInfo? = null
|
||||
|
||||
override fun getTomlDependencySpecifications(): List<TomlDependencySpecification> = listOf(
|
||||
TomlDependencySpecification.PathDependency("tool.poetry.dependencies"),
|
||||
TomlDependencySpecification.PathDependency("tool.poetry.dev-dependencies"),
|
||||
TomlDependencySpecification.GroupPathDependency("tool.poetry.group", "dependencies"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ sealed class TomlTableSafeGetError {
|
||||
* On a missing value, returns an instance of [Result.Success] with a value of null.
|
||||
* On a present value with the valid type, returns an instance of [Result.Success] with that value.
|
||||
*
|
||||
* @param key is the key in a table
|
||||
* @param unquotedDottedKey specifies whether a key should be treated as a dotted key without quotes
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
@@ -46,8 +49,10 @@ sealed class TomlTableSafeGetError {
|
||||
* ```
|
||||
*/
|
||||
@Internal
|
||||
inline fun <reified T> TomlTable.safeGet(key: String): Result<T?, TomlTableSafeGetError.UnexpectedType> {
|
||||
val value = get("\"$key\"")
|
||||
inline fun <reified T> TomlTable.safeGet(
|
||||
key: String, unquotedDottedKey: Boolean = false,
|
||||
): Result<T?, TomlTableSafeGetError.UnexpectedType> {
|
||||
val value = if (unquotedDottedKey) get(key) else get("\"$key\"")
|
||||
|
||||
if (value == null) {
|
||||
return success(null)
|
||||
@@ -101,6 +106,9 @@ internal inline fun <reified T> TomlTable.safeGetRequired(key: String): Result<T
|
||||
* On a missing value, returns an instance of [Result.Success] with a value of null.
|
||||
* On a present value with the valid type, returns an instance of [Result.Success] with a [List] of type [T].
|
||||
*
|
||||
* @param key is the key in a table
|
||||
* @param unquotedDottedKey specifies whether a key should be treated as a dotted key without quotes
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
@@ -116,8 +124,10 @@ internal inline fun <reified T> TomlTable.safeGetRequired(key: String): Result<T
|
||||
* ```
|
||||
*/
|
||||
@Internal
|
||||
inline fun <reified T> TomlTable.safeGetArr(key: String): Result<List<T>?, TomlTableSafeGetError.UnexpectedType> {
|
||||
val array = safeGet<TomlArray>(key).getOr { return it }
|
||||
inline fun <reified T> TomlTable.safeGetArr(
|
||||
key: String, unquotedDottedKey: Boolean = false,
|
||||
): Result<List<T>?, TomlTableSafeGetError.UnexpectedType> {
|
||||
val array = safeGet<TomlArray>(key, unquotedDottedKey).getOr { return it }
|
||||
|
||||
if (array == null) {
|
||||
return success(null)
|
||||
|
||||
@@ -6,12 +6,14 @@ import com.intellij.python.pyproject.PyProjectToml
|
||||
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.safeGetArr
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.venvReader.Directory
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.apache.tuweni.toml.TomlTable
|
||||
import java.io.IOException
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
@@ -81,23 +83,6 @@ suspend fun walkFileSystemNoTomlContent(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun getPEP621Deps(
|
||||
entries: Map<ProjectName, PyProjectTomlProject>,
|
||||
rootIndex: Map<Directory, ProjectName>,
|
||||
): ProjectDependencies = withContext(Dispatchers.Default) {
|
||||
val deps = entries.asSequence().associate { (name, entry) ->
|
||||
val deps = getDependenciesFromToml(entry.pyProjectToml).mapNotNull { dir ->
|
||||
rootIndex[dir] ?: run {
|
||||
logger.warn("Can't find project for dir $dir")
|
||||
null
|
||||
}
|
||||
}.toSet()
|
||||
Pair(name, deps)
|
||||
}
|
||||
ProjectDependencies(deps) // No workspace info (yet)
|
||||
}
|
||||
|
||||
private val logger = fileLogger()
|
||||
|
||||
private suspend fun readFile(file: Path): PyProjectToml? {
|
||||
@@ -118,16 +103,93 @@ private suspend fun readFile(file: Path): PyProjectToml? {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun getDependenciesFromToml(projectToml: PyProjectToml): Set<Directory> {
|
||||
val depsFromFile = projectToml.project?.dependencies?.project ?: emptyList()
|
||||
val moduleDependencies = depsFromFile
|
||||
.mapNotNull { depSpec ->
|
||||
val match = PEP_621_PATH_DEPENDENCY.matchEntire(depSpec) ?: return@mapNotNull null
|
||||
val (_, depUri) = match.destructured
|
||||
return@mapNotNull parseDepUri(depUri)
|
||||
suspend fun getDependenciesFromToml(
|
||||
entries: Map<ProjectName, PyProjectTomlProject>,
|
||||
rootIndex: Map<Directory, ProjectName>,
|
||||
tomlDependencySpecifications: List<TomlDependencySpecification>,
|
||||
): ProjectDependencies = withContext(Dispatchers.Default) {
|
||||
val deps = entries.asSequence().associate { (name, entry) ->
|
||||
val depsPaths = collectAllDependencies(entry, tomlDependencySpecifications)
|
||||
val deps = processDependenciesWithRootIndex(depsPaths, rootIndex)
|
||||
Pair(name, deps)
|
||||
}
|
||||
ProjectDependencies(deps)
|
||||
}
|
||||
|
||||
private fun processDependenciesWithRootIndex(dependencies: Sequence<Directory>, rootIndex: Map<Directory, ProjectName>): Set<ProjectName> =
|
||||
dependencies.mapNotNull { dir ->
|
||||
rootIndex[dir] ?: run {
|
||||
logger.warn("Can't find project for dir $dir")
|
||||
null
|
||||
}
|
||||
return moduleDependencies.toSet()
|
||||
}.toSet()
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun collectAllDependencies(
|
||||
entry: PyProjectTomlProject, tomlDependencySpecifications: List<TomlDependencySpecification>,
|
||||
): Sequence<Directory> = sequence {
|
||||
yieldAll(getDependenciesFromProject(entry.pyProjectToml))
|
||||
yieldAll(getDependenciesFromPep735Groups(entry.pyProjectToml.toml))
|
||||
yieldAll(getToolSpecificDependencies(entry.root, entry.pyProjectToml.toml, tomlDependencySpecifications))
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun getToolSpecificDependencies(
|
||||
root: Path, tomlTable: TomlTable, tomlDependencySpecifications: List<TomlDependencySpecification>,
|
||||
): Sequence<Directory> {
|
||||
return tomlDependencySpecifications.asSequence().flatMap { specification ->
|
||||
when (specification) {
|
||||
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.GroupPathDependency -> {
|
||||
val groups = tomlTable.getTable(specification.tomlKeyToGroup) ?: return@flatMap emptySet()
|
||||
groups.keySet().flatMap { group ->
|
||||
groups.getTable("${group}.${specification.tomlKeyFromGroupToPath}")?.let {
|
||||
getToolSpecificDependenciesFromTomlTable(root, it)
|
||||
} ?: emptySet()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun getToolSpecificDependenciesFromTomlTable(root: Path, tomlTable: TomlTable): Set<Directory> {
|
||||
return tomlTable.keySet().asSequence().mapNotNull {
|
||||
tomlTable.getString("${it}.path")?.let { depPathString -> parseDepFromPathString(root, depPathString) }
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun getDependenciesFromPep735Groups(tomlTable: TomlTable): Sequence<Directory> {
|
||||
val groups = tomlTable.getTable("dependency-groups") ?: return emptySequence()
|
||||
return groups.keySet().asSequence().flatMap { group ->
|
||||
val deps = groups.safeGetArr<String>(group).successOrNull ?: emptyList()
|
||||
deps.asSequence().mapNotNull(::parsePep621Dependency)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun getDependenciesFromProject(projectToml: PyProjectToml): Sequence<Directory> {
|
||||
val depsFromFile = projectToml.project?.dependencies?.project ?: emptyList()
|
||||
return depsFromFile.asSequence().mapNotNull(::parsePep621Dependency)
|
||||
}
|
||||
|
||||
private fun parsePep621Dependency(depSpec: String): Path? {
|
||||
val match = PEP_621_PATH_DEPENDENCY.matchEntire(depSpec) ?: return null
|
||||
val (_, depUri) = match.destructured
|
||||
return parseDepUri(depUri)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -145,4 +207,13 @@ private fun parseDepUri(depUri: String): Path? =
|
||||
catch (e: URISyntaxException) {
|
||||
logger.info("Dep $depUri can't be parsed", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseDepFromPathString(root: Path, depPathString: String): Path? =
|
||||
try {
|
||||
root.resolve(depPathString).normalize()
|
||||
}
|
||||
catch (e: InvalidPathException) {
|
||||
logger.info("Dep $depPathString points to wrong path", e)
|
||||
null
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import com.intellij.python.pyproject.PyProjectToml
|
||||
import com.intellij.python.pyproject.model.internal.PY_PROJECT_SYSTEM_ID
|
||||
import com.intellij.python.pyproject.model.internal.PyProjectTomlBundle
|
||||
import com.intellij.python.pyproject.model.internal.pyProjectToml.FSWalkInfoWithToml
|
||||
import com.intellij.python.pyproject.model.internal.pyProjectToml.getPEP621Deps
|
||||
import com.intellij.python.pyproject.model.internal.pyProjectToml.getDependenciesFromToml
|
||||
import com.intellij.python.pyproject.model.spi.ProjectName
|
||||
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
|
||||
import com.intellij.python.pyproject.model.spi.Tool
|
||||
@@ -188,8 +188,8 @@ private suspend fun generatePyProjectTomlEntries(
|
||||
val entriesByName = entries.associateBy { it.name }
|
||||
val namesByDir = entries.associate { Pair(it.root, it.name) }
|
||||
val allNames = entriesByName.keys
|
||||
var dependencies = getPEP621Deps(entriesByName, namesByDir)
|
||||
for (tool in Tool.EP.extensionList) {
|
||||
var dependencies = getDependenciesFromToml(entriesByName, namesByDir, tools.flatMap { it.getTomlDependencySpecifications() })
|
||||
for (tool in tools) {
|
||||
// Tool provides deps and workspace members
|
||||
val toolSpecificInfo = tool.getProjectStructure(entriesByName, namesByDir)
|
||||
// Tool-agnostic pep621 deps
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.intellij.python.pyproject.model.spi
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.python.common.tools.ToolId
|
||||
import com.intellij.python.pyproject.model.internal.pyProjectToml.TomlDependencySpecification
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.venvReader.Directory
|
||||
import org.apache.tuweni.toml.TomlTable
|
||||
@@ -25,10 +26,18 @@ interface Tool {
|
||||
* All project names must be taken from provided data (use [rootIndex] to get name by directory).
|
||||
* If tool doesn't provide any specific structure (i.e: no dependencies except those described in pyproject.toml spec, no workspaces) return `null`
|
||||
*/
|
||||
suspend fun getProjectStructure(entries: Map<ProjectName, PyProjectTomlProject>, rootIndex: Map<Directory, ProjectName>): ProjectStructureInfo?
|
||||
suspend fun getProjectStructure(
|
||||
entries: Map<ProjectName, PyProjectTomlProject>,
|
||||
rootIndex: Map<Directory, ProjectName>,
|
||||
): ProjectStructureInfo?
|
||||
|
||||
/**
|
||||
* Tool that supports build systems might return additional src directories
|
||||
*/
|
||||
suspend fun getSrcRoots(toml: TomlTable, projectRoot: Directory): Set<Directory>
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool-specific toml sections where dependencies may be specified
|
||||
*/
|
||||
fun getTomlDependencySpecifications(): List<TomlDependencySpecification>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
package com.intellij.python.junit5Tests.unit.pyproject
|
||||
|
||||
import com.intellij.python.pyproject.PyProjectToml
|
||||
import com.intellij.python.pyproject.model.internal.pyProjectToml.TomlDependencySpecification
|
||||
import com.intellij.python.pyproject.model.internal.pyProjectToml.getDependenciesFromToml
|
||||
import com.intellij.python.pyproject.model.spi.ProjectName
|
||||
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
|
||||
import com.intellij.testFramework.common.timeoutRunBlocking
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.createDirectories
|
||||
|
||||
internal class GetDependenciesFromTomlTest {
|
||||
|
||||
private data class TestProject(
|
||||
override val pyProjectToml: PyProjectToml,
|
||||
override val root: Path,
|
||||
) : PyProjectTomlProject
|
||||
|
||||
@Test
|
||||
fun `pep 621 project dependencies`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
|
||||
val mainDir = tempDir.resolve("main").createDirectories()
|
||||
val libDir = tempDir.resolve("lib").createDirectories()
|
||||
|
||||
val libUri = libDir.toUri()
|
||||
val toml = PyProjectToml.parse("""
|
||||
[project]
|
||||
name = "main"
|
||||
version = "1.0"
|
||||
dependencies = ["lib @ $libUri"]
|
||||
""".trimIndent())
|
||||
|
||||
val mainName = ProjectName("main")
|
||||
val libName = ProjectName("lib")
|
||||
|
||||
val entries = mapOf(mainName to TestProject(toml, mainDir))
|
||||
val rootIndex = mapOf(libDir to libName)
|
||||
|
||||
val result = getDependenciesFromToml(entries, rootIndex, emptyList())
|
||||
assertThat(result.map[mainName]).containsExactly(libName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `pep 735 dependency groups`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
|
||||
val mainDir = tempDir.resolve("main").createDirectories()
|
||||
val libDir = tempDir.resolve("lib").createDirectories()
|
||||
val testLibDir = tempDir.resolve("test-lib").createDirectories()
|
||||
|
||||
val libUri = libDir.toUri()
|
||||
val testLibUri = testLibDir.toUri()
|
||||
val toml = PyProjectToml.parse("""
|
||||
[project]
|
||||
name = "main"
|
||||
version = "1.0"
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["lib @ $libUri"]
|
||||
test = ["test-lib @ $testLibUri"]
|
||||
""".trimIndent())
|
||||
|
||||
val mainName = ProjectName("main")
|
||||
val libName = ProjectName("lib")
|
||||
val testLibName = ProjectName("test-lib")
|
||||
|
||||
val entries = mapOf(mainName to TestProject(toml, mainDir))
|
||||
val rootIndex = mapOf(libDir to libName, testLibDir to testLibName)
|
||||
|
||||
val result = getDependenciesFromToml(entries, rootIndex, emptyList())
|
||||
assertThat(result.map[mainName]).containsExactlyInAnyOrder(libName, testLibName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tool PathDependency`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
|
||||
val mainDir = tempDir.resolve("main").createDirectories()
|
||||
val libDir = tempDir.resolve("lib").createDirectories()
|
||||
|
||||
val toml = PyProjectToml.parse("""
|
||||
[project]
|
||||
name = "main"
|
||||
version = "1.0"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
lib = {path = "../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.PathDependency("tool.poetry.dependencies"))
|
||||
|
||||
val result = getDependenciesFromToml(entries, rootIndex, specs)
|
||||
assertThat(result.map[mainName]).containsExactly(libName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tool Pep621Dependency`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
|
||||
val mainDir = tempDir.resolve("main").createDirectories()
|
||||
val libDir = tempDir.resolve("lib").createDirectories()
|
||||
|
||||
val libUri = libDir.toUri()
|
||||
val toml = PyProjectToml.parse("""
|
||||
[project]
|
||||
name = "main"
|
||||
version = "1.0"
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = ["lib @ $libUri"]
|
||||
""".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.Pep621Dependency("tool.uv.dev-dependencies"))
|
||||
|
||||
val result = getDependenciesFromToml(entries, rootIndex, specs)
|
||||
assertThat(result.map[mainName]).containsExactly(libName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tool GroupPathDependency`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
|
||||
val mainDir = tempDir.resolve("main").createDirectories()
|
||||
val libDir = tempDir.resolve("lib").createDirectories()
|
||||
|
||||
val toml = PyProjectToml.parse("""
|
||||
[project]
|
||||
name = "main"
|
||||
version = "1.0"
|
||||
|
||||
[tool.poetry.group.test.dependencies]
|
||||
lib = {path = "../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.GroupPathDependency("tool.poetry.group", "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()
|
||||
|
||||
val toml = PyProjectToml.parse("""
|
||||
[project]
|
||||
name = "main"
|
||||
version = "1.0"
|
||||
dependencies = ["pytest>=7", "requests"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["black>=23"]
|
||||
""".trimIndent())
|
||||
|
||||
val mainName = ProjectName("main")
|
||||
val entries = mapOf(mainName to TestProject(toml, mainDir))
|
||||
|
||||
val result = getDependenciesFromToml(entries, emptyMap(), emptyList())
|
||||
assertThat(result.map[mainName]).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty and missing sections`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
|
||||
val mainDir = tempDir.resolve("main").createDirectories()
|
||||
|
||||
val toml = PyProjectToml.parse("""
|
||||
[project]
|
||||
name = "main"
|
||||
version = "1.0"
|
||||
""".trimIndent())
|
||||
|
||||
val mainName = ProjectName("main")
|
||||
val entries = mapOf(mainName to TestProject(toml, mainDir))
|
||||
val specs = listOf(
|
||||
TomlDependencySpecification.PathDependency("tool.poetry.dependencies"),
|
||||
TomlDependencySpecification.Pep621Dependency("tool.uv.dev-dependencies"),
|
||||
TomlDependencySpecification.GroupPathDependency("tool.poetry.group", "dependencies"),
|
||||
)
|
||||
|
||||
val result = getDependenciesFromToml(entries, emptyMap(), specs)
|
||||
assertThat(result.map[mainName]).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `mixed sources collected together`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
|
||||
val mainDir = tempDir.resolve("main").createDirectories()
|
||||
val pep621Lib = tempDir.resolve("pep621-lib").createDirectories()
|
||||
val groupLib = tempDir.resolve("group-lib").createDirectories()
|
||||
val toolLib = tempDir.resolve("tool-lib").createDirectories()
|
||||
|
||||
val pep621Uri = pep621Lib.toUri()
|
||||
val groupUri = groupLib.toUri()
|
||||
val toml = PyProjectToml.parse("""
|
||||
[project]
|
||||
name = "main"
|
||||
version = "1.0"
|
||||
dependencies = ["pep621-lib @ $pep621Uri"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["group-lib @ $groupUri"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
tool-lib = {path = "../tool-lib"}
|
||||
""".trimIndent())
|
||||
|
||||
val mainName = ProjectName("main")
|
||||
val pep621Name = ProjectName("pep621-lib")
|
||||
val groupName = ProjectName("group-lib")
|
||||
val toolName = ProjectName("tool-lib")
|
||||
|
||||
val entries = mapOf(mainName to TestProject(toml, mainDir))
|
||||
val rootIndex = mapOf(pep621Lib to pep621Name, groupLib to groupName, toolLib to toolName)
|
||||
val specs = listOf(TomlDependencySpecification.PathDependency("tool.poetry.dependencies"))
|
||||
|
||||
val result = getDependenciesFromToml(entries, rootIndex, specs)
|
||||
assertThat(result.map[mainName]).containsExactlyInAnyOrder(pep621Name, groupName, toolName)
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ class PyProjectTomlTest {
|
||||
val configContents = "[proj"
|
||||
|
||||
// WHEN
|
||||
val result = PyProjectToml.Companion.parse(configContents)
|
||||
val result = PyProjectToml.parse(configContents)
|
||||
|
||||
// THEN
|
||||
Assertions.assertThat(result.toml.errors()).isNotEmpty()
|
||||
@@ -51,7 +51,7 @@ class PyProjectTomlTest {
|
||||
bar="test bar"
|
||||
baz="test baz"
|
||||
""".trimIndent()
|
||||
val pyproject = PyProjectToml.Companion.parse(configContents)
|
||||
val pyproject = PyProjectToml.parse(configContents)
|
||||
|
||||
// WHEN
|
||||
val testTool = pyproject.getTool(TestPyProject)
|
||||
@@ -75,7 +75,7 @@ class PyProjectTomlTest {
|
||||
""".trimIndent()
|
||||
|
||||
// WHEN
|
||||
val pyproject = PyProjectToml.Companion.parse(configContents)
|
||||
val pyproject = PyProjectToml.parse(configContents)
|
||||
val testTool = pyproject.getTool(TestPyProject)
|
||||
|
||||
// THEN
|
||||
@@ -101,7 +101,7 @@ class PyProjectTomlTest {
|
||||
""".trimIndent()
|
||||
|
||||
// WHEN
|
||||
val pyproject = PyProjectToml.Companion.parse(configContents)
|
||||
val pyproject = PyProjectToml.parse(configContents)
|
||||
val testTool = pyproject.getTool(TestPyProject)
|
||||
|
||||
// THEN
|
||||
@@ -120,7 +120,7 @@ class PyProjectTomlTest {
|
||||
name="Some project"
|
||||
version="1.2.3"
|
||||
""".trimIndent()
|
||||
val pyproject = PyProjectToml.Companion.parse(configContents)
|
||||
val pyproject = PyProjectToml.parse(configContents)
|
||||
|
||||
// WHEN
|
||||
val testTool = pyproject.getTool(TestPyProject)
|
||||
@@ -133,7 +133,7 @@ class PyProjectTomlTest {
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("parseTestCases")
|
||||
fun parseTests(name: String, pyprojectToml: String, expectedProjectTable: PyProjectTable?, expectedIssues: List<PyProjectIssue>) {
|
||||
val result = PyProjectToml.Companion.parse(pyprojectToml)
|
||||
val result = PyProjectToml.parse(pyprojectToml)
|
||||
val unwrapped = result
|
||||
|
||||
assertEquals(expectedProjectTable, unwrapped.project)
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.intellij.openapi.util.getPathMatcher
|
||||
import com.intellij.python.common.tools.ToolId
|
||||
import com.intellij.python.community.impl.uv.common.UV_TOOL_ID
|
||||
import com.intellij.python.community.impl.uv.common.UV_UI_INFO
|
||||
import com.intellij.python.pyproject.model.internal.pyProjectToml.TomlDependencySpecification
|
||||
import com.intellij.python.pyproject.model.spi.ProjectDependencies
|
||||
import com.intellij.python.pyproject.model.spi.ProjectName
|
||||
import com.intellij.python.pyproject.model.spi.ProjectStructureInfo
|
||||
@@ -97,6 +98,11 @@ internal class UvTool : Tool {
|
||||
|
||||
}
|
||||
|
||||
override fun getTomlDependencySpecifications(): List<TomlDependencySpecification> = listOf(
|
||||
TomlDependencySpecification.PathDependency("tool.uv.sources"),
|
||||
TomlDependencySpecification.Pep621Dependency("tool.uv.dev-dependencies"),
|
||||
)
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun getWorkspaceMembers(toml: TomlTable): WorkspaceInfo? {
|
||||
val workspace = toml.getTable("tool.uv.workspace") ?: return null
|
||||
|
||||
Reference in New Issue
Block a user