Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@ on:
pull_request:
push:
branches: [main]
workflow_dispatch:
inputs:
sdk-revision:
description: 'Exact lithe-plugin-docs commit for development-only Java 8 validation'
required: true
type: string

permissions:
contents: read

jobs:
package:
if: github.event_name != 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
Expand Down Expand Up @@ -40,6 +47,15 @@ jobs:
env:
LITHE_TEST_JAVA: ${{ env.JAVA_HOME_8_X64 }}/bin/java
run: node scripts/test-backend.mjs
- name: Test native Provider execution and cleanup (Java 17)
env:
LITHE_REPORT_DIR: .artifacts/test-stability/native-java17
run: node scripts/test-native.mjs
- name: Test native Provider execution and cleanup (Java 8)
env:
LITHE_TEST_JAVA: ${{ env.JAVA_HOME_8_X64 }}/bin/java
LITHE_REPORT_DIR: .artifacts/test-stability/native-java8
run: node scripts/test-native.mjs
- name: Download Maven ${{ matrix.maven }}
if: matrix.maven != 'runner'
env:
Expand Down Expand Up @@ -79,3 +95,75 @@ jobs:
name: script-timings-maven-${{ matrix.maven }}
path: .artifacts/test-stability/
if-no-files-found: warn

development-sdk:
name: Development SDK / Maven ${{ matrix.maven }} / Java 8
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
maven: [runner, 3.6.3]
steps:
- uses: actions/checkout@v4
- name: Require a fixed SDK source revision
env:
SDK_REVISION: ${{ inputs.sdk-revision }}
run: '[[ "$SDK_REVISION" =~ ^[0-9a-f]{40}$ ]]'
- uses: actions/checkout@v4
with:
repository: Lithe-IDEA/lithe-plugin-docs
ref: ${{ inputs.sdk-revision }}
path: .artifacts/sdk-source
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: |
8
17
- name: Build the explicit development SDK and isolated plugin package
run: |
mvn -B -ntp -q -f .artifacts/sdk-source/sdk/java/pom.xml -DskipTests package
node scripts/package-development.mjs --sdk-jar "$GITHUB_WORKSPACE/.artifacts/sdk-source/sdk/java/plugin-sdk/target/lithe-plugin-sdk-java-0.1.0.jar"
node --input-type=module -e 'import {readFileSync,appendFileSync} from "node:fs"; const p=JSON.parse(readFileSync(".artifacts/native-development/latest.json")); if(p.developmentOnly!==true) throw Error("Expected a development package"); appendFileSync(process.env.GITHUB_ENV, `LITHE_TEST_PACKAGE=${p.package}\n`);'
- name: Check process cleanup
run: node scripts/test-process.mjs
- name: Test snapshot and native execution on Java 8
env:
LITHE_TEST_JAVA: ${{ env.JAVA_HOME_8_X64 }}/bin/java
run: |
node scripts/test-backend.mjs
node scripts/test-native.mjs
- name: Download Maven ${{ matrix.maven }}
if: matrix.maven != 'runner'
env:
VERSION: ${{ matrix.maven }}
run: |
base="https://archive.apache.org/dist/maven/maven-3/$VERSION/binaries/apache-maven-$VERSION-bin.tar.gz"
curl -fsSLo "$RUNNER_TEMP/maven.tar.gz" "$base"
echo "$(curl -fsSL "$base.sha512" | cut -d' ' -f1) $RUNNER_TEMP/maven.tar.gz" | sha512sum -c -
tar -xzf "$RUNNER_TEMP/maven.tar.gz" -C "$RUNNER_TEMP"
echo "MVN=$RUNNER_TEMP/apache-maven-$VERSION/bin/mvn" >> "$GITHUB_ENV"
- name: Run the real Maven reactor through the Java 8 Provider
env:
LITHE_TEST_JAVA: ${{ env.JAVA_HOME_8_X64 }}/bin/java
run: node scripts/test.mjs
- name: Prove the release inputs stayed unchanged
run: |
git diff --exit-code -- sdk.lock.json backend/pom.xml extension/pom.xml io.github.lithe-idea.maven-build
test ! -e dist
- name: Upload development timing and identity reports
if: always()
uses: actions/upload-artifact@v4
with:
name: development-sdk-maven-${{ matrix.maven }}
path: |
.artifacts/test-stability/
.artifacts/native-development/latest.json
.artifacts/native-development/run-*/identity.json
if-no-files-found: warn
60 changes: 58 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

