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
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,48 @@ jobs:
path: dist/
retention-days: 7
if-no-files-found: error

linux:
name: Build and run Linux ${{ matrix.arch }} package
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
arch: x64
- os: ubuntu-24.04-arm
arch: arm64
runs-on: ${{ matrix.os }}
timeout-minutes: 40
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install packaging tools
run: sudo apt-get update && sudo apt-get install -y zsh zip unzip
- name: Restore checksum-verified downloads
uses: actions/cache@v4
with:
path: |
.artifacts/jdtls-downloads
.artifacts/jdk-downloads
key: linux-${{ matrix.arch }}-${{ hashFiles('third_party/*/manifest.json') }}
- name: Test the native backend
run: cargo test --workspace --locked
- name: Build and verify the archive
run: node scripts/package/build-linux.mjs "${{ matrix.arch }}"
- name: Start the packaged backend and language server
run: node scripts/package/smoke-linux.mjs dist/*-linux-${{ matrix.arch }}.zip
- uses: actions/upload-artifact@v4
with:
name: java-linux-${{ matrix.arch }}
path: |
dist/*-linux-*.zip
dist/*-linux-*.zip.sha256
if-no-files-found: error
retention-days: 7
58 changes: 54 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Release

# Tags must match plugin.json. Build unsigned macOS arm64/x64 and Windows
# x64 packages from the same source revision, then upload a draft prerelease.
# Tags must match plugin.json. Build macOS arm64/x64, Windows x64 and Linux
# ARM64/x64 packages from the same revision, then upload a draft prerelease.
# The maintainer verifies the downloaded assets before publishing the draft.
# Contract 0 currently requires the matching Viva preview, not stable Lithe.
on:
Expand Down Expand Up @@ -69,7 +69,7 @@ jobs:

release:
name: Publish ${{ github.ref_name }}
needs: [test, windows]
needs: [test, windows, linux]
runs-on: macos-26
timeout-minutes: 60
permissions:
Expand Down Expand Up @@ -118,6 +118,11 @@ jobs:
archive=$(node scripts/package/build-windows.mjs .artifacts/windows-backend/lithe-java-backend.exe)
mkdir -p dist
cp "$archive" "$archive.sha256" dist/
- uses: actions/download-artifact@v4
with:
pattern: java-linux-*
merge-multiple: true
path: dist
- name: Archive and publish
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -137,8 +142,53 @@ jobs:
echo '### SHA-256'
echo '```'
sed 's# dist/# #' "$checksums"
cat dist/*windows-x64.zip.sha256
cat dist/*windows-x64.zip.sha256 dist/*linux-*.zip.sha256
echo '```'
} >> "$RUNNER_TEMP/notes.md"
gh release create "$GITHUB_REF_NAME" dist/*.zip dist/*.zip.sha256 --verify-tag --draft --prerelease \
--title "Java Support $GITHUB_REF_NAME" --notes-file "$RUNNER_TEMP/notes.md"

linux:
name: Build and run Linux ${{ matrix.arch }} package
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
arch: x64
- os: ubuntu-24.04-arm
arch: arm64
runs-on: ${{ matrix.os }}
timeout-minutes: 40
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install packaging tools
run: sudo apt-get update && sudo apt-get install -y zsh zip unzip
- name: Restore checksum-verified downloads
uses: actions/cache@v4
with:
path: |
.artifacts/jdtls-downloads
.artifacts/jdk-downloads
key: linux-${{ matrix.arch }}-${{ hashFiles('third_party/*/manifest.json') }}
- name: Test the native backend
run: cargo test --workspace --locked
- name: Build and verify the archive
run: node scripts/package/build-linux.mjs "${{ matrix.arch }}"
- name: Start the packaged backend and language server
run: node scripts/package/smoke-linux.mjs dist/*-linux-${{ matrix.arch }}.zip
- uses: actions/upload-artifact@v4
with:
name: java-linux-${{ matrix.arch }}
path: |
dist/*-linux-*.zip
dist/*-linux-*.zip.sha256
if-no-files-found: error
retention-days: 7
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,12 +245,12 @@ fixtures/ # 与主仓库共享的契约样例(JSON)
third_party/ # JDT LS 与 JDK 的来源地址和 SHA-256
sdk.lock.json # 后端链接的 Rust SDK:标签、提交、crate 归档的 SHA-256
scripts/ # 组装、校验、归档插件包,检查 SDK 锁
.github/workflows/ci.yml # PR:格式、lint、测试、SDK 锁,以及 macOS 上的打包任务(构建并归档两个架构)
.github/workflows/ci.yml # PR:格式、lint、测试、SDK 锁,以及 macOS 双架构打包和 Linux 双架构原生验证
.github/workflows/release.yml # 推送 v<版本> 标签:测试后打包并发布 GitHub Release(预发布)
MIGRATION.md # 主仓库路径到本仓库的映射、待宿主接口清单、主仓库删除清单
```

Rust 部分需要 Rust 工具链和 Cargo;打包脚本需要 macOS、Node.js 22、`zsh`、`curl`、`ditto`,不需要 Xcode 或 Swift 工具链。
Rust 部分需要 Rust 工具链和 Cargo;macOS 打包脚本需要 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 @@ -291,7 +291,7 @@ node scripts/package/build-windows.mjs /path/to/lithe-java-backend.exe
## 发布新版本

构建和发布由现有工作流执行,正式发布前按 Lithe 的版本规则决定版本并核验兼容宿主。
当前依赖 Viva preview,只能作为预发布。推送匹配版本的标签后,工作流为 macOS arm64/x64 与 Windows x64 生成 ZIP 和 SHA-256,并创建草稿预发布;维护者下载核对后再公开草稿。发布说明必须位于 `docs/releases/v<版本>.md`,缺失则终止发布。
当前依赖 Viva preview,只能作为预发布。推送匹配版本的标签后,工作流为 macOS arm64/x64、Windows x64 与 Linux ARM64/x64 生成 ZIP 和 SHA-256,并创建草稿预发布;维护者下载核对后再公开草稿。发布说明必须位于 `docs/releases/v<版本>.md`,缺失则终止发布。
不得覆盖已发布 ZIP,也不得将本地隔离试验的 JDT/JDK 资源当作生产升级:本轮生产仍固定 JDT LS 1.61 和 Temurin 21。

## 许可
Expand All @@ -301,3 +301,7 @@ node scripts/package/build-windows.mjs /path/to/lithe-java-backend.exe
### 随包许可

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

## Linux ARM64 / x64

Linux 构建、资源归属与原生验证入口见 [Linux 构建与验证](docs/linux-packaging.md)。
10 changes: 10 additions & 0 deletions docs/feature-matrix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# 功能验证矩阵

## Linux 安装包

| 能力 | Linux ARM64 | Linux x64 | 验证入口 |
| --- | --- | --- | --- |
| 构建与安装包 | 已实现;[原生验证结果](https://github.com/Lithe-IDEA/lithe-plugin-java/pull/15/checks) | 已实现;[原生验证结果](https://github.com/Lithe-IDEA/lithe-plugin-java/pull/15/checks) | `.github/workflows/ci.yml`、[资源说明](linux-packaging.md) |
| 真实工具/语言服务器流程 | [原生验证结果](https://github.com/Lithe-IDEA/lithe-plugin-java/pull/15/checks) | [原生验证结果](https://github.com/Lithe-IDEA/lithe-plugin-java/pull/15/checks) | `scripts/package/` 和现有集成测试 |

跨架构本地包检查通过不等于原生工作流验收。既有平台验证记录保持不变。
13 changes: 13 additions & 0 deletions docs/linux-packaging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Linux 构建与验证

`node scripts/package/build-linux.mjs arm64|x64` 在对应 Linux runner 上编译 Rust 后端,校验其 ELF 架构,再从固定 SHA-256 的 Temurin JDK、JDT LS、Lombok、Java Debug 和 Java Test 输入组装包。ARM64 使用 `config_linux_arm`,x64 使用 `config_linux`。JDK 上游符号链接仅在构建时展开,插件 ZIP 不含链接。完整许可证与反编译补丁校验沿用已有流程。归档解压后再次检查清单、JDK/后端/VM 架构、执行权限和配置。

`node scripts/package/smoke-linux.mjs <zip>` 在原生 runner 上运行解压后的后端握手与 `java.resolveLaunch`,启动包内 Java 和 JDT LS,执行 initialize/shutdown,并对比整个安装目录的文件内容与权限。状态目录和 Equinox 配置副本在临时用户目录;结束时清理进程组和目录。

Linux 产物面向 GNU/glibc 环境,原生 CI 使用 Ubuntu 24.04;本包不面向 musl/Alpine。

## 资源与验证边界

所有下载、解压、编译与归档都属于本插件仓库的构建阶段。`dist/` 为输出包;`.artifacts/`、`.cache/`、`target/` 或工具自身缓存为本地构建状态,不跨工作树共享可变状态。校验过的下载以固定来源与摘要识别,提取结果不作为可信下载源。运行时只读取已安装插件,缓存、日志、索引和工作区状态归用户可写目录。Linux 不使用 macOS 代码签名或 Sparkle;本次不改变 macOS 包签名与更新基线。

原生 ARM64 和 x64 验证由 `.github/workflows/ci.yml` 执行;本机的交叉编译与归档检查不能替代原生运行结果。只有目标平台 CI 成功后才能宣称该平台通过。发布新版本后,使用实际 Release 附件的 SHA-256 更新 Store,保留所有旧版本与既有平台。
13 changes: 13 additions & 0 deletions docs/releases/v1.3.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Java Support 1.3.0

## 中文

- 新增 Linux ARM64 和 x64 安装包,兼容现有插件契约 v0 与 Lithe 桌面预览版。
- 保留已支持平台;安装包在构建时验证,运行时状态保存在用户目录。

---

## English

- Add Linux ARM64 and x64 packages for the existing plugin contract v0 and Lithe desktop preview.
- Keep existing platforms; verify release archives at build time and keep runtime state in user directories.
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.2.0",
"version": "1.3.0",
"pluginAPIVersion": 0,
"requires": {"backend": 0, "debugAdapters": 0, "languages": 0, "languageServers": 0, "pluginFunctions": 0, "settings": 0, "projectGenerators": 0, "toolDefinitions": 0},
"requiresTools": ["java"],
Expand Down
57 changes: 57 additions & 0 deletions scripts/package/build-linux.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env node
// Linux packages use fixed, verified JDK/JDT inputs and a native backend from
// this source checkout. All writes are build outputs, never installed files.
import {execFileSync} from 'node:child_process';
import {chmodSync,cpSync,mkdirSync,readFileSync,rmSync,mkdtempSync,readdirSync,lstatSync,writeFileSync} from 'node:fs';
import {join,resolve} from 'node:path';
import {architectures,root,prepareJDK,digest,verifyELF,verifyBinary} from './linux-runtime.mjs';
import {collectNotices,verifyNotices} from './collect-notices.mjs';
import {patch,verifyPatch} from './patch-jdt-decompiler.mjs';
const arch=process.argv[2];const target=architectures[arch];
if(!target)throw Error('Usage: node scripts/package/build-linux.mjs arm64|x64 [native-backend]');
const run=(exe,args,options={})=>execFileSync(exe,args,{cwd:root,timeout:600000,stdio:['ignore','pipe','inherit'],...options});
let backend=process.argv[3];
if(!backend){
if(process.platform!=='linux' || process.arch!==(arch==='x64'?'x64':'arm64'))throw Error('A native Linux runner or a matching backend binary is required');
run('cargo',['build','--release','--locked','-p','lithe-java-backend']);
backend=join(root,'target/release/lithe-java-backend');
}
backend=resolve(backend);verifyELF(readFileSync(backend),arch);
const runtime=await prepareJDK(arch);
const jdt=run(join(root,'scripts/prepare-jdtls.sh'),[],{encoding:'utf8',env:{...process.env,LITHE_ARCH:target.runtime}}).trim();
const id='io.github.lithe-idea.java-support';
const manifest=JSON.parse(readFileSync(join(root,id,'plugin.json')));
const staging=mkdtempSync(join(root,'.artifacts',`linux-${arch}-`));
const pkg=join(staging,id);
function verify(directory){
const actual=JSON.parse(readFileSync(join(directory,'plugin.json')));
if(JSON.stringify(actual)!==JSON.stringify(manifest))throw Error('Packaged manifest changed');
for(const name of ['LICENSE','NOTICE'])if(!readFileSync(join(directory,name)).equals(readFileSync(join(root,name))))throw Error(`Missing or modified ${name}`);
for(const path of [manifest.backend.launch.path,`runtime/${target.runtime}/bin/java`,`runtime/${target.runtime}/lib/server/libjvm.so`]){
verifyBinary(join(directory,path),arch,{executable:!path.endsWith('/libjvm.so')});
}
if(!readFileSync(join(directory,'language-server/configuration',target.runtime,'config.ini')).equals(readFileSync(join(jdt,target.configuration,'config.ini'))))throw Error('Wrong Equinox platform configuration');
const walk=path=>{for(const name of readdirSync(path)){const file=join(path,name);const s=lstatSync(file);if(s.isSymbolicLink())throw Error(`Package link: ${file}`);if(s.isDirectory())walk(file);}};walk(directory);
verifyNotices(directory);verifyPatch(directory);
}
try{
mkdirSync(pkg);
cpSync(join(root,id,'plugin.json'),join(pkg,'plugin.json'));
for(const name of ['icons','templates'])cpSync(join(root,id,name),join(pkg,name),{recursive:true});
mkdirSync(join(pkg,'runtime'));cpSync(runtime,join(pkg,'runtime',target.runtime),{recursive:true});
const language=join(pkg,'language-server');mkdirSync(language);
for(const name of ['plugins','features','lombok','java-debug','java-test','manifest.json','LICENSE-EPL-2.0.txt'])cpSync(join(jdt,name),join(language,name),{recursive:true});
mkdirSync(join(language,'configuration',target.runtime),{recursive:true});
cpSync(join(jdt,target.configuration,'config.ini'),join(language,'configuration',target.runtime,'config.ini'));
mkdirSync(join(pkg,'bin'));cpSync(backend,join(pkg,manifest.backend.launch.path));chmodSync(join(pkg,manifest.backend.launch.path),0o755);
collectNotices(pkg);patch(pkg);verify(pkg);
const dist=join(root,'dist');mkdirSync(dist,{recursive:true});
const asset=join(dist,`Lithe-Java-Support-${manifest.version}-linux-${arch}.zip`);rmSync(asset,{force:true});
run('zip',['-qrXy',asset,id],{cwd:staging,timeout:120000});
const check=join(staging,'extracted');mkdirSync(check);
run('unzip',['-q',asset,'-d',check],{timeout:120000});
if(readdirSync(check).join()!==id)throw Error('Archive must contain only the plugin directory');
verify(join(check,id));
writeFileSync(`${asset}.sha256`,`${await digest(asset)} ${asset.split('/').at(-1)}\n`);
console.log(asset);
}finally{rmSync(staging,{recursive:true,force:true});}
6 changes: 6 additions & 0 deletions scripts/package/json-value.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Portable replacement for build-time plutil extraction on Linux.
import {readFileSync} from 'node:fs';
let value=JSON.parse(readFileSync(process.argv[2],'utf8'));
for(const part of process.argv[3].split('.'))value=value?.[part];
if(value===undefined || value===null || typeof value==='object')process.exit(1);
process.stdout.write(String(value)+'\n');
Loading
Loading