You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
claude-code/packaging/build-dist.ps1

254 lines
9.0 KiB

#!/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
}