为 [Lithe](https://github.com/1lck/Lithe-IDEA) 提供 Maven 项目支持:识别 `pom.xml`,在 Maven 工具窗口里列出
模块、生命周期和插件目标,运行任务和测试,查看依赖树。
这是一个**社区插件**,用 Lithe 的声明式构建系统接口(插件 API 3 的 `buildSystems`)实现,
这是一个**社区插件**,用 Lithe 的构建系统接口(插件 API 3 的 `buildSystems` 和 `buildProviders: 3`)实现,
除了下面说的 Maven 扩展 jar 和读取项目用的 Java 后端,没有任何宿主代码。

> English: Maven support for Lithe through the declarative `buildSystems` plugin API — project tree, lifecycle and
Expand Down Expand Up @@ -56,11 +56,60 @@ brew install maven
指纹不变时,重新打开项目或展开模块直接用快照,不启动 Maven。
- 同时到达的请求共用一次 Maven 运行;全部请求都取消后才结束 Maven。运行期间 POM 被修改时,本次结果照常显示但不保存。
- 指纹看不到的变化(例如根目录外的父 POM、远程仓库里更新的 SNAPSHOT)请点 Reload,它总是重新运行 Maven。
- 不会编译或打包,也不会生成 `target/`;解析依赖时只读 POM,不下载依赖 jar。
- 读取模型和依赖不会编译或打包,也不会生成 `target/`;只有用户显式执行任务或测试才运行构建。解析依赖时只读 POM,不下载依赖 jar。
- 没有 Lithe 的属性时扩展什么都不做;插件目录在运行时保持只读。

读取项目会执行 POM 中声明的 Maven 扩展和插件,首次读取还可能联网下载插件描述,所以 Lithe 会先确认你信任这个工作区。

## 原生任务与测试(开发中)

当前源码把生命周期、模型中的插件目标、自定义目标以及测试统一交给同一个工作区
Maven 后端执行。清单里这些入口的命令模板已删除,宿主仅传任务 ID、工程选择、
生效选项和原始参数数组。Maven 自己读取工程并执行;插件没有重写 POM、工程模型、
依赖求解或 Surefire。普通任务的 Skip Tests 仍按任务语义生效;显式测试动作不会
被这个工具栏开关跳过。

嵌套工作区使用宿主提供的工作区相对路径,执行前按当前 Maven 扩展模型重新确认
模块名称、目录及插件目标,插件目标不能借一个伪造 properties.goal 运行。输出通过
Provider 进度传回,最终结果保留非零退出码;取消或宿主退出会结束本次 Maven 与子进程。
总输出最多 8 MiB,超限会明确失败并停止工具。报告和编译输出仍写用户工程的 target;
快照写宿主提供的工作区状态目录,插件安装目录保持只读。

测试的类和方法选择映射到 Surefire 的公开接口,特殊模式明确拒绝,不把一个精确
请求扩大成通配匹配。使用 -am 构建依赖模块时,它们可以没有所选测试;执行结束后
还检查当前模块/反应堆是否产生所选测试的新报告,避免“不存在的方法 + 成功空结果”
或旧报告被当成测试通过。完整结果仍由宿主既有 JUnit XML 解析器展示。

插件通过 buildModelInputs 1 显式公开规范化项目、目标、内容目录及 profiles/offline/
skipTests 的生效值;共享内容不包含任务、运行计划、settings.xml 内容或私有函数。
这为后续 Java 协作提供数据入口,不表示 Java/JDT、旧 MavenService、Agent 或设置
已经完成迁移。

**发布前依赖尚未完成:** 该源码需要包含原生执行处理器的新版 Java SDK,正式
sdk.lock.json 仍锁定已有发布物,没有伪造新版本/校验值或放松 SHA-256 检查。
必须先发布配套 SDK 并更新锁定值,之后才可走正式 package.mjs 和 Release 流程。
当前只使用显式的本地开发构建验证,不应把它作为可发布包。

```bash
node scripts/package-development.mjs --sdk-jar /absolute/path/to/development-sdk.jar
# 上一步打印独立目录,或读取 .artifacts/native-development/latest.json 的 package。
export LITHE_TEST_PACKAGE=/absolute/path/printed/above/io.github.lithe-idea.maven-build
node scripts/test-backend.mjs
node scripts/test-native.mjs
node scripts/test.mjs
# 可选:在包含 buildModelInputs 的当前宿主 Core 上完整验证计划、报告及模型共享。
LITHE_CORE_LIBRARY=/absolute/path/to/liblithe_core.dylib node scripts/test-native-core.mjs
```

开发构建在 .artifacts/native-development/run-* 的独立源码副本中编译,记录显式 SDK
的 SHA-256 和完整包文件清单;不生成发行 ZIP、不写 dist/,不改 sdk.lock.json。
这些工作区产物不能跨工作树共享,不是产品运行缓存。测试保留每项 JSON/HTML/JUnit
耗时并清理所有本次进程。真实 Core 验证还需要 Python 3 的标准 ctypes 模块,不会安装
Python 包。Windows 产品尚未接入本契约,本地 Java 21 验证也不能代替 Java 8 实机验收。

公开接口依据:[Maven CLI](https://maven.apache.org/ref/3-LATEST/maven-embedder/cli.html)、
[Surefire 的测试选择](https://maven.apache.org/surefire/maven-surefire-plugin/test-mojo)。

## 安装

Lithe Store 的 Maven 条目合并后,才可在上述兼容开发版中打开 **设置 → 插件 → 插件市场**,搜索 “Maven”,安装并按提示重启。
Expand Down Expand Up @@ -143,6 +192,13 @@ node scripts/verify-scripts.mjs --host-bundle /Applications/Lithe.app

## 发布新版本

合并前验证新版 SDK 源码时,可以手动运行 CI 的 `workflow_dispatch`,传入
`sdk-revision`(`lithe-plugin-docs` 的完整提交 SHA)。该模式只构建隔离的开发包,
在 Java 8 上执行快照、原生任务/取消/清理与真实 Maven 反应堆测试,覆盖当前 Maven
和最低支持的 3.6.3。报告包含 SDK JAR 哈希和包文件清单;不会修改 `sdk.lock.json`、
生成 `dist/` 或发布工件。普通 PR、主分支和 Release 继续使用正式 SDK 的地址与哈希
校验,开发验证通过不能代替正式打包。

1. 同时修改 `plugin.json`、`extension/pom.xml` 和 `backend/pom.xml` 的版本(例如 `0.2.0`;打包脚本会检查三者一致),
确认 `sdk.lock.json` 已填入正式发布的 SDK 地址和 SHA-256,合入 `main`。
2. 推送同名标签:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
Expand Down Expand Up @@ -61,9 +58,23 @@ private void serve() {
});
backend.onModel((params, context) -> copy(load(params, context).model()));
backend.onDependencies(this::dependencies);
backend.onExecute((params, context) -> execution(params, context, false));
backend.onTest((params, context) -> execution(params, context, true));
backend.listen();
}

private Object execution(Map<String, Object> params, CallContext context, boolean tests) throws Exception {
Path root = root(params);
Map<String, String> environment = environment(params.get("environment"));
Object tool = params.get("tool");
String executable = tool instanceof Map ? Json.string(Json.object(tool), "executable") : null;
Path maven = MavenExecutable.resolve(executable, root, environment);
// Model and execution use the same effective options and Maven runtime.
// The extension remains the owner of reactor and plugin-goal facts.
MavenRuns.Loaded loaded = load(params, context);
return MavenExecution.run(params, context, root, workspaceRoot, maven, environment, loaded, tests);
}

private Object dependencies(Map<String, Object> params, CallContext context) throws Exception {
Path root = root(params);
MavenRuns.Loaded loaded = load(params, context);
Expand All @@ -76,7 +87,7 @@ private Object dependencies(Map<String, Object> params, CallContext context) thr
throw new PluginError("maven.invalidProject", "The selected project has no path.");
}
Map<String, Object> result = new LinkedHashMap<>();
result.put("dependencies", Projects.dependenciesOf(loaded, root, path));
result.put("dependencies", Projects.dependenciesOf(loaded, workspaceRoot, path));
return result;
}

