Skip to content

Commit 099d800

Browse files
engleflyclaude
andcommitted
[dev-tool] add fast-compile-fe.sh for incremental FE compilation
Add `tools/fast-compile-fe.sh`, a developer utility that incrementally compiles only changed Java source files and patches them directly into `doris-fe.jar`, avoiding a full Maven rebuild during development. Key features: - Auto-detects stale `.java` files by comparing mtime with `.class` files - Supports explicit file arguments (by path or filename search under src/) - Caches the classpath derived from `target/lib` to skip repeated mvn calls - Updates both `target/doris-fe.jar` and `output/fe/lib/doris-fe.jar` - Handles inner classes and anonymous classes (e.g. `Foo$Bar.class`) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9770ecf commit 099d800

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed

tools/fast-compile-fe.sh

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
#!/usr/bin/env bash
2+
# fast-compile-fe.sh — 增量编译 FE 并更新 doris-fe.jar
3+
#
4+
# 用法:
5+
# ./fast-compile-fe.sh # 自动检测改动文件并编译
6+
# ./fast-compile-fe.sh Foo.java Bar.java # 指定编译某些文件
7+
#
8+
# 依赖: javac, jar(JDK 8+),mvn(首次获取 classpath 时需要)
9+
10+
set -euo pipefail
11+
12+
DORIS_HOME="$(cd "$(dirname "$0")/.." && pwd)"
13+
FE_CORE="$DORIS_HOME/fe/fe-core"
14+
SRC_ROOT="$FE_CORE/src/main/java"
15+
TARGET_CLASSES="$FE_CORE/target/classes"
16+
TARGET_LIB="$FE_CORE/target/lib"
17+
OUTPUT_JAR="$DORIS_HOME/output/fe/lib/doris-fe.jar"
18+
TARGET_JAR="$FE_CORE/target/doris-fe.jar"
19+
CP_CACHE="$FE_CORE/target/fast-compile-cp.txt"
20+
21+
# 生成的源码目录(protobuf/thrift/annotation processor 生成的 java 文件)
22+
GEN_SOURCES=(
23+
"$FE_CORE/target/generated-sources/doris"
24+
"$FE_CORE/target/generated-sources/org"
25+
"$FE_CORE/target/generated-sources/java"
26+
"$FE_CORE/target/generated-sources/annotations"
27+
"$FE_CORE/target/generated-sources/antlr4"
28+
)
29+
30+
# ─── 颜色输出 ────────────────────────────────────────────────────────────────
31+
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
32+
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
33+
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
34+
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
35+
36+
# ─── 检查环境 ─────────────────────────────────────────────────────────────────
37+
check_env() {
38+
if [[ ! -d "$TARGET_CLASSES" ]]; then
39+
error "target/classes 不存在,请先完整编译一次: cd $DORIS_HOME && mvn package -pl fe/fe-core -DskipTests -T4"
40+
exit 1
41+
fi
42+
if [[ ! -f "$OUTPUT_JAR" && ! -f "$TARGET_JAR" ]]; then
43+
error "doris-fe.jar 不存在,请先完整编译一次"
44+
exit 1
45+
fi
46+
}
47+
48+
# ─── 获取 classpath(带缓存)─────────────────────────────────────────────────
49+
get_classpath() {
50+
# 如果 pom.xml 比缓存新,则重新生成 classpath
51+
if [[ ! -f "$CP_CACHE" || ! -s "$CP_CACHE" || "$FE_CORE/pom.xml" -nt "$CP_CACHE" ]]; then
52+
info "生成 classpath 缓存..."
53+
# 直接使用 target/lib(本次完整构建产生的依赖,比 .m2 仓库更可靠)
54+
find "$TARGET_LIB" -name "*.jar" | tr '\n' ':' | sed 's/:$//' > "$CP_CACHE"
55+
info "classpath 缓存已保存到 $CP_CACHE"
56+
fi
57+
58+
# classpath = 依赖 jars + target/classes(项目内部依赖)
59+
echo "$(cat "$CP_CACHE"):$TARGET_CLASSES"
60+
}
61+
62+
# ─── 找出需要编译的 java 文件 ────────────────────────────────────────────────
63+
find_stale_java_files() {
64+
local stale_files=()
65+
66+
while IFS= read -r java_file; do
67+
# java_file: /path/to/src/main/java/org/apache/doris/Foo.java
68+
# 转换为 class 文件路径
69+
local rel_path="${java_file#$SRC_ROOT/}" # org/apache/doris/Foo.java
70+
local class_path="$TARGET_CLASSES/${rel_path%.java}.class"
71+
72+
if [[ ! -f "$class_path" ]]; then
73+
# class 文件不存在,肯定需要编译
74+
stale_files+=("$java_file")
75+
elif [[ "$java_file" -nt "$class_path" ]]; then
76+
# java 文件比主 class 文件新
77+
stale_files+=("$java_file")
78+
fi
79+
done < <(find "$SRC_ROOT" -name "*.java")
80+
81+
printf '%s\n' "${stale_files[@]}"
82+
}
83+
84+
# ─── 编译 java 文件 ───────────────────────────────────────────────────────────
85+
compile_files() {
86+
local classpath="$1"
87+
shift
88+
local java_files=("$@")
89+
90+
# 构建源码路径(包含生成的源码目录)
91+
local source_path="$SRC_ROOT"
92+
for gen_src in "${GEN_SOURCES[@]}"; do
93+
[[ -d "$gen_src" ]] && source_path="$source_path:$gen_src"
94+
done
95+
96+
info "编译 ${#java_files[@]} 个文件..."
97+
for f in "${java_files[@]}"; do
98+
echo "${f#$DORIS_HOME/}"
99+
done
100+
101+
# javac 编译
102+
javac \
103+
-source 8 -target 8 \
104+
-encoding UTF-8 \
105+
-cp "$classpath" \
106+
-sourcepath "$source_path" \
107+
-d "$TARGET_CLASSES" \
108+
"${java_files[@]}" 2>&1
109+
110+
info "编译完成"
111+
}
112+
113+
# ─── 收集需要更新到 jar 的 class 文件 ────────────────────────────────────────
114+
collect_updated_classes() {
115+
local java_files=("$@")
116+
local class_files=()
117+
118+
for java_file in "${java_files[@]}"; do
119+
local rel_path="${java_file#$SRC_ROOT/}"
120+
local class_prefix="$TARGET_CLASSES/${rel_path%.java}"
121+
local dir
122+
dir="$(dirname "$class_prefix")"
123+
local base
124+
base="$(basename "$class_prefix")"
125+
126+
# 主 class 文件
127+
[[ -f "$class_prefix.class" ]] && class_files+=("$class_prefix.class")
128+
129+
# 内部类和匿名类:Foo$Bar.class, Foo$1.class 等
130+
while IFS= read -r inner; do
131+
class_files+=("$inner")
132+
done < <(find "$dir" -maxdepth 1 -name "${base}\$*.class" 2>/dev/null)
133+
done
134+
135+
printf '%s\n' "${class_files[@]}"
136+
}
137+
138+
# ─── 更新 jar ─────────────────────────────────────────────────────────────────
139+
update_jar() {
140+
local class_files=("$@")
141+
142+
info "更新 jar(共 ${#class_files[@]} 个 class 文件)..."
143+
144+
# 将 class 文件路径转为相对于 TARGET_CLASSES 的路径,供 jar 命令使用
145+
local tmpfile
146+
tmpfile="$(mktemp)"
147+
trap "rm -f $tmpfile" EXIT
148+
149+
for cf in "${class_files[@]}"; do
150+
echo "${cf#$TARGET_CLASSES/}" >> "$tmpfile"
151+
done
152+
153+
# 在 TARGET_CLASSES 目录下执行 jar uf,使 jar 内路径正确
154+
pushd "$TARGET_CLASSES" > /dev/null
155+
156+
# 更新 target/doris-fe.jar
157+
if [[ -f "$TARGET_JAR" ]]; then
158+
xargs jar uf "$TARGET_JAR" < "$tmpfile"
159+
info "已更新 $TARGET_JAR"
160+
fi
161+
162+
# 更新 output/fe/lib/doris-fe.jar
163+
if [[ -f "$OUTPUT_JAR" ]]; then
164+
xargs jar uf "$OUTPUT_JAR" < "$tmpfile"
165+
info "已更新 $OUTPUT_JAR"
166+
fi
167+
168+
popd > /dev/null
169+
}
170+
171+
# ─── 主流程 ───────────────────────────────────────────────────────────────────
172+
main() {
173+
check_env
174+
175+
local java_files=()
176+
177+
if [[ $# -gt 0 ]]; then
178+
# 用户直接指定文件
179+
for arg in "$@"; do
180+
# 支持相对路径和绝对路径
181+
local abs_path
182+
if [[ "$arg" = /* ]]; then
183+
abs_path="$arg"
184+
else
185+
abs_path="$(pwd)/$arg"
186+
fi
187+
if [[ ! -f "$abs_path" ]]; then
188+
# 尝试在 SRC_ROOT 下搜索
189+
local found
190+
found="$(find "$SRC_ROOT" -name "$(basename "$arg")" | head -1)"
191+
if [[ -z "$found" ]]; then
192+
error "文件不存在: $arg"
193+
exit 1
194+
fi
195+
abs_path="$found"
196+
fi
197+
java_files+=("$abs_path")
198+
done
199+
info "手动指定 ${#java_files[@]} 个文件"
200+
else
201+
# 自动检测改动
202+
info "扫描改动的 Java 文件..."
203+
while IFS= read -r f; do
204+
[[ -n "$f" ]] && java_files+=("$f")
205+
done < <(find_stale_java_files)
206+
207+
if [[ ${#java_files[@]} -eq 0 ]]; then
208+
info "没有发现需要编译的文件,已是最新状态"
209+
exit 0
210+
fi
211+
info "发现 ${#java_files[@]} 个文件需要重新编译"
212+
fi
213+
214+
local start_time
215+
start_time=$(date +%s)
216+
217+
# 获取 classpath
218+
local classpath
219+
classpath="$(get_classpath)"
220+
221+
# 编译
222+
compile_files "$classpath" "${java_files[@]}"
223+
224+
# 收集 class 文件(含内部类)π
225+
local class_files=()
226+
while IFS= read -r cf; do
227+
[[ -n "$cf" ]] && class_files+=("$cf")
228+
done < <(collect_updated_classes "${java_files[@]}")
229+
230+
if [[ ${#class_files[@]} -eq 0 ]]; then
231+
warn "未找到编译产物,跳过 jar 更新"
232+
exit 0
233+
fi
234+
235+
# 更新 jar
236+
update_jar "${class_files[@]}"
237+
238+
local end_time
239+
end_time=$(date +%s)
240+
info "完成!耗时 $((end_time - start_time))"
241+
}
242+
243+
main "$@"

0 commit comments

Comments
 (0)