feat: jlink打包脚本 - 创建最小JRE发行版(Windows/Linux/macOS)

- packaging/build-dist.ps1: PowerShell跨平台构建脚本
- packaging/build-dist.sh: Bash构建脚本
- BUILD.md: 完整构建安装指南
- 自动jdeps分析 + jlink精简JRE(~49MB)+ fat jar + 启动脚本
- 发行包约120MB,免安装JDK直接运行
- 删除旧run.bat/run.ps1

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
pull/1/head
abel533 1 month ago
parent 2d704cc014
commit f9072534ef
  1. 4
      .gitignore
  2. 269
      BUILD.md
  3. 64
      README.md
  4. 254
      packaging/build-dist.ps1
  5. 180
      packaging/build-dist.sh
  6. 32
      run.bat
  7. 32
      run.ps1

4
.gitignore vendored

@ -2,6 +2,10 @@
target/ target/
*.class *.class
# Distribution
dist/
dist2/
# IDE # IDE
.idea/ .idea/
*.iml *.iml

@ -0,0 +1,269 @@
# Claude Code Java — 构建与安装指南
## 前置要求
| 依赖 | 版本 | 说明 |
|------|------|------|
| **JDK** | 25+ | 必须,推荐 [Oracle JDK 25](https://www.oracle.com/java/technologies/downloads/) 或 [OpenJDK 25](https://jdk.java.net/25/) |
| **Maven** | 3.9+ | 必须,[下载地址](https://maven.apache.org/download.cgi) |
| **API Key** | - | OpenAI / Anthropic / 兼容 API 的密钥 |
> ⚠ JDK 25 是必须的,项目使用了 preview 特性(如 pattern matching、string templates)。
---
## 快速开始(开发模式)
适合开发和调试,通过 Maven 直接运行:
```bash
# 1. 设置 JDK
export JAVA_HOME=/path/to/jdk-25 # Linux/macOS
set JAVA_HOME=C:\Dev\jdk-25.0.2 # Windows
# 2. 设置 API Key
export AI_API_KEY=your-api-key # Linux/macOS
set AI_API_KEY=your-api-key # Windows
# 3. 运行
mvn spring-boot:run
```
> 📌 开发模式下,工作目录(AI 操作的目录)为执行 `mvn` 命令的目录。
---
## 构建发行版(推荐用于日常使用)
发行版使用 **jlink** 创建精简 JRE,打包为独立可执行程序,无需目标机器安装 JDK。
### Windows
```powershell
# 构建
.\packaging\build-dist.ps1 -JavaHome "C:\Dev\jdk-25.0.2"
# 或跳过 Maven 构建(已有 jar 的情况)
.\packaging\build-dist.ps1 -JavaHome "C:\Dev\jdk-25.0.2" -SkipBuild
```
### Linux / macOS
```bash
# 构建
JAVA_HOME=/path/to/jdk-25 ./packaging/build-dist.sh
# 或跳过 Maven 构建
JAVA_HOME=/path/to/jdk-25 ./packaging/build-dist.sh --skip-build
```
### 构建输出
```
dist/
├── bin/
│ ├── claude-code # Unix 启动脚本 (Linux/macOS)
│ └── claude-code.cmd # Windows 启动脚本
├── lib/
│ └── claude-code-java.jar # Spring Boot fat jar (~71 MB)
└── runtime/ # jlink 精简 JRE (~49 MB)
├── bin/
├── conf/
├── lib/
└── release
```
总大小约 **120 MB**(对比完整 JDK ~350 MB)。
---
## 安装到系统 PATH
`dist/bin` 加入系统 PATH 后,可在任意目录直接使用 `claude-code` 命令。
### Windows
**方法 1:CMD 临时生效(当前终端)**
```cmd
set PATH=C:\path\to\claude-code-java\dist\bin;%PATH%
```
**方法 2:PowerShell 临时生效(当前终端)**
```powershell
$env:PATH = "C:\path\to\claude-code-java\dist\bin;$env:PATH"
```
**方法 3:CMD 永久生效(用户级)**
```cmd
setx PATH "%PATH%;C:\path\to\claude-code-java\dist\bin"
```
> 需要重开终端窗口生效。
**方法 4:PowerShell 永久生效**
```powershell
$binPath = "C:\path\to\claude-code-java\dist\bin"
$currentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
if ($currentPath -notmatch [regex]::Escape($binPath)) {
[Environment]::SetEnvironmentVariable("PATH", "$currentPath;$binPath", "User")
Write-Host "Added to PATH. Restart terminal to take effect."
}
```
### Linux
```bash
# 临时生效
export PATH="/path/to/claude-code-java/dist/bin:$PATH"
# 永久生效 — 添加到 ~/.bashrc 或 ~/.zshrc
echo 'export PATH="/path/to/claude-code-java/dist/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
```
### macOS
```bash
# 临时生效
export PATH="/path/to/claude-code-java/dist/bin:$PATH"
# 永久生效 — 添加到 ~/.zshrc (macOS 默认 shell 是 zsh)
echo 'export PATH="/path/to/claude-code-java/dist/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
```
### 验证安装
```bash
# 在任意目录执行
claude-code # Linux/macOS
claude-code.cmd # Windows CMD
claude-code # Windows PowerShell (自动找 .cmd)
```
---
## 在其他目录使用
安装到 PATH 后,`claude-code` 可以在任何目录启动。**AI 的工作目录就是你启动命令时的当前目录**。
```bash
# 示例:在项目目录启动,AI 会自动读取该目录的上下文
cd /path/to/my-project
claude-code
# AI 将:
# - 自动加载 ./CLAUDE.md(如果存在)
# - 读取 .git 信息获取项目上下文
# - 所有文件操作基于当前目录
```
```bash
# 示例:在不同项目之间切换
cd ~/projects/web-app && claude-code # 操作 web-app 项目
cd ~/projects/api-server && claude-code # 操作 api-server 项目
```
### 工作目录说明
| 场景 | 工作目录 | 说明 |
|------|----------|------|
| `cd /my-project && claude-code` | `/my-project` | AI 在此目录下操作文件 |
| 启动后使用工具 | 当前目录 | `bash`, `file_read` 等都基于启动目录 |
| `CLAUDE.md` 加载 | 当前目录 + `~/.claude/` | 项目级 + 全局级自动合并 |
| Git 上下文 | 当前目录的 `.git` | 自动检测分支、状态、最近提交 |
---
## 环境变量
| 变量 | 必须 | 说明 | 默认值 |
|------|------|------|--------|
| `AI_API_KEY` | ✅ | API 密钥 | - |
| `CLAUDE_CODE_PROVIDER` | ❌ | 提供者 (`openai` / `anthropic`) | `openai` |
| `AI_BASE_URL` | ❌ | API 基础 URL | 按提供者不同 |
| `AI_MODEL` | ❌ | 模型名称 | 按提供者不同 |
| `AI_MAX_TOKENS` | ❌ | 最大生成 Token 数 | `8096` |
| `CLAUDE_CODE_VIM` | ❌ | 启用 Vim 编辑模式 | `0` |
| `CLAUDE_CODE_CONTEXT_WINDOW` | ❌ | 上下文窗口大小 | `200000` |
建议将常用变量配置到 shell profile 中:
```bash
# ~/.bashrc 或 ~/.zshrc
export AI_API_KEY="sk-your-key-here"
export CLAUDE_CODE_PROVIDER="openai"
export AI_BASE_URL="https://api.deepseek.com"
export AI_MODEL="deepseek-chat"
```
Windows 可用 `setx` 持久化:
```cmd
setx AI_API_KEY "sk-your-key-here"
setx CLAUDE_CODE_PROVIDER "openai"
```
---
## 跨平台构建注意事项
jlink 创建的 JRE 是 **平台相关** 的。在 Windows 上构建的 `dist/` 只能在 Windows 上运行。
如果需要为多个平台构建:
| 构建平台 | 产出 | 可运行平台 |
|----------|------|------------|
| Windows x64 | `dist/runtime/` (Windows JRE) | Windows x64 |
| Linux x64 | `dist/runtime/` (Linux JRE) | Linux x64 |
| macOS ARM | `dist/runtime/` (macOS JRE) | macOS ARM (M1/M2/M3) |
| macOS x64 | `dist/runtime/` (macOS JRE) | macOS Intel |
> 💡 `lib/claude-code-java.jar` 是跨平台的,只有 `runtime/` 需要针对每个平台构建。
>
> 如果在 CI/CD 中构建,可以在 GitHub Actions 中使用 matrix strategy 分别在 `ubuntu-latest`, `windows-latest`, `macos-latest` 上构建。
---
## 常见问题
### Q: 启动时报 "OpenAI API key must be set"
设置 `AI_API_KEY` 环境变量:
```bash
export AI_API_KEY="your-key"
```
### Q: Windows 终端中文乱码
发行版启动脚本已自动执行 `chcp 65001`。如果仍有问题,手动运行:
```cmd
chcp 65001
```
### Q: 如何使用 Anthropic API 而不是 OpenAI?
```bash
export CLAUDE_CODE_PROVIDER="anthropic"
export AI_API_KEY="sk-ant-your-key"
export AI_MODEL="claude-sonnet-4-20250514"
```
### Q: 如何使用 DeepSeek / Azure OpenAI 等兼容 API?
```bash
export CLAUDE_CODE_PROVIDER="openai"
export AI_BASE_URL="https://api.deepseek.com"
export AI_MODEL="deepseek-chat"
export AI_API_KEY="your-deepseek-key"
```
### Q: dist/ 目录可以复制到其他机器吗?
可以,只要目标机器的操作系统和 CPU 架构与构建机器一致。`dist/` 是完全自包含的,不需要安装 JDK。
### Q: 如何更新?
重新执行构建脚本覆盖 `dist/` 目录即可:
```bash
git pull
./packaging/build-dist.sh # 或 .ps1
```

@ -57,6 +57,8 @@
## 🚀 快速开始 ## 🚀 快速开始
> 📖 完整的构建、安装、跨平台使用说明请参阅 **[BUILD.md](BUILD.md)**
### 前置要求 ### 前置要求
- **JDK 25**(配置 `JAVA_HOME` - **JDK 25**(配置 `JAVA_HOME`
@ -90,19 +92,20 @@ export AI_MODEL="gpt-4o"
### 运行 ### 运行
**Windows PowerShell:** **构建发行版后运行:**
```powershell ```bash
.\run.ps1 # 1. 构建发行包(仅需一次)
``` .\packaging\build-dist.ps1 -JavaHome "C:\Dev\jdk-25.0.2" # Windows
# 或
JAVA_HOME=/path/to/jdk-25 ./packaging/build-dist.sh # Linux/macOS
**Windows CMD:** # 2. 运行
```cmd dist\bin\claude-code.cmd # Windows
run.bat dist/bin/claude-code # Linux/macOS
``` ```
**手动运行:** **开发模式运行:**
```bash ```bash
cd claude-code-copy
mvn spring-boot:run mvn spring-boot:run
``` ```
@ -589,11 +592,50 @@ mvn clean package -DskipTests
java -jar target/claude-code-java-0.1.0-SNAPSHOT.jar java -jar target/claude-code-java-0.1.0-SNAPSHOT.jar
``` ```
### 发行版构建(jlink 最小 JRE)
创建包含精简 JRE 的独立发行包,无需目标机器安装 JDK:
**Windows (PowerShell):**
```powershell
.\packaging\build-dist.ps1 -JavaHome "C:\Dev\jdk-25.0.2"
```
**Linux / macOS (Bash):**
```bash
JAVA_HOME=/path/to/jdk-25 ./packaging/build-dist.sh
```
生成的 `dist/` 目录结构:
```
dist/
├── bin/
│ ├── claude-code # Unix 启动脚本
│ └── claude-code.cmd # Windows 启动脚本
├── lib/
│ └── claude-code-java.jar # Spring Boot fat jar (~71 MB)
└── runtime/ # jlink 精简 JRE (~49 MB)
└── ...
```
使用方式:
```bash
# 将 bin/ 加入 PATH
export PATH="/path/to/dist/bin:$PATH" # Linux/macOS
set PATH=C:\path\to\dist\bin;%PATH% # Windows
# 设置 API Key 后即可使用
export AI_API_KEY=your-key
claude-code
```
> 💡 发行包约 120 MB,比完整 JDK(~350 MB)小得多。每个平台需要独立构建(jlink JRE 是平台相关的)。
### 已知问题 ### 已知问题
- **Windows 编码**:需要 `chcp 65001` 切换到 UTF-8 编码页(启动脚本已自动处理) - **Windows 编码**:需要 `chcp 65001` 切换到 UTF-8 编码页(发行版启动脚本已自动处理)
- **IDE 终端**:IntelliJ IDEA 等 IDE 内置终端为 dumb 模式,Tab 补全和行编辑受限 - **IDE 终端**:IntelliJ IDEA 等 IDE 内置终端为 dumb 模式,Tab 补全和行编辑受限
- **JDK 25 警告**:Maven 的 jansi/guava 会触发 native access 警告(启动脚本已通过 MAVEN_OPTS 抑制) - **JDK 25 警告**:Maven 的 jansi/guava 会触发 native access 警告(启动脚本已通过 JVM 参数抑制)
## 📝 对应关系 ## 📝 对应关系

@ -0,0 +1,254 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
构建 Claude Code Java 发行版jlink 最小 JRE + fat jar
.DESCRIPTION
生成可分发包bin/ 启动脚本 + lib/ fat jar + runtime/ 精简 JRE
用户只需将 bin/ 目录加入 PATH 即可使用 claude-code 命令
.PARAMETER JavaHome
JDK 25 安装路径默认使用 JAVA_HOME 环境变量
.PARAMETER OutputDir
输出目录默认 dist/
.PARAMETER SkipBuild
跳过 Maven 构建使用已存在的 jar
.EXAMPLE
.\packaging\build-dist.ps1
.\packaging\build-dist.ps1 -JavaHome "C:\Dev\jdk-25.0.2"
.\packaging\build-dist.ps1 -SkipBuild
#>
param(
[string]$JavaHome = $env:JAVA_HOME,
[string]$OutputDir = "dist",
[switch]$SkipBuild
)
$ErrorActionPreference = "Stop"
$ProjectRoot = Split-Path -Parent $PSScriptRoot
# ─── 验证环境 ───
$javaExe = if ($IsWindows -or $env:OS -eq "Windows_NT") { "java.exe" } else { "java" }
$jlinkExe = if ($IsWindows -or $env:OS -eq "Windows_NT") { "jlink.exe" } else { "jlink" }
$jdepsExe = if ($IsWindows -or $env:OS -eq "Windows_NT") { "jdeps.exe" } else { "jdeps" }
if (-not $JavaHome) {
Write-Error "JAVA_HOME not set. Use -JavaHome parameter or set JAVA_HOME environment variable."
exit 1
}
$javaBinPath = Join-Path $JavaHome "bin"
if (-not (Test-Path (Join-Path $javaBinPath $javaExe))) {
Write-Error "JDK not found at $JavaHome. Ensure JDK 25 is installed."
exit 1
}
$env:JAVA_HOME = $JavaHome
$env:PATH = "$javaBinPath$([IO.Path]::PathSeparator)$env:PATH"
$javaVersion = & (Join-Path $javaBinPath $javaExe) --version 2>&1 | Select-Object -First 1
Write-Host ""
Write-Host " Claude Code Java - Distribution Builder" -ForegroundColor Cyan
Write-Host " ========================================" -ForegroundColor Cyan
Write-Host " JDK: $javaVersion" -ForegroundColor Gray
Write-Host " Output: $OutputDir" -ForegroundColor Gray
# ─── 检测平台 ───
$isWin = $IsWindows -or ($env:OS -eq "Windows_NT")
$isMac = $IsMacOS
if ($isWin) { $platform = "windows-x64" }
elseif ($isMac) {
$arch = & uname -m 2>&1
$platform = if ($arch -match "arm64") { "macos-aarch64" } else { "macos-x64" }
} else {
$arch = & uname -m 2>&1
$platform = if ($arch -match "aarch64") { "linux-aarch64" } else { "linux-x64" }
}
Write-Host " Platform: $platform" -ForegroundColor Gray
Write-Host ""
# ─── Step 1: Maven 构建 ───
Push-Location $ProjectRoot
try {
if (-not $SkipBuild) {
Write-Host "[1/4] Building fat jar with Maven..." -ForegroundColor Yellow
& mvn package -q -DskipTests 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Error "Maven build failed"
exit 1
}
Write-Host " OK Build succeeded" -ForegroundColor Green
} else {
Write-Host "[1/4] Skipping build (using existing jar)" -ForegroundColor DarkGray
}
# 查找 fat jar
$jarFile = Get-ChildItem "target/*.jar" -ErrorAction SilentlyContinue |
Where-Object { $_.Name -notmatch "original" } |
Select-Object -First 1
if (-not $jarFile) {
Write-Error "No jar found in target/. Run without -SkipBuild."
exit 1
}
$jarSize = [math]::Round($jarFile.Length / 1MB, 1)
Write-Host " Jar: $($jarFile.Name) ($jarSize MB)" -ForegroundColor Gray
# ─── Step 2: jdeps 分析模块 ───
Write-Host "[2/4] Analyzing module dependencies with jdeps..." -ForegroundColor Yellow
$detected = & (Join-Path $javaBinPath $jdepsExe) --print-module-deps --ignore-missing-deps --multi-release 25 $jarFile.FullName 2>&1
if ($LASTEXITCODE -ne 0 -or -not $detected) {
Write-Host " Warning: jdeps failed, using default modules" -ForegroundColor DarkYellow
$detected = "java.base,java.desktop,java.management,java.net.http"
}
Write-Host " Detected: $detected" -ForegroundColor Gray
# Spring Boot 运行时必要模块
$required = @(
"java.naming", # JNDI (Spring)
"java.xml", # XML processing (Spring)
"java.sql", # JDBC
"java.instrument", # Java agent / instrumentation
"java.compiler", # Annotation processing
"java.scripting", # Script engines
"jdk.unsupported", # sun.misc.Unsafe
"jdk.crypto.ec", # HTTPS/TLS ECDH
"jdk.zipfs", # ZIP filesystem provider
"jdk.jfr", # Flight Recorder
"jdk.net" # Extended socket options (RestClient)
)
$allSet = [System.Collections.Generic.HashSet[string]]::new()
foreach ($m in ($detected -split ",")) { [void]$allSet.Add($m.Trim()) }
foreach ($m in $required) { [void]$allSet.Add($m) }
$modules = ($allSet | Sort-Object) -join ","
Write-Host " Final modules: $modules" -ForegroundColor Gray
# ─── Step 3: jlink ───
Write-Host "[3/4] Creating minimal JRE with jlink..." -ForegroundColor Yellow
$distDir = Join-Path $ProjectRoot $OutputDir
$runtimeDir = Join-Path $distDir "runtime"
if (Test-Path $distDir) { Remove-Item $distDir -Recurse -Force }
New-Item -ItemType Directory -Path $distDir -Force | Out-Null
& (Join-Path $javaBinPath $jlinkExe) `
--add-modules $modules `
--output $runtimeDir `
--strip-debug `
--compress zip-6 `
--no-header-files `
--no-man-pages 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Error "jlink failed"
exit 1
}
$runtimeSize = [math]::Round(
(Get-ChildItem -Recurse $runtimeDir | Measure-Object -Property Length -Sum).Sum / 1MB, 1
)
Write-Host " OK Runtime: $runtimeSize MB" -ForegroundColor Green
# ─── Step 4: 组装 ───
Write-Host "[4/4] Assembling distribution..." -ForegroundColor Yellow
# lib/
$libDir = Join-Path $distDir "lib"
New-Item -ItemType Directory -Path $libDir -Force | Out-Null
Copy-Item $jarFile.FullName (Join-Path $libDir "claude-code-java.jar")
# bin/
$binDir = Join-Path $distDir "bin"
New-Item -ItemType Directory -Path $binDir -Force | Out-Null
# ── Windows launcher (claude-code.cmd) ──
$cmdContent = @'
@echo off
setlocal enabledelayedexpansion
chcp 65001 >nul 2>&1
set "SCRIPT_DIR=%~dp0"
set "APP_HOME=%SCRIPT_DIR%.."
set "RUNTIME=%APP_HOME%\runtime"
set "JAR=%APP_HOME%\lib\claude-code-java.jar"
if not exist "%RUNTIME%\bin\java.exe" (
echo Error: Runtime not found at %RUNTIME%
exit /b 1
)
"%RUNTIME%\bin\java.exe" ^
--enable-preview ^
--enable-native-access=ALL-UNNAMED ^
--sun-misc-unsafe-memory-access=allow ^
-Xmx512m ^
-Djava.net.preferIPv4Stack=true ^
-Dstdout.encoding=UTF-8 ^
-Dstderr.encoding=UTF-8 ^
-Dfile.encoding=UTF-8 ^
-jar "%JAR%" ^
%*
'@
Set-Content -Path (Join-Path $binDir "claude-code.cmd") -Value $cmdContent -Encoding ASCII
# ── Unix launcher (claude-code) ──
$shContent = @'
#!/bin/sh
# Claude Code Java launcher
SCRIPT="$0"
while [ -L "$SCRIPT" ]; do
DIR="$(cd "$(dirname "$SCRIPT")" && pwd)"
SCRIPT="$(readlink "$SCRIPT")"
[ "${SCRIPT#/}" = "$SCRIPT" ] && SCRIPT="$DIR/$SCRIPT"
done
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT")" && pwd)"
APP_HOME="$(cd "$SCRIPT_DIR/.." && pwd)"
exec "$APP_HOME/runtime/bin/java" \
--enable-preview \
--enable-native-access=ALL-UNNAMED \
--sun-misc-unsafe-memory-access=allow \
-Xmx512m \
-Djava.net.preferIPv4Stack=true \
-Dstdout.encoding=UTF-8 \
-Dstderr.encoding=UTF-8 \
-Dfile.encoding=UTF-8 \
-jar "$APP_HOME/lib/claude-code-java.jar" \
"$@"
'@
$shContent = $shContent -replace "`r`n", "`n"
[IO.File]::WriteAllText(
(Join-Path $binDir "claude-code"),
$shContent,
[Text.UTF8Encoding]::new($false)
)
# ── 总计 ──
$totalSize = [math]::Round(
(Get-ChildItem -Recurse $distDir | Measure-Object -Property Length -Sum).Sum / 1MB, 1
)
Write-Host ""
Write-Host " Distribution built successfully!" -ForegroundColor Green
Write-Host " ================================" -ForegroundColor Green
Write-Host " Location : $distDir" -ForegroundColor White
Write-Host " Size : $totalSize MB (Runtime $runtimeSize MB + Jar $jarSize MB)" -ForegroundColor White
Write-Host " Platform : $platform" -ForegroundColor White
Write-Host ""
Write-Host " Quick start:" -ForegroundColor Cyan
if ($isWin) {
Write-Host " set AI_API_KEY=your-key" -ForegroundColor Gray
Write-Host " $binDir\claude-code.cmd" -ForegroundColor Gray
} else {
Write-Host " chmod +x $binDir/claude-code" -ForegroundColor Gray
Write-Host " export AI_API_KEY=your-key" -ForegroundColor Gray
Write-Host " $binDir/claude-code" -ForegroundColor Gray
}
Write-Host ""
Write-Host " Or add bin/ to PATH for global access:" -ForegroundColor Cyan
if ($isWin) {
Write-Host " setx PATH `"%PATH%;$(Resolve-Path $binDir)`"" -ForegroundColor Gray
} else {
Write-Host " export PATH=`"${binDir}:`$PATH`"" -ForegroundColor Gray
}
Write-Host ""
} finally {
Pop-Location
}

@ -0,0 +1,180 @@
#!/usr/bin/env bash
#
# 构建 Claude Code Java 发行版(jlink 最小 JRE + fat jar)
#
# 用法:
# ./packaging/build-dist.sh # 使用 JAVA_HOME
# JAVA_HOME=/path/to/jdk25 ./packaging/build-dist.sh # 指定 JDK
# ./packaging/build-dist.sh --skip-build # 跳过 Maven 构建
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
OUTPUT_DIR="${OUTPUT_DIR:-dist}"
SKIP_BUILD=false
for arg in "$@"; do
case "$arg" in
--skip-build) SKIP_BUILD=true ;;
--help|-h)
echo "Usage: $0 [--skip-build]"
echo " Environment: JAVA_HOME (required), OUTPUT_DIR (default: dist)"
exit 0 ;;
esac
done
# ─── 验证环境 ───
if [ -z "${JAVA_HOME:-}" ] || [ ! -x "$JAVA_HOME/bin/java" ]; then
echo "Error: JAVA_HOME not set or JDK not found."
echo " export JAVA_HOME=/path/to/jdk-25"
exit 1
fi
export PATH="$JAVA_HOME/bin:$PATH"
JAVA_VERSION=$("$JAVA_HOME/bin/java" --version 2>&1 | head -1)
echo ""
echo " Claude Code Java - Distribution Builder"
echo " ========================================"
echo " JDK: $JAVA_VERSION"
echo " Output: $OUTPUT_DIR"
# 检测平台
OS="$(uname -s)"
ARCH="$(uname -m)"
case "$OS" in
Darwin)
PLATFORM="macos-$([ "$ARCH" = "arm64" ] && echo "aarch64" || echo "x64")" ;;
Linux)
PLATFORM="linux-$([ "$ARCH" = "aarch64" ] && echo "aarch64" || echo "x64")" ;;
*)
PLATFORM="unknown-$OS-$ARCH" ;;
esac
echo " Platform: $PLATFORM"
echo ""
cd "$PROJECT_ROOT"
# ─── Step 1: Maven 构建 ───
if [ "$SKIP_BUILD" = false ]; then
echo "[1/4] Building fat jar with Maven..."
mvn package -q -DskipTests
echo " OK Build succeeded"
else
echo "[1/4] Skipping build (using existing jar)"
fi
JAR_FILE=$(find target -maxdepth 1 -name "*.jar" ! -name "*original*" | head -1)
if [ -z "$JAR_FILE" ]; then
echo "Error: No jar found in target/. Run without --skip-build."
exit 1
fi
JAR_SIZE=$(du -m "$JAR_FILE" | cut -f1)
echo " Jar: $(basename "$JAR_FILE") (${JAR_SIZE} MB)"
# ─── Step 2: jdeps 分析 ───
echo "[2/4] Analyzing module dependencies with jdeps..."
DETECTED=$("$JAVA_HOME/bin/jdeps" --print-module-deps --ignore-missing-deps --multi-release 25 "$JAR_FILE" 2>/dev/null || echo "")
if [ -z "$DETECTED" ]; then
echo " Warning: jdeps failed, using defaults"
DETECTED="java.base,java.desktop,java.management,java.net.http"
fi
echo " Detected: $DETECTED"
# 追加 Spring Boot 运行时模块
EXTRA="java.naming,java.xml,java.sql,java.instrument,java.compiler,java.scripting,jdk.unsupported,jdk.crypto.ec,jdk.zipfs,jdk.jfr,jdk.net"
ALL_MODULES=$(echo "$DETECTED,$EXTRA" | tr ',' '\n' | sort -u | tr '\n' ',' | sed 's/,$//')
echo " Final: $ALL_MODULES"
# ─── Step 3: jlink ───
echo "[3/4] Creating minimal JRE with jlink..."
DIST_DIR="$PROJECT_ROOT/$OUTPUT_DIR"
RUNTIME_DIR="$DIST_DIR/runtime"
rm -rf "$DIST_DIR"
mkdir -p "$DIST_DIR"
"$JAVA_HOME/bin/jlink" \
--add-modules "$ALL_MODULES" \
--output "$RUNTIME_DIR" \
--strip-debug \
--compress zip-6 \
--no-header-files \
--no-man-pages
RUNTIME_SIZE=$(du -sm "$RUNTIME_DIR" | cut -f1)
echo " OK Runtime: ${RUNTIME_SIZE} MB"
# ─── Step 4: 组装 ───
echo "[4/4] Assembling distribution..."
# lib/
mkdir -p "$DIST_DIR/lib"
cp "$JAR_FILE" "$DIST_DIR/lib/claude-code-java.jar"
# bin/
mkdir -p "$DIST_DIR/bin"
# Unix launcher
cat > "$DIST_DIR/bin/claude-code" << 'LAUNCHER'
#!/bin/sh
SCRIPT="$0"
while [ -L "$SCRIPT" ]; do
DIR="$(cd "$(dirname "$SCRIPT")" && pwd)"
SCRIPT="$(readlink "$SCRIPT")"
[ "${SCRIPT#/}" = "$SCRIPT" ] && SCRIPT="$DIR/$SCRIPT"
done
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT")" && pwd)"
APP_HOME="$(cd "$SCRIPT_DIR/.." && pwd)"
exec "$APP_HOME/runtime/bin/java" \
--enable-preview \
--enable-native-access=ALL-UNNAMED \
--sun-misc-unsafe-memory-access=allow \
-Xmx512m \
-Djava.net.preferIPv4Stack=true \
-Dstdout.encoding=UTF-8 \
-Dstderr.encoding=UTF-8 \
-Dfile.encoding=UTF-8 \
-jar "$APP_HOME/lib/claude-code-java.jar" \
"$@"
LAUNCHER
chmod +x "$DIST_DIR/bin/claude-code"
# Windows launcher
cat > "$DIST_DIR/bin/claude-code.cmd" << 'LAUNCHER'
@echo off
setlocal enabledelayedexpansion
chcp 65001 >nul 2>&1
set "SCRIPT_DIR=%~dp0"
set "APP_HOME=%SCRIPT_DIR%.."
"%APP_HOME%\runtime\bin\java.exe" ^
--enable-preview ^
--enable-native-access=ALL-UNNAMED ^
--sun-misc-unsafe-memory-access=allow ^
-Xmx512m ^
-Djava.net.preferIPv4Stack=true ^
-Dstdout.encoding=UTF-8 ^
-Dstderr.encoding=UTF-8 ^
-Dfile.encoding=UTF-8 ^
-jar "%APP_HOME%\lib\claude-code-java.jar" ^
%*
LAUNCHER
# 统计
TOTAL_SIZE=$(du -sm "$DIST_DIR" | cut -f1)
echo ""
echo " Distribution built successfully!"
echo " ================================"
echo " Location : $DIST_DIR"
echo " Size : ${TOTAL_SIZE} MB (Runtime ${RUNTIME_SIZE} MB + Jar ${JAR_SIZE} MB)"
echo " Platform : $PLATFORM"
echo ""
echo " Quick start:"
echo " export AI_API_KEY=your-key"
echo " $DIST_DIR/bin/claude-code"
echo ""
echo " Or add to PATH:"
echo " export PATH=\"$DIST_DIR/bin:\$PATH\""
echo ""

