Repository navigation
Expand file tree
/
Copy pathlink.sh
More file actions
executable file
·285 lines (250 loc) · 8.65 KB
/
Copy pathlink.sh
File metadata and controls
executable file
·285 lines (250 loc) · 8.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#!/usr/bin/env bash
# Link helper for object-only builds.
# Example:
# ./link.sh smtps_test.cpp.o
# ./link.sh smtp_test.o -o smtpsServer
set -euo pipefail
SCRIPT_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
print_usage() {
cat <<'EOF'
Usage: ./link.sh <entry_obj> [-o output] [--obj-root dir] [--compiler cxx] [--dry-run]
Options:
--obj-root <dir> 显式指定收集 .o 的根目录。强烈建议在部署脚本里always 指定:
不指定时会退回按脚本所在位置推断,若同一目录下存在多份
对象树(如 obj/ 与 build-obj/)会被一并链接 → 重复符号。
--exclude <pat> 跳过路径含该子串的 .o(用于排除其它入口的 main)
--compiler <cxx> 指定编译器(交叉部署时通常是目标机上的 g++-13)
Examples:
./link.sh smtps_test.cpp.o
./link.sh smtp_test.o -o smtpsServer
./link.sh smtps_test.cpp.o --compiler g++-13
./link.sh obj/CMakeFiles/smtpsServer_obj.dir/test/smtps_test.cpp.o \
--obj-root obj -o smtpsServer --compiler g++-13
./link.sh smtps_test.cpp.o --pie
./link.sh smtps_test.cpp.o --dry-run
EOF
}
if [[ $# -lt 1 ]]; then
print_usage
exit 1
fi
ENTRY_INPUT="$1"
shift
OUTPUT="smtpsServer"
CXX_BIN="${CXX:-g++}"
DRY_RUN="0"
PIE_MODE="OFF"
OBJ_ROOT_OVERRIDE=""
EXCLUDE_PATTERNS=()
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output)
[[ $# -ge 2 ]] || { echo "Missing value for $1" >&2; exit 1; }
OUTPUT="$2"
shift 2
;;
--compiler)
[[ $# -ge 2 ]] || { echo "Missing value for --compiler" >&2; exit 1; }
CXX_BIN="$2"
shift 2
;;
--obj-root)
[[ $# -ge 2 ]] || { echo "Missing value for --obj-root" >&2; exit 1; }
OBJ_ROOT_OVERRIDE="$2"
shift 2
;;
--exclude)
[[ $# -ge 2 ]] || { echo "Missing value for --exclude" >&2; exit 1; }
EXCLUDE_PATTERNS+=("$2")
shift 2
;;
--dry-run)
DRY_RUN="1"
shift
;;
--pie)
PIE_MODE="ON"
shift
;;
-h|--help)
print_usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
print_usage
exit 1
;;
esac
done
if ! command -v "$CXX_BIN" >/dev/null 2>&1; then
echo "C++ compiler not found: $CXX_BIN" >&2
exit 1
fi
resolve_entry_obj() {
local input="$1"
local base stem
if [[ -f "$input" ]]; then
printf '%s\n' "$input"
return
fi
base="$(basename "$input")"
stem="${base%.o}"
# 指定了 --obj-root 就只在它底下找。
# 否则从 CWD 全盘搜:部署机上同名入口 .o 可能同时存在于当前对象树和历史遗留的
# build-obj/ 里,模糊匹配会挑中旧的,链出一个用旧代码编译的二进制且毫无提示。
local search_root="${OBJ_ROOT_OVERRIDE:-.}"
local matches=()
while IFS= read -r line; do
matches+=("$line")
done < <(find "$search_root" -type f -name "$base" | sort)
if [[ ${#matches[@]} -eq 0 ]]; then
while IFS= read -r line; do
matches+=("$line")
done < <(find "$search_root" -type f -name "*${stem}*.o" | sort)
fi
if [[ ${#matches[@]} -eq 0 ]]; then
local wildcard_stem
wildcard_stem="${stem//_/*}"
wildcard_stem="${wildcard_stem//-/*}"
while IFS= read -r line; do
matches+=("$line")
done < <(find "$search_root" -type f -name "*${wildcard_stem}*.o" | sort)
fi
if [[ ${#matches[@]} -eq 0 ]]; then
echo "Cannot find entry object: $input" >&2
exit 1
fi
if [[ ${#matches[@]} -gt 1 ]]; then
echo "Multiple matching object files found for '$input', using first:" >&2
printf ' %s\n' "${matches[@]}" >&2
fi
printf '%s\n' "${matches[0]}"
}
ENTRY_OBJ="$(resolve_entry_obj "$ENTRY_INPUT")"
abspath() {
local p="$1"
local d
if [[ -d "$p" ]]; then
(cd "$p" && pwd)
else
d="$(cd "$(dirname "$p")" && pwd)"
printf '%s/%s\n' "$d" "$(basename "$p")"
fi
}
ENTRY_OBJ_ABS="$(abspath "$ENTRY_OBJ")"
# 对象根目录:优先用显式 --obj-root。
#
# 没有显式指定时才退回推断,但推断规则有个历史陷阱:入口 .o 若位于脚本所在
# 目录之下,就会收集该目录下**所有** .o。部署机上同时存在 obj/ 与 build-obj/
# 两份对象树时,两份会被一起链接 → 重复符号。过去靠"把 link.sh 拷到 /tmp 再跑"
# 来绕开,属于用脚本位置来控制行为,太隐晦。部署脚本请一律显式传 --obj-root。
if [[ -n "$OBJ_ROOT_OVERRIDE" ]]; then
if [[ ! -d "$OBJ_ROOT_OVERRIDE" ]]; then
echo "Object root not found: $OBJ_ROOT_OVERRIDE" >&2
exit 1
fi
OBJ_ROOT="$(abspath "$OBJ_ROOT_OVERRIDE")"
if [[ "$ENTRY_OBJ_ABS" != "$OBJ_ROOT/"* ]]; then
echo "Entry object is outside --obj-root:" >&2
echo " entry : $ENTRY_OBJ_ABS" >&2
echo " obj-root : $OBJ_ROOT" >&2
exit 1
fi
elif [[ "$ENTRY_OBJ_ABS" == "$SCRIPT_HOME/"* ]]; then
OBJ_ROOT="$SCRIPT_HOME"
elif [[ "$ENTRY_OBJ_ABS" == *".dir/"* ]]; then
OBJ_ROOT="${ENTRY_OBJ_ABS%%.dir/*}.dir"
else
OBJ_ROOT="$(dirname "$ENTRY_OBJ_ABS")"
fi
if [[ ! -d "$OBJ_ROOT" ]]; then
echo "Object root not found: $OBJ_ROOT" >&2
exit 1
fi
# 同一份对象树里出现多个 CMake 对象目录时提前告警:它们各自带一个 main,
# 链接必然重复符号。历史上 obj/ 与 build-obj/ 混链就是这么炸的。
_obj_dirs="$(find "$OBJ_ROOT" -type d -name '*_obj.dir' 2>/dev/null | wc -l | tr -d ' ')"
if [[ "$_obj_dirs" -gt 1 && ${#EXCLUDE_PATTERNS[@]} -eq 0 ]]; then
echo "Warning: found $_obj_dirs *_obj.dir trees under $OBJ_ROOT and no --exclude given." >&2
echo " Multiple entry points will collide (duplicate main). Consider --exclude." >&2
fi
OBJECTS=()
while IFS= read -r line; do
skip=0
for pat in "${EXCLUDE_PATTERNS[@]}"; do
if [[ "$line" == *"$pat"* ]]; then
skip=1
break
fi
done
[[ $skip -eq 0 ]] && OBJECTS+=("$line")
done < <(find "$OBJ_ROOT" -type f -name '*.o' | sort)
if [[ ${#OBJECTS[@]} -eq 0 ]]; then
echo "No object files found under: $OBJ_ROOT" >&2
exit 1
fi
# Ensure entry object is present in the link list.
if ! printf '%s\n' "${OBJECTS[@]}" | grep -F -x -q "$ENTRY_OBJ_ABS"; then
OBJECTS+=("$ENTRY_OBJ_ABS")
fi
add_pkg_libs() {
local pkg="$1"
if command -v pkg-config >/dev/null 2>&1 && pkg-config --exists "$pkg"; then
# shellcheck disable=SC2207
PKG_LIBS+=( $(pkg-config --libs "$pkg") )
return 0
fi
return 1
}
PKG_LIBS=()
FALLBACK_LIBS=()
# Prefer pkg-config when available.
if [[ "${PROTORELAY_LOG_BACKEND:-SPDLOG}" == "SPDLOG" ]]; then
add_pkg_libs spdlog || FALLBACK_LIBS+=( -lspdlog )
fi
add_pkg_libs openssl || FALLBACK_LIBS+=( -lssl -lcrypto )
add_pkg_libs libcares || add_pkg_libs cares || FALLBACK_LIBS+=( -lcares )
add_pkg_libs mysqlclient || add_pkg_libs mariadb || add_pkg_libs libmariadb || FALLBACK_LIBS+=( -lmysqlclient )
# nlohmann/json and Boost.Asio are header-first, but Boost.System/Thread are commonly required.
FALLBACK_LIBS+=( -lboost_system -lboost_thread -pthread -ldl -lz )
# 额外链接参数(环境变量,空格分隔)。
# 用途:ASan 交叉部署时由 deploy.sh 传入 -fsanitize=address,undefined,
# 让目标机 g++ 在链接期拉起 libasan/libubsan(编译侧的 -fsanitize 不够,
# 链接时不带会直接 undefined symbol: __asan_report_*)。
# shellcheck disable=SC2206
EXTRA_LDFLAGS=( ${LINK_EXTRA_FLAGS:-} )
LINK_CMD=( "$CXX_BIN" -o "$OUTPUT" )
if [[ "$PIE_MODE" != "ON" ]]; then
# Ubuntu enables PIE by default; many transferred .o files are non-PIE.
LINK_CMD+=( -no-pie )
fi
LINK_CMD+=( "${OBJECTS[@]}" )
LINK_CMD+=( "${PKG_LIBS[@]}" )
LINK_CMD+=( "${FALLBACK_LIBS[@]}" )
LINK_CMD+=( "${EXTRA_LDFLAGS[@]}" )
echo "Entry object : $ENTRY_OBJ_ABS"
echo "Object root : $OBJ_ROOT"
echo "Object count : ${#OBJECTS[@]}"
echo "Output file : $OUTPUT"
echo "Compiler : $CXX_BIN"
echo ""
echo "Link command:"
printf ' %q' "${LINK_CMD[@]}"
echo ""
if [[ "$DRY_RUN" == "1" ]]; then
echo ""
echo "Dry-run only. No linking performed."
exit 0
fi
if "${LINK_CMD[@]}"; then
echo ""
echo "Link succeeded: $OUTPUT"
else
echo ""
echo "Link failed."
echo "Tips:"
echo " 1) Install target libraries on this machine (boost, spdlog, openssl, c-ares, mysqlclient)."
echo " 2) Or pass --compiler to use the intended target compiler toolchain."
exit 1
fi