|
|
|
#!/usr/bin/env bash
|
|
|
|
|
|
|
|
export LC_ALL=C
|
|
|
|
set -Eeuo pipefail
|
|
|
|
|
|
|
|
# Declare paths to libraries
|
|
|
|
declare -A LIBS
|
|
|
|
LIBS[cli]="libbitcoin_cli.a"
|
|
|
|
LIBS[common]="libbitcoin_common.a"
|
|
|
|
LIBS[consensus]="libbitcoin_consensus.a"
|
|
|
|
LIBS[crypto]="crypto/libbitcoin_crypto.a crypto/libbitcoin_crypto_x86_shani.a crypto/libbitcoin_crypto_sse41.a crypto/libbitcoin_crypto_avx2.a"
|
|
|
|
LIBS[node]="libbitcoin_node.a"
|
|
|
|
LIBS[util]="util/libbitcoin_util.a"
|
|
|
|
LIBS[wallet]="wallet/libbitcoin_wallet.a"
|
|
|
|
|
|
|
|
# Declare allowed dependencies "X Y" where X is allowed to depend on Y. This
|
|
|
|
# list is taken from doc/design/libraries.md.
|
|
|
|
ALLOWED_DEPENDENCIES=(
|
|
|
|
"cli common"
|
|
|
|
"cli util"
|
|
|
|
"common consensus"
|
|
|
|
"common crypto"
|
|
|
|
"common util"
|
|
|
|
"consensus crypto"
|
|
|
|
"node common"
|
|
|
|
"node consensus"
|
|
|
|
"node crypto"
|
|
|
|
"node kernel"
|
|
|
|
"node util"
|
|
|
|
"util crypto"
|
|
|
|
"wallet common"
|
|
|
|
"wallet crypto"
|
|
|
|
"wallet util"
|
|
|
|
)
|
|
|
|
|
|
|
|
# Add minor dependencies omitted from doc/design/libraries.md to keep the
|
|
|
|
# dependency diagram simple.
|
|
|
|
ALLOWED_DEPENDENCIES+=(
|
|
|
|
"wallet consensus"
|
|
|
|
)
|
|
|
|
|
|
|
|
# Declare list of known errors that should be suppressed.
|
|
|
|
declare -A SUPPRESS
|
|
|
|
# init.cpp file currently calls Berkeley DB sanity check function on startup, so
|
|
|
|
# there is an undocumented dependency of the node library on the wallet library.
|
|
|
|
SUPPRESS["init.cpp.o bdb.cpp.o _ZN6wallet27BerkeleyDatabaseSanityCheckEv"]=1
|
|
|
|
# init/common.cpp file calls InitError and InitWarning from interface_ui which
|
|
|
|
# is currently part of the node library. interface_ui should just be part of the
|
|
|
|
# common library instead, and is moved in
|
|
|
|
# https://github.com/bitcoin/bitcoin/issues/10102
|
|
|
|
SUPPRESS["common.cpp.o interface_ui.cpp.o _Z11InitWarningRK13bilingual_str"]=1
|
|
|
|
SUPPRESS["common.cpp.o interface_ui.cpp.o _Z9InitErrorRK13bilingual_str"]=1
|
|
|
|
# rpc/external_signer.cpp adds defines node RPC methods but is built as part of the
|
|
|
|
# common library. It should be moved to the node library instead.
|
|
|
|
SUPPRESS["external_signer.cpp.o server.cpp.o _ZN9CRPCTable13appendCommandERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEPK11CRPCCommand"]=1
|
|
|
|
|
|
|
|
usage() {
|
|
|
|
echo "Usage: $(basename "${BASH_SOURCE[0]}") [BUILD_DIR]"
|
|
|
|
}
|
|
|
|
|
|
|
|
# Output makefile targets, converting library .a paths to libtool .la targets
|
|
|
|
lib_targets() {
|
|
|
|
for lib in "${!LIBS[@]}"; do
|
|
|
|
for lib_path in ${LIBS[$lib]}; do
|
|
|
|
local name="${lib_path##*/}"
|
|
|
|
name="${name#lib}"
|
|
|
|
name="${name%.a}"
|
|
|
|
echo "$name"
|
|
|
|
done
|
|
|
|
done
|
|
|
|
}
|
|
|
|
|
|
|
|
# Extract symbol names and object names and write to text files
|
|
|
|
extract_symbols() {
|
|
|
|
local temp_dir="$1"
|
|
|
|
for lib in "${!LIBS[@]}"; do
|
|
|
|
for lib_path in ${LIBS[$lib]}; do
|
|
|
|
nm -o "$lib_path" | grep ' T \| W ' | awk '{print $3, $1}' >> "${temp_dir}/${lib}_exports.txt"
|
|
|
|
nm -o "$lib_path" | grep ' U ' | awk '{print $3, $1}' >> "${temp_dir}/${lib}_imports.txt"
|
|
|
|
awk '{print $1}' "${temp_dir}/${lib}_exports.txt" | sort -u > "${temp_dir}/${lib}_exported_symbols.txt"
|
|
|
|
awk '{print $1}' "${temp_dir}/${lib}_imports.txt" | sort -u > "${temp_dir}/${lib}_imported_symbols.txt"
|
|
|
|
done
|
|
|
|
done
|
|
|
|
}
|
|
|
|
|
|
|
|
# Lookup object name(s) corresponding to symbol name in text file
|
|
|
|
obj_names() {
|
|
|
|
local symbol="$1"
|
|
|
|
local txt_file="$2"
|
|
|
|
sed -n "s/^$symbol [^:]\\+:\\([^:]\\+\\):[^:]*\$/\\1/p" "$txt_file" | sort -u
|
|
|
|
}
|
|
|
|
|
|
|
|
# Iterate through libraries and find disallowed dependencies
|
|
|
|
check_libraries() {
|
|
|
|
local temp_dir="$1"
|
|
|
|
local result=0
|
|
|
|
for src in "${!LIBS[@]}"; do
|
|
|
|
for dst in "${!LIBS[@]}"; do
|
|
|
|
if [ "$src" != "$dst" ] && ! is_allowed "$src" "$dst"; then
|
|
|
|
if ! check_disallowed "$src" "$dst" "$temp_dir"; then
|
|
|
|
result=1
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
done
|
|
|
|
check_not_suppressed
|
|
|
|
return $result
|
|
|
|
}
|
|
|
|
|
|
|
|
# Return whether src library is allowed to depend on dst.
|
|
|
|
is_allowed() {
|
|
|
|
local src="$1"
|
|
|
|
local dst="$2"
|
|
|
|
for allowed in "${ALLOWED_DEPENDENCIES[@]}"; do
|
|
|
|
if [ "$src $dst" = "$allowed" ]; then
|
|
|
|
return 0
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
# Return whether src library imports any symbols from dst, assuming src is not
|
|
|
|
# allowed to depend on dst.
|
|
|
|
check_disallowed() {
|
|
|
|
local src="$1"
|
|
|
|
local dst="$2"
|
|
|
|
local temp_dir="$3"
|
|
|
|
local result=0
|
|
|
|
|
|
|
|
# Loop over symbol names exported by dst and imported by src
|
|
|
|
while read symbol; do
|
|
|
|
local dst_obj
|
|
|
|
dst_obj=$(obj_names "$symbol" "${temp_dir}/${dst}_exports.txt")
|
|
|
|
while read src_obj; do
|
|
|
|
if ! check_suppress "$src_obj" "$dst_obj" "$symbol"; then
|
|
|
|
echo "Error: $src_obj depends on $dst_obj symbol '$(c++filt "$symbol")', can suppess with:"
|
|
|
|
echo " SUPPRESS[\"$src_obj $dst_obj $symbol\"]=1"
|
|
|
|
result=1
|
|
|
|
fi
|
|
|
|
done < <(obj_names "$symbol" "${temp_dir}/${src}_imports.txt")
|
|
|
|
done < <(comm -12 "${temp_dir}/${dst}_exported_symbols.txt" "${temp_dir}/${src}_imported_symbols.txt")
|
|
|
|
return $result
|
|
|
|
}
|
|
|
|
|
|
|
|
# Declare array to track errors which were suppressed.
|
|
|
|
declare -A SUPPRESSED
|
|
|
|
|
|
|
|
# Return whether error should be suppressed and record suppresssion in
|
|
|
|
# SUPPRESSED array.
|
|
|
|
check_suppress() {
|
|
|
|
local src_obj="$1"
|
|
|
|
local dst_obj="$2"
|
|
|
|
local symbol="$3"
|
|
|
|
for suppress in "${!SUPPRESS[@]}"; do
|
|
|
|
read suppress_src suppress_dst suppress_pattern <<<"$suppress"
|
|
|
|
if [[ "$src_obj" == "$suppress_src" && "$dst_obj" == "$suppress_dst" && "$symbol" =~ $suppress_pattern ]]; then
|
|
|
|
SUPPRESSED["$suppress"]=1
|
|
|
|
return 0
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
# Warn about error which were supposed to be suppress, but were not encountered.
|
|
|
|
check_not_suppressed() {
|
|
|
|
for suppress in "${!SUPPRESS[@]}"; do
|
|
|
|
if [[ ! -v SUPPRESSED[$suppress] ]]; then
|
|
|
|
echo >&2 "Warning: suppression '$suppress' was ignored, consider deleting."
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
}
|
|
|
|
|
|
|
|
# Check arguments.
|
|
|
|
if [ "$#" = 0 ]; then
|
|
|
|
BUILD_DIR="$(dirname "${BASH_SOURCE[0]}")/../../build"
|
|
|
|
elif [ "$#" = 1 ]; then
|
|
|
|
BUILD_DIR="$1"
|
|
|
|
else
|
|
|
|
echo >&2 "Error: wrong number of arguments."
|
|
|
|
usage >&2
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
if [ ! -f "$BUILD_DIR/Makefile" ]; then
|
|
|
|
echo >&2 "Error: directory '$BUILD_DIR' does not contain a makefile, please specify path to build directory for library targets."
|
|
|
|
usage >&2
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
|
|
|
|
# Build libraries and run checks.
|
|
|
|
# shellcheck disable=SC2046
|
|
|
|
cmake --build "$BUILD_DIR" -j"$(nproc)" -t $(lib_targets)
|
|
|
|
TEMP_DIR="$(mktemp -d)"
|
|
|
|
cd "$BUILD_DIR/src"
|
|
|
|
extract_symbols "$TEMP_DIR"
|
|
|
|
if check_libraries "$TEMP_DIR"; then
|
|
|
|
echo "Success! No unexpected dependencies were detected."
|
|
|
|
else
|
|
|
|
echo >&2 "Error: Unexpected dependencies were detected. Check previous output."
|
|
|
|
fi
|
|
|
|
rm -r "$TEMP_DIR"
|