@ -1,32 +0,0 @@
@echo off
REM ============================================
REM Claude Code (Java) 启动脚本
REM 请在 Windows Terminal / PowerShell / cmd 中运行
REM ============================================
REM === JDK 25 配置 ===
set JAVA_HOME=D:\Dev\jdk-25
set PATH=%JAVA_HOME%\bin;%PATH%
REM === 抑制 Maven JVM 的 JDK25 兼容性警告 ===
set MAVEN_OPTS=--enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow
REM === AI API 配置(按需修改) ===
REM 选择 API 提供者:openai(默认)或 anthropic
REM set CLAUDE_CODE_PROVIDER=openai
REM set CLAUDE_CODE_PROVIDER=anthropic
REM 统一环境变量(两种 Provider 通用)
REM set AI_API_KEY=your-api-key-here
REM set AI_BASE_URL=https://api.openai.com
REM set AI_MODEL=gpt-4o
REM
REM OpenAI 默认: AI_BASE_URL=https://api.openai.com AI_MODEL=gpt-4o
REM Anthropic 默认: AI_BASE_URL=https://api.anthropic.com AI_MODEL=claude-sonnet-4-20250514
REM === 设置控制台 UTF-8 编码(支持 emoji 等字符) ===
chcp 65001 >nul 2>&1
REM === 启动应用 ===
cd /d %~dp0
mvn spring-boot:run -q

