mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
PY-79488 Expose more system-specific information in project graphs
In particular, for uv, retain the information about workspace membership. Make discovery of nested workspace members more robust. GitOrigin-RevId: 9af260bbb47baac9d5fec7e2b605931ceb0ce3c4
This commit is contained in:
committed by
intellij-monorepo-bot
parent
4d3376bd64
commit
35c66600cc
@@ -19,10 +19,10 @@ import kotlin.reflect.KClass
|
||||
/**
|
||||
* Syncs the project model described in pyproject.toml files with the IntelliJ project model.
|
||||
*/
|
||||
abstract class BaseProjectModelService<E : EntitySource> {
|
||||
abstract class BaseProjectModelService<E : EntitySource, P : ExternalProject> {
|
||||
abstract val systemName: @NlsSafe String
|
||||
|
||||
abstract val projectModelResolver: PythonProjectModelResolver
|
||||
abstract val projectModelResolver: PythonProjectModelResolver<P>
|
||||
|
||||
abstract fun getSettings(project: Project): ProjectModelSettings
|
||||
|
||||
|
||||
@@ -10,9 +10,15 @@ import org.apache.tuweni.toml.TomlTable
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
|
||||
data class PoetryProject(
|
||||
override val name: String,
|
||||
override val root: Path,
|
||||
override val dependencies: List<ExternalProjectDependency>
|
||||
) : ExternalProject
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
object PoetryProjectModelResolver : PythonProjectModelResolver {
|
||||
override fun discoverProjectRootSubgraph(root: Path): ExternalProjectGraph? {
|
||||
object PoetryProjectModelResolver : PythonProjectModelResolver<PoetryProject> {
|
||||
override fun discoverProjectRootSubgraph(root: Path): ExternalProjectGraph<PoetryProject>? {
|
||||
if (!root.resolve(PoetryConstants.PYPROJECT_TOML).exists()) {
|
||||
return null
|
||||
}
|
||||
@@ -23,7 +29,7 @@ object PoetryProjectModelResolver : PythonProjectModelResolver {
|
||||
if (poetryProjects.isNotEmpty()) {
|
||||
val modules = poetryProjects
|
||||
.map {
|
||||
ExternalProject(
|
||||
PoetryProject(
|
||||
name = it.projectName,
|
||||
root = it.root,
|
||||
dependencies = it.editablePathDependencies.map { entry ->
|
||||
|
||||
@@ -17,8 +17,8 @@ import kotlin.reflect.KClass
|
||||
/**
|
||||
* Syncs the project model described in pyproject.toml files with the IntelliJ project model.
|
||||
*/
|
||||
object PoetryProjectModelService : BaseProjectModelService<PoetryEntitySource>() {
|
||||
override val projectModelResolver: PythonProjectModelResolver
|
||||
object PoetryProjectModelService : BaseProjectModelService<PoetryEntitySource, PoetryProject>() {
|
||||
override val projectModelResolver: PythonProjectModelResolver<PoetryProject>
|
||||
get() = PoetryProjectModelResolver
|
||||
|
||||
override val systemName: @NlsSafe String
|
||||
|
||||
@@ -12,17 +12,21 @@ import kotlin.io.path.visitFileTree
|
||||
* These modules might depend on each other, but it's not a requirement.
|
||||
* The root itself can be a valid module root, but it's not a requirement.
|
||||
*/
|
||||
data class ExternalProjectGraph(val root: Path, val projects: List<ExternalProject>)
|
||||
data class ExternalProjectGraph<P : ExternalProject>(val root: Path, val projects: List<P>)
|
||||
|
||||
/**
|
||||
* Defines a project module in a particular directory with its unique name, and a set of module dependencies
|
||||
* (usually editable Python path dependencies to other modules in the same IJ project).
|
||||
*/
|
||||
data class ExternalProject(val name: String, val root: Path, val dependencies: List<ExternalProjectDependency>)
|
||||
interface ExternalProject {
|
||||
val name: String
|
||||
val root: Path
|
||||
val dependencies: List<ExternalProjectDependency>
|
||||
}
|
||||
|
||||
data class ExternalProjectDependency(val name: String, val path: Path)
|
||||
|
||||
interface PythonProjectModelResolver {
|
||||
interface PythonProjectModelResolver<P : ExternalProject> {
|
||||
/**
|
||||
* If the `root` directory is considered a project root in a particular project management system
|
||||
* (e.g. it contains pyproject.toml or other such marker files), traverse it and return a subgraph describing modules
|
||||
@@ -41,7 +45,7 @@ interface PythonProjectModelResolver {
|
||||
* this method should return `null` for `libs/` but module graphs containing *only* modules `project1` and `project2`
|
||||
* for the directories `project1/` and `project2` respectively, even if there is a dependency between them.
|
||||
*/
|
||||
fun discoverProjectRootSubgraph(root: Path): ExternalProjectGraph?
|
||||
fun discoverProjectRootSubgraph(root: Path): ExternalProjectGraph<P>?
|
||||
|
||||
/**
|
||||
* Find all project model graphs within the given directory (presumably the root directory of an IJ project).
|
||||
@@ -56,13 +60,13 @@ interface PythonProjectModelResolver {
|
||||
* project2/
|
||||
* pyproject.toml
|
||||
* ```
|
||||
* If `project1` depends on `project2` (or vice-versa), this methods should return a single graph with its
|
||||
* If `project1` depends on `project2` (or vice-versa), this methods should return a single graph with its
|
||||
* root in `libs/` containing modules for both `project1` and `project2`.
|
||||
* If these two projects are independents, there will be two graphs for `project1` and `project2` respectively.
|
||||
*/
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
fun discoverIndependentProjectGraphs(root: Path): List<ExternalProjectGraph> {
|
||||
val graphs = mutableListOf<ExternalProjectGraph>()
|
||||
fun discoverIndependentProjectGraphs(root: Path): List<ExternalProjectGraph<P>> {
|
||||
val graphs = mutableListOf<ExternalProjectGraph<P>>()
|
||||
root.visitFileTree {
|
||||
onPreVisitDirectory { dir, _ ->
|
||||
val buildSystemRoot = discoverProjectRootSubgraph(dir)
|
||||
@@ -91,7 +95,7 @@ interface ProjectModelSyncListener {
|
||||
fun onFinish(projectRoot: Path): Unit = Unit
|
||||
}
|
||||
|
||||
private fun mergeRootsReferringToEachOther(roots: MutableList<ExternalProjectGraph>): List<ExternalProjectGraph> {
|
||||
private fun <P : ExternalProject> mergeRootsReferringToEachOther(roots: List<ExternalProjectGraph<P>>): List<ExternalProjectGraph<P>> {
|
||||
fun commonAncestorPath(paths: Iterable<Path>): Path {
|
||||
val normalized = paths.map { it.normalize() }
|
||||
return normalized.reduce { p1, p2 -> FileUtil.findAncestor(p1, p2)!! }
|
||||
@@ -106,7 +110,7 @@ private fun mergeRootsReferringToEachOther(roots: MutableList<ExternalProjectGra
|
||||
}
|
||||
|
||||
val expandedProjectRootsByRootPath = expandedProjectRoots.sortedBy { it.root }
|
||||
val mergedProjectRoots = mutableListOf<ExternalProjectGraph>()
|
||||
val mergedProjectRoots = mutableListOf<ExternalProjectGraph<P>>()
|
||||
for (root in expandedProjectRootsByRootPath) {
|
||||
if (mergedProjectRoots.isEmpty()) {
|
||||
mergedProjectRoots.add(root)
|
||||
|
||||
@@ -8,81 +8,139 @@ import com.jetbrains.python.projectModel.ExternalProjectGraph
|
||||
import com.jetbrains.python.projectModel.PythonProjectModelResolver
|
||||
import org.apache.tuweni.toml.Toml
|
||||
import org.apache.tuweni.toml.TomlTable
|
||||
import java.nio.file.FileVisitResult
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.PathMatcher
|
||||
import kotlin.io.path.*
|
||||
|
||||
private const val DEFAULT_VENV_DIR = ".venv"
|
||||
|
||||
data class UvProject(
|
||||
override val name: String,
|
||||
override val root: Path,
|
||||
override val dependencies: List<ExternalProjectDependency>,
|
||||
val isWorkspace: Boolean,
|
||||
val parentWorkspace: UvProject?,
|
||||
) : ExternalProject
|
||||
|
||||
private data class UvPyProjectToml(
|
||||
val projectName: String,
|
||||
val root: Path,
|
||||
val workspaceDependencies: List<String>,
|
||||
val pathDependencies: Map<String, Path>,
|
||||
val workspaceMemberPathMatchers: List<PathMatcher>,
|
||||
val workspaceExcludePathMatchers: List<PathMatcher>,
|
||||
) {
|
||||
val isWorkspace = workspaceMemberPathMatchers.isNotEmpty()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
object UvProjectModelResolver : PythonProjectModelResolver {
|
||||
override fun discoverProjectRootSubgraph(root: Path): ExternalProjectGraph? {
|
||||
object UvProjectModelResolver : PythonProjectModelResolver<UvProject> {
|
||||
override fun discoverProjectRootSubgraph(root: Path): ExternalProjectGraph<UvProject>? {
|
||||
if (!root.resolve(UvConstants.PYPROJECT_TOML).exists()) {
|
||||
return null
|
||||
}
|
||||
val rootUvProject = readUvPyProjectToml(root / "pyproject.toml")
|
||||
if (rootUvProject == null) {
|
||||
return null
|
||||
}
|
||||
val workspaceMemberMatchers = rootUvProject.workspaceMemberGlobs.map { getPathMatcher(it) }
|
||||
val workspaceExcludeMatchers = rootUvProject.workspaceExcludeGlobs.map { getPathMatcher(it) }
|
||||
// TODO check if we can speed up traversal by not traversing further into workspace member directories
|
||||
// Can workspace members contain editable path dependencies inside?
|
||||
val allUvProjects: List<UvPyProjectToml> = root
|
||||
.walk()
|
||||
// TODO skip excluded project directories
|
||||
.filterNot { it.startsWith(root / DEFAULT_VENV_DIR) }
|
||||
.filter { it.name == UvConstants.PYPROJECT_TOML }
|
||||
.mapNotNull(::readUvPyProjectToml)
|
||||
.toList()
|
||||
val workspaceMembers: Map<String, UvPyProjectToml>
|
||||
if (workspaceMemberMatchers.isEmpty()) {
|
||||
workspaceMembers = emptyMap()
|
||||
}
|
||||
else {
|
||||
workspaceMembers = allUvProjects
|
||||
.filter { uvProject ->
|
||||
if (uvProject == rootUvProject) return@filter true
|
||||
val relProjectPath = uvProject.root.relativeTo(root)
|
||||
return@filter workspaceExcludeMatchers.none { it.matches(relProjectPath) }
|
||||
&& workspaceMemberMatchers.any { it.matches(relProjectPath) }
|
||||
}.associateBy { it.projectName }
|
||||
}
|
||||
|
||||
return ExternalProjectGraph(
|
||||
root = root,
|
||||
projects = allUvProjects
|
||||
.map { uvProject ->
|
||||
val pathDependencies = uvProject.editablePathDependencies.map { ExternalProjectDependency(it.key, it.value) }
|
||||
val resolvedWorkspaceDependencies = uvProject.workspaceDependencies.mapNotNull {
|
||||
val workspaceMember = workspaceMembers[it]
|
||||
if (workspaceMember != null) ExternalProjectDependency(it, workspaceMember.root)
|
||||
else null
|
||||
}
|
||||
ExternalProject(
|
||||
name = uvProject.projectName,
|
||||
root =uvProject.root,
|
||||
dependencies = pathDependencies + resolvedWorkspaceDependencies
|
||||
)
|
||||
val workspaceMembers = mutableMapOf<UvPyProjectToml, MutableMap<String, UvPyProjectToml>>()
|
||||
val standaloneProjects = mutableListOf<UvPyProjectToml>()
|
||||
val workspaceStack = ArrayDeque<UvPyProjectToml>()
|
||||
root.visitFileTree {
|
||||
onPreVisitDirectory { dir, _ ->
|
||||
if (dir.name == DEFAULT_VENV_DIR) {
|
||||
return@onPreVisitDirectory FileVisitResult.SKIP_SUBTREE
|
||||
}
|
||||
)
|
||||
val projectToml = readUvPyProjectToml(dir / "pyproject.toml")
|
||||
if (projectToml == null) {
|
||||
return@onPreVisitDirectory FileVisitResult.CONTINUE
|
||||
}
|
||||
if (projectToml.isWorkspace) {
|
||||
workspaceStack.add(projectToml)
|
||||
workspaceMembers.put(projectToml, mutableMapOf())
|
||||
return@onPreVisitDirectory FileVisitResult.CONTINUE
|
||||
}
|
||||
if (workspaceStack.isNotEmpty()) {
|
||||
val closestWorkspace = workspaceStack.last()
|
||||
val relProjectPath = projectToml.root.relativeTo(closestWorkspace.root)
|
||||
val isWorkspaceMember = closestWorkspace.workspaceExcludePathMatchers.none { it.matches(relProjectPath) } &&
|
||||
closestWorkspace.workspaceMemberPathMatchers.any { it.matches(relProjectPath) }
|
||||
if (isWorkspaceMember) {
|
||||
workspaceMembers[closestWorkspace]!!.put(projectToml.projectName, projectToml)
|
||||
return@onPreVisitDirectory FileVisitResult.CONTINUE
|
||||
}
|
||||
}
|
||||
standaloneProjects.add(projectToml)
|
||||
return@onPreVisitDirectory FileVisitResult.CONTINUE
|
||||
}
|
||||
|
||||
onPostVisitDirectory { dir, _ ->
|
||||
if (workspaceStack.lastOrNull()?.root == dir) {
|
||||
workspaceStack.removeLast()
|
||||
}
|
||||
FileVisitResult.CONTINUE
|
||||
}
|
||||
}
|
||||
val allUvProjects = mutableListOf<UvProject>()
|
||||
standaloneProjects.mapTo(allUvProjects) {
|
||||
UvProject(
|
||||
name = it.projectName,
|
||||
root = it.root,
|
||||
dependencies = it.pathDependencies.map { dep -> ExternalProjectDependency(name = dep.key, path = dep.value) },
|
||||
isWorkspace = false,
|
||||
parentWorkspace = null,
|
||||
)
|
||||
}
|
||||
|
||||
for ((wsRootToml, wsMembersByNames) in workspaceMembers) {
|
||||
fun resolved(wsDependencies: List<String>): Map<String, Path> {
|
||||
return wsDependencies.mapNotNull { name -> wsMembersByNames[name]?.let { name to it.root } }.toMap()
|
||||
}
|
||||
val wsRootProject = UvProject(
|
||||
name = wsRootToml.projectName,
|
||||
root = wsRootToml.root,
|
||||
dependencies = (wsRootToml.pathDependencies + resolved(wsRootToml.workspaceDependencies))
|
||||
.map { ExternalProjectDependency(name = it.key, path = it.value) },
|
||||
isWorkspace = true,
|
||||
parentWorkspace = null,
|
||||
)
|
||||
allUvProjects.add(wsRootProject)
|
||||
for ((_, wsMemberToml) in wsMembersByNames) {
|
||||
allUvProjects.add(UvProject(
|
||||
name = wsMemberToml.projectName,
|
||||
root = wsMemberToml.root,
|
||||
dependencies = (wsMemberToml.pathDependencies + resolved(wsMemberToml.workspaceDependencies))
|
||||
.map { ExternalProjectDependency(name = it.key, path = it.value) },
|
||||
isWorkspace = false,
|
||||
parentWorkspace = wsRootProject,
|
||||
))
|
||||
}
|
||||
}
|
||||
return ExternalProjectGraph(root = root, projects = allUvProjects)
|
||||
}
|
||||
|
||||
private fun readUvPyProjectToml(pyprojectTomlPath: Path): UvPyProjectToml? {
|
||||
if (!(pyprojectTomlPath.exists())) {
|
||||
return null
|
||||
}
|
||||
val pyprojectToml = Toml.parse(pyprojectTomlPath)
|
||||
val projectName = pyprojectToml.getString("project.name")
|
||||
if (projectName == null) {
|
||||
return null
|
||||
}
|
||||
val workspaceTable = pyprojectToml.getTable("tool.uv.workspace")
|
||||
val includeGlobs = mutableListOf<String>()
|
||||
val excludeGlobs = mutableListOf<String>()
|
||||
val includeGlobs: List<PathMatcher>
|
||||
val excludeGlobs: List<PathMatcher>
|
||||
if (workspaceTable != null) {
|
||||
workspaceTable.getArrayOrEmpty("members")
|
||||
includeGlobs = workspaceTable.getArrayOrEmpty("members")
|
||||
.toList()
|
||||
.mapNotNullTo(includeGlobs) { it as? String }
|
||||
workspaceTable.getArrayOrEmpty("exclude")
|
||||
.filterIsInstance<String>()
|
||||
.map { getPathMatcher(it) }
|
||||
excludeGlobs = workspaceTable.getArrayOrEmpty("exclude")
|
||||
.toList()
|
||||
.mapNotNullTo(excludeGlobs) { it as? String }
|
||||
.filterIsInstance<String>()
|
||||
.map { getPathMatcher(it) }
|
||||
}
|
||||
else {
|
||||
includeGlobs = emptyList()
|
||||
excludeGlobs = emptyList()
|
||||
}
|
||||
val workspaceDependencies = mutableListOf<String>()
|
||||
val editablePathDependencies = mutableMapOf<String, Path>()
|
||||
@@ -95,7 +153,7 @@ object UvProjectModelResolver : PythonProjectModelResolver {
|
||||
}
|
||||
else if (depSpec.getBoolean("editable") == true) {
|
||||
val depPath = depSpec.getString("path")?.let { pyprojectTomlPath.parent.resolve(it).normalize() }
|
||||
if (depPath != null && depPath.isDirectory() && depPath.resolve(UvConstants.PYPROJECT_TOML).exists()) {
|
||||
if (depPath != null && depPath.isDirectory() && (depPath / UvConstants.PYPROJECT_TOML).exists()) {
|
||||
editablePathDependencies[depName] = depPath
|
||||
}
|
||||
}
|
||||
@@ -105,18 +163,9 @@ object UvProjectModelResolver : PythonProjectModelResolver {
|
||||
projectName = projectName,
|
||||
root = pyprojectTomlPath.parent,
|
||||
workspaceDependencies = workspaceDependencies,
|
||||
editablePathDependencies = editablePathDependencies,
|
||||
workspaceMemberGlobs = includeGlobs,
|
||||
workspaceExcludeGlobs = excludeGlobs,
|
||||
pathDependencies = editablePathDependencies,
|
||||
workspaceMemberPathMatchers = includeGlobs,
|
||||
workspaceExcludePathMatchers = excludeGlobs,
|
||||
)
|
||||
}
|
||||
|
||||
private data class UvPyProjectToml(
|
||||
val projectName: String,
|
||||
val root: Path,
|
||||
val workspaceDependencies: List<String>,
|
||||
val editablePathDependencies: Map<String, Path>,
|
||||
val workspaceMemberGlobs: List<String>,
|
||||
val workspaceExcludeGlobs: List<String>,
|
||||
)
|
||||
}
|
||||
@@ -17,8 +17,8 @@ import kotlin.reflect.KClass
|
||||
/**
|
||||
* Syncs the project model described in pyproject.toml files with the IntelliJ project model.
|
||||
*/
|
||||
object UvProjectModelService : BaseProjectModelService<UvEntitySource>() {
|
||||
override val projectModelResolver: PythonProjectModelResolver
|
||||
object UvProjectModelService : BaseProjectModelService<UvEntitySource, UvProject>() {
|
||||
override val projectModelResolver: PythonProjectModelResolver<UvProject>
|
||||
get() = UvProjectModelResolver
|
||||
|
||||
override val systemName: @NlsSafe String
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "root-ws-member"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.5"
|
||||
dependencies = []
|
||||
@@ -0,0 +1,12 @@
|
||||
[project]
|
||||
name = "ws-root"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.5"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = [
|
||||
"dir/subdir/root-ws-member",
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
[project]
|
||||
name = "intermediate-non-ws"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.5"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = [
|
||||
# "root-ws-member",
|
||||
]
|
||||
@@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "root-ws-member"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.5"
|
||||
dependencies = []
|
||||
@@ -0,0 +1,13 @@
|
||||
[project]
|
||||
name = "root-ws"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.5"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = [
|
||||
"root-ws-direct-member",
|
||||
"intermediate-non-ws/root-ws-member",
|
||||
]
|
||||
@@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "root-ws-direct-member"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.5"
|
||||
dependencies = []
|
||||
@@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "nested-ws-member"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.5"
|
||||
dependencies = []
|
||||
@@ -0,0 +1,12 @@
|
||||
[project]
|
||||
name = "nested-ws"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.5"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = [
|
||||
"nested-ws-member",
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
[project]
|
||||
name = "root-ws"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.5"
|
||||
dependencies = [
|
||||
"nested-ws-member",
|
||||
]
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = [
|
||||
"root-ws-member",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
nested-ws-member = { workspace = true }
|
||||
@@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "root-ws-member"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.5"
|
||||
dependencies = []
|
||||
@@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.5"
|
||||
dependencies = []
|
||||
@@ -0,0 +1,12 @@
|
||||
[project]
|
||||
name = "ws-root"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.5"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = [
|
||||
"ws-member",
|
||||
]
|
||||
@@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "ws-member"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10.5"
|
||||
dependencies = []
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.projectModel.uv
|
||||
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
import com.intellij.testFramework.junit5.fixture.tempPathFixture
|
||||
import com.intellij.util.io.copyRecursively
|
||||
import com.jetbrains.python.PythonTestUtil.getTestDataPath
|
||||
import com.jetbrains.python.projectModel.ExternalProjectGraph
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInfo
|
||||
import org.junit.jupiter.api.assertNotNull
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths.get
|
||||
import kotlin.io.path.div
|
||||
|
||||
@TestApplication
|
||||
class UvProjectModelResolverTest {
|
||||
private val testRoot by tempPathFixture()
|
||||
|
||||
private lateinit var testInfo: TestInfo
|
||||
|
||||
@BeforeEach
|
||||
fun setUp(testInfo: TestInfo) {
|
||||
this.testInfo = testInfo
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nestedWorkspaces() {
|
||||
val ijProjectRoot = testRoot / "project"
|
||||
testDataDir.copyRecursively(ijProjectRoot)
|
||||
val graph = UvProjectModelResolver.discoverProjectRootSubgraph(ijProjectRoot / "root-ws")
|
||||
assertNotNull(graph)
|
||||
assertEquals(4, graph.projects.size)
|
||||
|
||||
val rootWs = graph.project("root-ws")
|
||||
assertNotNull(rootWs)
|
||||
assertTrue(rootWs.isWorkspace)
|
||||
assertEquals(ijProjectRoot / "root-ws", rootWs.root)
|
||||
|
||||
val rootWsMember = graph.project("root-ws-member")
|
||||
assertNotNull(rootWsMember)
|
||||
assertFalse(rootWsMember.isWorkspace)
|
||||
assertEquals(ijProjectRoot / "root-ws" / "root-ws-member", rootWsMember.root)
|
||||
assertEquals(rootWs, rootWsMember.parentWorkspace)
|
||||
|
||||
val nestedWs = graph.project("nested-ws")
|
||||
assertNotNull(nestedWs)
|
||||
assertTrue(nestedWs.isWorkspace)
|
||||
assertEquals(ijProjectRoot / "root-ws" / "nested-ws", nestedWs.root)
|
||||
|
||||
val nestedWsMember = graph.project("nested-ws-member")
|
||||
assertNotNull(nestedWsMember)
|
||||
assertFalse(nestedWsMember.isWorkspace)
|
||||
assertEquals(ijProjectRoot / "root-ws" / "nested-ws" / "nested-ws-member", nestedWsMember.root)
|
||||
assertEquals(nestedWs, nestedWsMember.parentWorkspace)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun workspaceInsideStandalone() {
|
||||
val ijProjectRoot = testRoot / "project"
|
||||
testDataDir.copyRecursively(ijProjectRoot)
|
||||
val graph = UvProjectModelResolver.discoverProjectRootSubgraph(ijProjectRoot / "project")
|
||||
|
||||
assertNotNull(graph)
|
||||
assertEquals(3, graph.projects.size)
|
||||
|
||||
val standaloneProject = graph.project("project")
|
||||
assertNotNull(standaloneProject)
|
||||
assertFalse(standaloneProject.isWorkspace)
|
||||
assertEquals(ijProjectRoot / "project" , standaloneProject.root)
|
||||
|
||||
val wsRoot = graph.project("ws-root")
|
||||
assertNotNull(wsRoot)
|
||||
assertTrue(wsRoot.isWorkspace)
|
||||
assertEquals(ijProjectRoot / "project" / "ws-root", wsRoot.root)
|
||||
|
||||
val wsMember = graph.project("ws-member")
|
||||
assertNotNull(wsMember)
|
||||
assertFalse(wsMember.isWorkspace)
|
||||
assertEquals(ijProjectRoot / "project" / "ws-root" / "ws-member", wsMember.root)
|
||||
assertEquals(wsRoot, wsMember.parentWorkspace)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intermediateNonWorkspaceProjects() {
|
||||
val ijProjectRoot = testRoot / "project"
|
||||
testDataDir.copyRecursively(ijProjectRoot)
|
||||
val graph = UvProjectModelResolver.discoverProjectRootSubgraph(ijProjectRoot / "root-ws")
|
||||
|
||||
assertNotNull(graph)
|
||||
assertEquals(4, graph.projects.size)
|
||||
|
||||
val wsRoot = graph.project("root-ws")
|
||||
assertNotNull(wsRoot)
|
||||
assertTrue(wsRoot.isWorkspace)
|
||||
assertEquals(ijProjectRoot / "root-ws", wsRoot.root)
|
||||
|
||||
val intermediateNonWsProject = graph.project("intermediate-non-ws")
|
||||
assertNotNull(intermediateNonWsProject)
|
||||
assertFalse(intermediateNonWsProject.isWorkspace)
|
||||
assertNull(intermediateNonWsProject.parentWorkspace)
|
||||
assertEquals(ijProjectRoot / "root-ws" / "intermediate-non-ws", intermediateNonWsProject.root)
|
||||
|
||||
val nestedWsMember = graph.project("root-ws-member")
|
||||
assertNotNull(nestedWsMember)
|
||||
assertFalse(nestedWsMember.isWorkspace)
|
||||
assertEquals(wsRoot, nestedWsMember.parentWorkspace)
|
||||
assertEquals(ijProjectRoot / "root-ws" / "intermediate-non-ws" / "root-ws-member", nestedWsMember.root)
|
||||
|
||||
val directWsMember = graph.project("root-ws-direct-member")
|
||||
assertNotNull(directWsMember)
|
||||
assertFalse(directWsMember.isWorkspace)
|
||||
assertEquals(wsRoot, directWsMember.parentWorkspace)
|
||||
assertEquals(ijProjectRoot / "root-ws" / "root-ws-direct-member", directWsMember.root)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intermediateNonProjectDirs() {
|
||||
val ijProjectRoot = testRoot / "project"
|
||||
testDataDir.copyRecursively(ijProjectRoot)
|
||||
val graph = UvProjectModelResolver.discoverProjectRootSubgraph(ijProjectRoot / "ws-root")
|
||||
|
||||
assertNotNull(graph)
|
||||
assertEquals(2, graph.projects.size)
|
||||
|
||||
val wsRoot = graph.project("ws-root")
|
||||
assertNotNull(wsRoot)
|
||||
assertTrue(wsRoot.isWorkspace)
|
||||
assertEquals(ijProjectRoot / "ws-root", wsRoot.root)
|
||||
|
||||
val wsMember = graph.project("root-ws-member")
|
||||
assertNotNull(wsMember)
|
||||
assertFalse(wsMember.isWorkspace)
|
||||
assertEquals(wsRoot, wsMember.parentWorkspace)
|
||||
assertEquals(ijProjectRoot / "ws-root" / "dir" / "subdir" / "root-ws-member", wsMember.root)
|
||||
}
|
||||
|
||||
|
||||
private val testDataDir: Path
|
||||
get() = get(getTestDataPath()) / "projectModel" / testInfo.testMethod.get().name
|
||||
|
||||
private fun ExternalProjectGraph<UvProject>.project(name: String): UvProject? = projects.firstOrNull { it.name == name }
|
||||
}
|
||||
Reference in New Issue
Block a user