Skip to content
Merged
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
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ jobs:
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Test package notices
run: node --test --test-timeout=60000 scripts/package/collect-notices.test.mjs
- name: Check formatting
run: cargo fmt --all --check
- name: Lint
Expand All @@ -46,6 +51,9 @@ jobs:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 22
# scripts/assemble-package.sh compiles the plugin backend for each
# architecture it packages.
- uses: dtolnay/rust-toolchain@stable
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Test package notices
run: node --test --test-timeout=60000 scripts/package/collect-notices.test.mjs
- name: Check formatting
run: cargo fmt --all --check
- name: Lint
Expand Down
4 changes: 4 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ here from the Lithe repository; the file names its origin.

Lithe is not affiliated with OpenIDEA, JetBrains s.r.o., the Eclipse
Foundation, Microsoft or their products.

Rust build dependency license texts and a per-file SHA-256 manifest are
provided under licenses/rust/ in distributed plugin archives. The manifest
records the locked crate names, versions and license expressions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Java Support for Lithe

> **Viva 预览版 Java 支持(1.1.1)**:清单已迁到当前契约 0,后端使用 Core
> **Viva 预览版 Java 支持(1.1.2)**:清单已迁到当前契约 0,后端使用 Core
> `65cf82343c5e315aae2e335b2e8f0217b703e89a` 中的 Rust SDK 0.4.0(尚无对应发布标签)。
> 本轮验证含第三方 JAR 依赖的普通 Maven 工程:JDT 补全、Maven 编译、main 运行和断点调试,包含变量读取、继续和退出。
> 需要同时包含 Lithe 客户端的动作注册解码、动作刷新与首次调试加载修复;未修改这些行为的旧 preview 不能直接使用本包。
Expand All @@ -24,7 +24,7 @@
> SDK. Maven and Gradle import settings come from the Maven and Gradle plugins through a function interface that
> Lithe Core relays between plugins.