@ -1,32 +0,0 @@
# ============================================
# Claude Code (Java) 启动脚本 - PowerShell 版
# 请在 Windows Terminal / PowerShell 中运行
# ============================================
# === JDK 25 配置 ===
$env:JAVA_HOME = "D:\Dev\jdk-25"
$env:Path = "D:\Dev\jdk-25\bin;$env:Path"
# === 抑制 Maven JVM 的 JDK25 兼容性警告 ===
$env:MAVEN_OPTS = "--enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow"
# === AI API 配置(按需修改) ===
# 选择 API 提供者:openai(默认)或 anthropic
# $env:CLAUDE_CODE_PROVIDER = "openai" # 使用 OpenAI 兼容 API(支持代理)
# $env:CLAUDE_CODE_PROVIDER = "anthropic" # 使用 Anthropic 原生 API
# 统一环境变量(两种 Provider 通用)
# $env:AI_API_KEY = "your-api-key-here" # API 密钥(必须)
# $env:AI_BASE_URL = "https://api.openai.com" # API 基础 URL(按 Provider 不同默认值不同)
# $env:AI_MODEL = "gpt-4o" # 模型名称(按 Provider 不同默认值不同)
#
# OpenAI 默认: AI_BASE_URL=https://api.openai.com AI_MODEL=gpt-4o
# Anthropic 默认: AI_BASE_URL=https://api.anthropic.com AI_MODEL=claude-sonnet-4-20250514
# === 设置控制台 UTF-8 编码(支持 emoji 等字符) ===
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
# === 启动应用 ===
Set-Location $PSScriptRoot
mvn spring-boot:run -q
Loading…
Cancel
Save