Expand All @@ -92,16 +103,21 @@ private MavenRuns.Loaded load(Map<String, Object> params, CallContext context) t
Object tool = params.get("tool");
String executable = tool instanceof Map ? Json.string(Json.object(tool), "executable") : null;
Path maven = MavenExecutable.resolve(executable, root, environment);
Inputs inputs = new Inputs(root, maven, strings(params.get("optionArguments")),
Inputs inputs = new Inputs(root, maven, MavenExecution.optionArguments(params, false),
environment, hostEnvironment(params.get("environment")), extensionJar());
boolean refresh = Boolean.TRUE.equals(params.get("refresh"));
return runs.load(inputs, refresh, context.cancellation());
}

private Path root(Map<String, Object> params) {
String root = Json.string(params, "root");
Path base = workspaceRoot == null ? Paths.get("").toAbsolutePath() : workspaceRoot;
return base.resolve(root == null || root.isEmpty() ? "." : root).toAbsolutePath().normalize();
private Path root(Map<String, Object> params) throws IOException {
if (workspaceRoot == null) throw new PluginError("maven.notInitialized", "The Maven backend was not initialized.");
String value = Json.string(params, "root");
if (value == null || value.isEmpty() || Paths.get(value).isAbsolute()) throw new PluginError("maven.invalidRoot", "Expected a workspace-relative Maven root.");
Path root = workspaceRoot.resolve(value).normalize().toRealPath();
if (!root.startsWith(workspaceRoot) || !Files.isRegularFile(root.resolve("pom.xml"))) {
throw new PluginError("maven.invalidRoot", "The Maven root must contain pom.xml inside the workspace.");
}
return root;
}

/**
Expand Down Expand Up @@ -130,19 +146,6 @@ private static Map<String, String> hostEnvironment(Object fromHost) {
return result;
}

private static List<String> strings(Object value) {
if (!(value instanceof List)) {
return Collections.emptyList();
}
List<String> result = new ArrayList<>();
for (Object item : (List<?>) value) {
if (item instanceof String) {
result.add((String) item);
}
}
return result;
}

/** The extension ships next to this JAR: {@code lib/lithe-maven-extension.jar}. */
private static Path extensionJar() {
try {
Expand All @@ -166,7 +169,7 @@ private static Path workspaceRoot(String rootUri) throws IOException {
return Paths.get("").toAbsolutePath();
}
// `Paths.get(URI)` accepts the trailing slash a directory URI may carry.
return Paths.get(URI.create(rootUri)).toAbsolutePath().normalize();
return Paths.get(URI.create(rootUri)).toRealPath();
}

/** A shallow copy: the SDK stamps {@code format} on the returned document. */
Expand Down
Loading
Loading