跳转至

Project Model、Workspace Model 与 External System

项目结构是很多插件的基础:模块、内容根、源码根、排除目录、库、SDK、Facet、外部构建系统导入结果,都属于这一层。2024.2 起,官方文档明确说明 Workspace Model API 可供第三方插件使用,并推荐优先于旧 Project Model API。

Project Model 层级

传统 Project Model 把项目组织成四层:

层级 含义 常用 API
Project 顶层项目容器 ProjectProjectRootManager
Module 可独立构建、运行、测试的功能单元 ModuleModuleRootManager
Content Root 模块拥有的目录边界 ContentEntry
Source / Exclude Root 源码根和排除根 SourceFolderExcludeFolder

查询文件归属时优先使用 ProjectFileIndex

val index = ProjectFileIndex.getInstance(project)
val module = index.getModuleForFile(virtualFile)
val sourceRoot = index.getSourceRootForFile(virtualFile)
val isInSource = index.isInSource(virtualFile)

需要打开 Project Structure 中相关页面时,使用 ProjectSettingsService,不要依赖内部 UI 类。

Workspace Model

Workspace Model 用统一存储表示项目结构,核心概念包括:

概念 作用
WorkspaceEntity 项目结构实体接口
VersionedEntityStorage 当前版本化存储入口
ImmutableEntityStorage 不受后续修改影响的不可变快照
MutableEntityStorage 用于批量修改的可变副本
Entity Source 表示实体来源,例如项目文件、外部系统
Symbolic Reference 跨实体软引用

Workspace Model 的优势:

  • 统一入口,不再分别操作 ModuleManager、LibraryTable、FacetManager 等多个服务。
  • 快照不可变,读操作更安全。
  • 便于批量修改和跨进程复用。
  • 可以声明新的语言或框架实体,减少旧 Project Model 的 Java 历史包袱。

常见映射:

Project Model Workspace Model
Module ModuleEntity
ContentEntry ContentRootEntity
SourceFolder SourceRootEntity
Sdk SdkEntity
Library LibraryEntity
Facet FacetEntity

选择 Project Model 还是 Workspace Model

场景 建议
只查询某文件属于哪个模块或源码根 ProjectFileIndex
读取当前项目结构快照 Workspace Model
批量修改模块、库、内容根 Workspace Model
需要兼容较旧 IDE 版本 Project Model API 或兼容层
与已有语言插件 API 交互 先看该插件公开 API,必要时桥接 Project Model

不要为了拿一个模块名就引入复杂的 Workspace Model 修改流程;也不要在需要批量项目结构修改时继续手工管理多个 modifiable model。

修改项目结构

旧 Project Model 修改通常需要创建 modifiable model、执行写动作、提交并处理事件。Workspace Model 则通过 MutableEntityStorage 对副本修改,再提交到当前模型。

设计建议:

  • 批量修改项目结构时,尽量一次性提交。
  • 修改逻辑要能重入,因为外部系统同步、VFS 刷新和用户设置都可能触发项目结构变更。
  • 读取结构时使用快照,避免遍历过程中结构被修改。
  • 不要依赖内部 impl 类;如果公开 API 不足,先查官方示例和 API 状态。

External System Integration

External System 子系统用于接入 Maven、Gradle、sbt 等外部项目管理系统,也可以用于接入自定义构建系统。

外部系统通常需要提供:

  • 从外部配置文件构建项目模型,例如 pom.xmlbuild.gradle.kts
  • 展示可用任务。
  • 执行外部任务。
  • 导入模块、库、内容根、依赖等项目结构。
  • 在配置文件变化时自动刷新项目。

DataNode 与 ProjectDataService

External System 用 DataNode 表示导入出来的数据图:

DataNode<ProjectData>
  DataNode<ModuleData>
    DataNode<ContentRootData>
    DataNode<LibraryDependencyData>
  DataNode<LibraryData>

核心类型:

类型 作用
DataNode<T> 数据图节点
Key<T> 节点数据类型标识
ExternalEntityData 外部系统数据基类
ProjectDataService 把某类外部数据导入 IDE 项目模型

自定义数据服务注册:

<extensions defaultExtensionNs="com.intellij">
  <externalProjectDataService implementation="com.example.ExampleProjectDataService"/>
</extensions>

项目导入

导入入口:

  • ProjectImportBuilder
  • ProjectImportProvider
  • AbstractExternalProjectImportBuilder
  • AbstractExternalProjectImportProvider

注册:

<extensions defaultExtensionNs="com.intellij">
  <projectImportBuilder implementation="com.example.ExampleProjectImportBuilder"/>
  <projectImportProvider implementation="com.example.ExampleProjectImportProvider"/>
</extensions>

导入逻辑要把“解析外部模型”和“写入 IDE 项目结构”分开。解析外部配置可以在后台运行;写入项目结构必须遵守平台写入规则。

Auto-Import

外部系统配置文件变化后,IDE 可以自动刷新项目结构。从 2020.1 起,用户不能关闭 auto-import。

实现方式:

  • ExternalSystemManager 实现 ExternalSystemAutoImportAware,描述哪些设置文件影响哪个外部项目。
  • getAffectedExternalProjectPath() 调用频繁,必须非常快。
  • 可用 CachingExternalSystemAutoImportAware 缓存计算结果。
  • 没有 ExternalSystemManager 的独立系统可实现 ExternalSystemProjectAware,并注册到 ExternalSystemProjectTracker

刷新通知图标可通过 ExternalSystemIconProviderreloadIcon 提供。

External System 设置和测试

设置 UI:

  • 通用设置。
  • 已链接外部项目列表。
  • 当前外部项目设置。
  • 导入外部项目时的目标路径和选项。

项目级设置控件可继承 AbstractExternalProjectSettingsControl,导入 UI 可继承 AbstractImportFromExternalSystemControl

测试依赖:

dependencies {
    testImplementation("com.jetbrains.intellij.platform:external-system-test-framework")
}

常用基类:

  • ExternalSystemImportingTestCase
  • ExternalSystemTestCase

实战检查清单

  • 只查询文件所属模块时,用 ProjectFileIndex
  • 2024.2+ 新项目结构能力优先评估 Workspace Model。
  • 避免直接访问 Project Model 内部实现类。
  • External System 的模型解析和 IDE 导入分层实现。
  • Auto-import 判断函数必须快。
  • 大型项目同步提供进度、取消和日志。
  • 对导入结果写测试,覆盖模块、源码根、库、依赖和排除目录。

参考来源