1.1.1 面向包含主仓库 [#1884](https://github.com/1lck/Lithe-IDEA/pull/1884) 的配套 Viva preview(客户端修订 `607f00967d70a7017d49805183d0d2e6ffdf1c0d` 或更新版本)。API / 能力与传输版本以 `plugin.json`、`sdk.lock.json`
1.1.2 面向包含主仓库 [#1884](https://github.com/1lck/Lithe-IDEA/pull/1884) 的配套 Viva preview(客户端修订 `607f00967d70a7017d49805183d0d2e6ffdf1c0d` 或更新版本)。API / 能力与传输版本以 `plugin.json`、`sdk.lock.json`
和固定 Core 中的[插件资料](https://github.com/Lithe-IDEA/Lithe-core-rust/tree/65cf82343c5e315aae2e335b2e8f0217b703e89a/shared/plugins)为准。
`MIGRATION.md` 保留迁移历史,不能用其中旧宿主、旧 API 数字推断当前兼容性。

Expand Down Expand Up @@ -233,7 +233,7 @@ scripts/ # 组装、校验、归档插件包,检查 SD
MIGRATION.md # 主仓库路径到本仓库的映射、待宿主接口清单、主仓库删除清单
```

Rust 部分需要 Rust 工具链和 Cargo;打包脚本需要 macOS、`zsh`、`curl`、`ditto`,不需要 Xcode 或 Swift 工具链。
Rust 部分需要 Rust 工具链和 Cargo;打包脚本需要 macOS、Node.js 22、`zsh`、`curl`、`ditto`,不需要 Xcode 或 Swift 工具链。
打包脚本会用 `cargo build --release --locked -p lithe-java-backend --target <triple>` 编译后端,所以还要安装两个 Apple target:
`rustup target add aarch64-apple-darwin x86_64-apple-darwin`(`--arch arm64` 用前者,`x86_64` 用后者,`universal` 两个都编译再用 `lipo -create` 合并)。

Expand Down Expand Up @@ -280,3 +280,7 @@ node scripts/package/build-windows.mjs /path/to/lithe-java-backend.exe
## 许可

插件自身的代码按 Apache License 2.0 发布(见 `LICENSE`)。插件包内的第三方组件各自保留原许可,见 `NOTICE`。

### 随包许可

两端构建都会复制自身 LICENSE/NOTICE,并用 Cargo 锁定的非 dev 构建图收集 Rust 依赖的许可,写入 licenses/rust 和不含本机路径的哈希清单。SDK 沿其 Git 检出查找共享许可;crate 未携带主许可时,只接受 third_party/rust-notices 中与 crate VCS 修订及 SHA-256 匹配的固定文本。tree-sitter 内嵌的 Unicode 许可一并保留。缺少许可或来源不匹配会终止打包;不在运行期下载,不跨工作树共享生成目录。
57 changes: 57 additions & 0 deletions docs/releases/v1.1.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## 中文

Java Support 1.1.2 为配套 Viva 预览版提供 Java 项目支持。本版本是预发布,插件包不带发布者签名。

### 下载与安装

下载本页对应系统的 ZIP:macOS Apple Silicon(`darwin-arm64`)、macOS Intel(`darwin-x64`)或 Windows x64(`windows-x64`)。在兼容 Viva 客户端的插件设置中选择从文件安装 ZIP。

需要包含 [Lithe #1884](https://github.com/1lck/Lithe-IDEA/pull/1884) 的客户端(修订 `607f00967d70a7017d49805183d0d2e6ffdf1c0d` 或更新版本)及 Core #29。旧版 macOS Lithe 和未包含这些修复的 Viva preview 不受支持。插件使用契约/API/能力版本 0;语言服务器使用随包 Temurin 21,项目运行仍使用用户选择的 JDK。

Maven 项目配合 Maven 0.0.1;Gradle 项目配合 Gradle 1.0.0。构建工具由用户安装或由项目 Wrapper 提供。

### 更新

- 补齐自身 LICENSE/NOTICE 与 Rust 构建依赖的许可和哈希清单,缺失时阻止打包。

- 跑通普通 Maven、Gradle 和普通 Java 目录的补全、编译、运行及断点调试,Java 8 项目保留正确的编译目标。
- Windows 后端静态链接编译器运行库,发布检查拒绝额外的 VC++/MinGW 运行库依赖。
- 修复 Windows 可执行路径、中文/UNC 文件 URI、Lombok 加载及过长 classpath;退出后清理临时 classpath JAR。
- 冷索引暂时无法返回运行动作时,在索引任务完成后有界恢复,不增加后台轮询。
- 已在 macOS 与 Windows 无头验证断点变量、继续/停止、编译错误阻止运行旧 class、暂停时关闭及插件安装目录只读。

### 兼容性与已知限制

尚未完整验收原生窗口、全部日志/取消路径、JUnit/TestNG 行内运行器和 Spring/MyBatis。JSP HTTP/Servlet 调试通过不代表 JSP 源码补全或 JSP 断点;Ant 源码布局不代表执行 Ant target。Java 5/7 源码级别、长 module-path、JavaCC 生成模型、多版本 JAR 和部分旧泛型项目仍有限制,详见 README 与主仓库 #1881–1883。

实测 JDT 内存与补全延迟没有显著改善,本版本不宣称整体性能提升。Windows 的真实工作流在 ARM Windows 上使用 x64 工具验证,其耗时不代表原生 AMD64 性能。

---

## English

Java Support 1.1.2 targets the matching Viva preview. This is a prerelease with unsigned plugin archives.

### Download and installation

Choose the ZIP for macOS Apple Silicon (`darwin-arm64`), macOS Intel (`darwin-x64`), or Windows x64 (`windows-x64`), then install it from a file in the compatible Viva client's plugin settings.

The client must include [Lithe #1884](https://github.com/1lck/Lithe-IDEA/pull/1884), revision `607f00967d70a7017d49805183d0d2e6ffdf1c0d` or newer, and Core #29. Legacy macOS Lithe and earlier Viva previews are unsupported. Contract, API and capability versions are 0. Bundled Temurin 21 runs the language server; project execution uses the selected project JDK.

Use Maven 0.0.1 for Maven projects and Gradle 1.0.0 for Gradle projects. Supply the build tool yourself or through the project's wrapper.

### Changes

- Include project and Rust dependency notices with a hash manifest; missing notices stop packaging.

- Validated completion, compilation, execution and breakpoint debugging for ordinary Maven, Gradle and plain Java projects, preserving Java 8 compilation targets.
- The Windows backend statically links compiler runtimes; release checks reject extra VC++/MinGW runtime DLL dependencies.
- Fixed Windows executable paths, Unicode/UNC file URIs, Lombok loading and long classpaths; temporary classpath JARs are removed after execution.
- Recover run actions after cold-index jobs finish, with bounded retries and no background polling.
- Headless macOS and Windows workflows cover breakpoint locals, continue/stop, refusal to run stale classes after compilation errors, closing paused sessions and read-only installed packages.

### Compatibility and known limitations

Native windows, all logging/cancellation paths, inline JUnit/TestNG and Spring/MyBatis have not been fully accepted. JSP HTTP/Servlet debugging does not cover JSP-source completion or JSP breakpoints; Ant source layouts do not cover Ant targets. Java 5/7 source levels, long module-paths, JavaCC models, multi-release JARs and some older generic code remain limited; see README and Lithe #1881–1883.

Measurements found no significant JDT memory or completion-latency improvement. Windows workflows used x64 tools on ARM Windows, so their timings do not represent native AMD64 performance.
2 changes: 1 addition & 1 deletion io.github.lithe-idea.java-support/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"schemaVersion": 0,
"id": "io.github.lithe-idea.java-support",
"displayName": "Java Support",
"version": "1.1.1",
"version": "1.1.2",
"pluginAPIVersion": 0,
"requires": {"backend": 0, "debugAdapters": 0, "languages": 0, "languageServers": 0, "pluginFunctions": 0, "settings": 0, "projectGenerators": 0, "toolDefinitions": 0},
"requiresTools": ["java"],
Expand Down
3 changes: 3 additions & 0 deletions scripts/assemble-package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,6 @@ if [[ -n "$(find "$OUTPUT_DIR" -type l -print -quit)" ]]; then
print -u2 -- "The Java Support package contains symbolic links: $(find "$OUTPUT_DIR" -type l -print -quit)"
exit 1
fi

# Notices are build inputs, copied alongside the binary from locked sources.
node "$ROOT_DIR/scripts/package/collect-notices.mjs" "$OUTPUT_DIR" >&2
3 changes: 3 additions & 0 deletions scripts/package/build-windows.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { fileURLToPath } from 'node:url';
import { execFileSync } from 'node:child_process';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
import { collectNotices, verifyNotices } from './collect-notices.mjs';

const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
if (process.platform !== 'darwin') throw new Error('Assemble this development package on macOS.');
Expand Down Expand Up @@ -86,6 +87,8 @@ try {
await writeFile(join(packageRoot, 'plugin.json'), `${JSON.stringify(manifest, null, 2)}\n`);
await mkdir(join(packageRoot, 'bin'));
await cp(resolve(backend), join(packageRoot, manifest.backend.launch.path));
collectNotices(packageRoot);
verifyNotices(packageRoot);
const target = join(artifacts, `Lithe-Java-Support-${manifest.version}-windows-x64.zip`);
execFileSync('/usr/bin/ditto', ['-c', '-k', '--keepParent', packageRoot, target], { timeout: 120_000 });
await writeFile(`${target}.sha256`, `${await digest(target)} ${target.split('/').at(-1)}\n`);
Expand Down
121 changes: 121 additions & 0 deletions scripts/package/collect-notices.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/env node
// Copy notices from Cargo's locked build graph. No runtime download or source
// modification: registry/Git inputs stay owned by Cargo, output by the package.
import {execFileSync} from 'node:child_process';
import {createHash} from 'node:crypto';
import {existsSync,readFileSync,readdirSync,lstatSync,mkdirSync,writeFileSync,chmodSync,rmSync} from 'node:fs';
import {dirname,isAbsolute,join,resolve} from 'node:path';
import {fileURLToPath} from 'node:url';

const repository=resolve(dirname(fileURLToPath(import.meta.url)),'../..');
const sha=data=>createHash('sha256').update(data).digest('hex');
const isNotice=name=>/^(LICENSE|COPYING|NOTICE)(?:[._-]|$)/i.test(name);
const isLicense=name=>/^(LICENSE|COPYING)(?:[._-]|$)/i.test(name);
function documents(directory,recursive=false,prefix='') {
return readdirSync(directory,{withFileTypes:true}).flatMap(entry=>{
const path=join(prefix,entry.name);
if(entry.isDirectory() && recursive) return documents(join(directory,entry.name),true,path);
return entry.isFile() && isNotice(entry.name) ? [path] : [];
}).sort();
}
function readDocument(path) {
if(!lstatSync(path).isFile() || lstatSync(path).size>1024*1024) throw Error('Invalid or oversized notice');
return readFileSync(path);
}

export function collectNotices(packageRoot,metadata) {
packageRoot=resolve(packageRoot);
const manifest=JSON.parse(readFileSync(join(packageRoot,'plugin.json'),'utf8'));
if(manifest.id!=='io.github.lithe-idea.java-support') throw Error('A Java Support package is required');
metadata??=JSON.parse(execFileSync('cargo',['metadata','--locked','--format-version','1'],{
cwd:repository,encoding:'utf8',timeout:180000,maxBuffer:16*1024*1024,
}));
const nodes=new Map(metadata.resolve.nodes.map(node=>[node.id,node]));
const backend=metadata.packages.find(pkg=>pkg.name==='lithe-java-backend');
if(!backend) throw Error('Missing backend build graph');
const selected=new Set();
function visit(id) {
if(selected.has(id)) return;
selected.add(id);
const node=nodes.get(id);
if(!node) throw Error('Incomplete Cargo graph');
for(const dep of node.deps) if(dep.dep_kinds.some(kind=>kind.kind!=='dev')) visit(dep.pkg);
}
visit(backend.id);
const fallbacks=JSON.parse(readFileSync(join(repository,'third_party/rust-notices/manifest.json'),'utf8'));
const destination=join(packageRoot,'licenses/rust');
rmSync(destination,{recursive:true,force:true});
mkdirSync(destination,{recursive:true});
for(const name of ['LICENSE','NOTICE']) {
writeFileSync(join(packageRoot,name),readDocument(join(metadata.workspace_root,name)),{mode:0o644});
chmodSync(join(packageRoot,name),0o644);
}
const records=[];
for(const pkg of metadata.packages.filter(pkg=>selected.has(pkg.id)).sort((a,b)=>`${a.name}@${a.version}`<`${b.name}@${b.version}`?-1:1)) {
const key=`${pkg.name}-${pkg.version}`;
if(!/^[a-zA-Z0-9_.+-]+$/.test(key)) throw Error('Invalid Cargo package identity');
const source=dirname(pkg.manifest_path);
let primary=source;
if(metadata.workspace_members.includes(pkg.id)) primary=metadata.workspace_root;
let names=documents(primary);
if(!names.some(isLicense) && pkg.source?.startsWith('git+')) {
// SDKs may inherit a shared license above their crate. Stop at the Git
// checkout boundary rather than accepting a license from the user home.
for(let level=0;level<8;level++) {
if(existsSync(join(primary,'.git')) || dirname(primary)===primary) break;
primary=dirname(primary); names=documents(primary);
if(names.some(isLicense)) break;
}
}
const files=[];
function copy(name,data) {
const target=join(destination,key,name);
mkdirSync(dirname(target),{recursive:true});
writeFileSync(target,data,{mode:0o644}); chmodSync(target,0o644);
files.push({path:`${key}/${name.split('\\').join('/')}`,sha256:sha(data)});
}
if(names.some(isLicense)) {
for(const name of names) copy(name,readDocument(join(primary,name)));
} else {
const fallback=fallbacks.find(item=>item.name===pkg.name && item.version===pkg.version);
if(!fallback) throw Error(`Missing primary license for ${key}`);
const vcs=JSON.parse(readFileSync(join(source,'.cargo_vcs_info.json'),'utf8'));
if(vcs.git.sha1!==fallback.revision) throw Error(`License source revision mismatch for ${key}`);
const data=readDocument(join(repository,'third_party/rust-notices',fallback.file));
if(sha(data)!==fallback.sha256) throw Error(`License hash mismatch for ${key}`);
copy('LICENSE',data);
}
// Retain embedded notices too, such as tree-sitter's Unicode license.
for(const name of metadata.workspace_members.includes(pkg.id)?[]:documents(source,true)) {
if(primary===source && names.includes(name)) continue;
copy(join('source',name),readDocument(join(source,name)));
}
records.push({name:pkg.name,version:pkg.version,license:pkg.license,files});
}
writeFileSync(join(destination,'manifest.json'),JSON.stringify({schemaVersion:1,packages:records},null,2)+'\n');
return records;
}
export function verifyNotices(packageRoot) {
for(const name of ['LICENSE','NOTICE']) {
if(!readDocument(join(packageRoot,name)).equals(readDocument(join(repository,name)))) throw Error(`Missing or modified ${name}`);
}
const root=join(packageRoot,'licenses/rust');
const manifest=JSON.parse(readDocument(join(root,'manifest.json')).toString('utf8'));
if(manifest.schemaVersion!==1 || !manifest.packages?.length) throw Error('Missing Rust build notices');
for(const pkg of manifest.packages) {
if(!pkg.files?.length) throw Error('Missing dependency notice files');
for(const file of pkg.files) {
if(typeof file.path!=='string' || isAbsolute(file.path) || file.path.split(/[\\/]/).includes('..')) throw Error('Unsafe notice path');
if(sha(readDocument(join(root,file.path)))!==file.sha256) throw Error('Modified dependency notice');
}
}
}
if(process.argv[1] && resolve(process.argv[1])===fileURLToPath(import.meta.url)) {
if(process.argv.length===4 && process.argv[2]==='--verify') verifyNotices(process.argv[3]);
else {
if(process.argv.length!==3) throw Error('Usage: collect-notices.mjs [--verify] <package-directory>');
const records=collectNotices(process.argv[2]);
verifyNotices(process.argv[2]);
process.stderr.write(`Collected locked Rust notices for ${records.length} build packages\n`);
}
}
Loading
Loading