|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +export LC_ALL=C |
| 4 | +set -Eeuo pipefail |
| 5 | + |
| 6 | +# Declare paths to libraries |
| 7 | +declare -A LIBS |
| 8 | +LIBS[cli]="libbitcoin_cli.a" |
| 9 | +LIBS[common]="libbitcoin_common.a" |
| 10 | +LIBS[consensus]="libbitcoin_consensus.a" |
| 11 | +LIBS[crypto]="crypto/.libs/libbitcoin_crypto_base.a crypto/.libs/libbitcoin_crypto_x86_shani.a crypto/.libs/libbitcoin_crypto_sse41.a crypto/.libs/libbitcoin_crypto_avx2.a" |
| 12 | +LIBS[node]="libbitcoin_node.a" |
| 13 | +LIBS[util]="libbitcoin_util.a" |
| 14 | +LIBS[wallet]="libbitcoin_wallet.a" |
| 15 | +LIBS[wallet_tool]="libbitcoin_wallet_tool.a" |
| 16 | + |
| 17 | +# Declare allowed dependencies "X Y" where X is allowed to depend on Y. This |
| 18 | +# list is taken from doc/design/libraries.md. |
| 19 | +ALLOWED_DEPENDENCIES=( |
| 20 | + "cli common" |
| 21 | + "cli util" |
| 22 | + "common consensus" |
| 23 | + "common crypto" |
| 24 | + "common util" |
| 25 | + "consensus crypto" |
| 26 | + "node common" |
| 27 | + "node consensus" |
| 28 | + "node crypto" |
| 29 | + "node kernel" |
| 30 | + "node util" |
| 31 | + "util crypto" |
| 32 | + "wallet common" |
| 33 | + "wallet crypto" |
| 34 | + "wallet util" |
| 35 | + "wallet_tool util" |
| 36 | + "wallet_tool wallet" |
| 37 | +) |
| 38 | + |
| 39 | +# Add minor dependencies omitted from doc/design/libraries.md to keep the |
| 40 | +# dependency diagram simple. |
| 41 | +ALLOWED_DEPENDENCIES+=( |
| 42 | + "wallet consensus" |
| 43 | + "wallet_tool common" |
| 44 | + "wallet_tool crypto" |
| 45 | +) |
| 46 | + |
| 47 | +# Declare list of known errors that should be suppressed. |
| 48 | +declare -A SUPPRESS |
| 49 | +# init.cpp file currently calls Berkeley DB sanity check function on startup, so |
| 50 | +# there is an undocumented dependency of the node library on the wallet library. |
| 51 | +SUPPRESS["libbitcoin_node_a-init.o libbitcoin_wallet_a-bdb.o _ZN6wallet27BerkeleyDatabaseSanityCheckEv"]=1 |
| 52 | +# init/common.cpp file calls InitError and InitWarning from interface_ui which |
| 53 | +# is currently part of the node library. interface_ui should just be part of the |
| 54 | +# common library instead, and is moved in |
| 55 | +# https://github.com/bitcoin/bitcoin/issues/10102 |
| 56 | +SUPPRESS["libbitcoin_common_a-common.o libbitcoin_node_a-interface_ui.o _Z11InitWarningRK13bilingual_str"]=1 |
| 57 | +SUPPRESS["libbitcoin_common_a-common.o libbitcoin_node_a-interface_ui.o _Z9InitErrorRK13bilingual_str"]=1 |
| 58 | +# rpc/external_signer.cpp adds defines node RPC methods but is built as part of the |
| 59 | +# common library. It should be moved to the node library instead. |
| 60 | +SUPPRESS["libbitcoin_common_a-external_signer.o libbitcoin_node_a-server.o _ZN9CRPCTable13appendCommandERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEPK11CRPCCommand"]=1 |
| 61 | + |
| 62 | +usage() { |
| 63 | + echo "Usage: $(basename "${BASH_SOURCE[0]}") [BUILD_DIR]" |
| 64 | +} |
| 65 | + |
| 66 | +# Output makefile targets, converting library .a paths to libtool .la targets |
| 67 | +lib_targets() { |
| 68 | + for lib in "${!LIBS[@]}"; do |
| 69 | + for lib_path in ${LIBS[$lib]}; do |
| 70 | + # shellcheck disable=SC2001 |
| 71 | + sed 's:/.libs/\(.*\)\.a$:/\1.la:g' <<<"$lib_path" |
| 72 | + done |
| 73 | + done |
| 74 | +} |
| 75 | + |
| 76 | +# Extract symbol names and object names and write to text files |
| 77 | +extract_symbols() { |
| 78 | + local temp_dir="$1" |
| 79 | + for lib in "${!LIBS[@]}"; do |
| 80 | + for lib_path in ${LIBS[$lib]}; do |
| 81 | + nm -o "$lib_path" | grep ' T ' | awk '{print $3, $1}' >> "${temp_dir}/${lib}_exports.txt" |
| 82 | + nm -o "$lib_path" | grep ' U ' | awk '{print $3, $1}' >> "${temp_dir}/${lib}_imports.txt" |
| 83 | + awk '{print $1}' "${temp_dir}/${lib}_exports.txt" | sort -u > "${temp_dir}/${lib}_exported_symbols.txt" |
| 84 | + awk '{print $1}' "${temp_dir}/${lib}_imports.txt" | sort -u > "${temp_dir}/${lib}_imported_symbols.txt" |
| 85 | + done |
| 86 | + done |
| 87 | +} |
| 88 | + |
| 89 | +# Lookup object name(s) corresponding to symbol name in text file |
| 90 | +obj_names() { |
| 91 | + local symbol="$1" |
| 92 | + local txt_file="$2" |
| 93 | + sed -n "s/^$symbol [^:]\\+:\\([^:]\\+\\):[^:]*\$/\\1/p" "$txt_file" | sort -u |
| 94 | +} |
| 95 | + |
| 96 | +# Iterate through libraries and find disallowed dependencies |
| 97 | +check_libraries() { |
| 98 | + local temp_dir="$1" |
| 99 | + local result=0 |
| 100 | + for src in "${!LIBS[@]}"; do |
| 101 | + for dst in "${!LIBS[@]}"; do |
| 102 | + if [ "$src" != "$dst" ] && ! is_allowed "$src" "$dst"; then |
| 103 | + if ! check_disallowed "$src" "$dst" "$temp_dir"; then |
| 104 | + result=1 |
| 105 | + fi |
| 106 | + fi |
| 107 | + done |
| 108 | + done |
| 109 | + check_not_suppressed |
| 110 | + return $result |
| 111 | +} |
| 112 | + |
| 113 | +# Return whether src library is allowed to depend on dst. |
| 114 | +is_allowed() { |
| 115 | + local src="$1" |
| 116 | + local dst="$2" |
| 117 | + for allowed in "${ALLOWED_DEPENDENCIES[@]}"; do |
| 118 | + if [ "$src $dst" = "$allowed" ]; then |
| 119 | + return 0 |
| 120 | + fi |
| 121 | + done |
| 122 | + return 1 |
| 123 | +} |
| 124 | + |
| 125 | +# Return whether src library imports any symbols from dst, assuming src is not |
| 126 | +# allowed to depend on dst. |
| 127 | +check_disallowed() { |
| 128 | + local src="$1" |
| 129 | + local dst="$2" |
| 130 | + local temp_dir="$3" |
| 131 | + local result=0 |
| 132 | + |
| 133 | + # Loop over symbol names exported by dst and imported by src |
| 134 | + while read symbol; do |
| 135 | + local dst_obj |
| 136 | + dst_obj=$(obj_names "$symbol" "${temp_dir}/${dst}_exports.txt") |
| 137 | + while read src_obj; do |
| 138 | + if ! check_suppress "$src_obj" "$dst_obj" "$symbol"; then |
| 139 | + echo "Error: $src_obj depends on $dst_obj symbol '$(c++filt "$symbol")', can suppess with:" |
| 140 | + echo " SUPPRESS[\"$src_obj $dst_obj $symbol\"]=1" |
| 141 | + result=1 |
| 142 | + fi |
| 143 | + done < <(obj_names "$symbol" "${temp_dir}/${src}_imports.txt") |
| 144 | + done < <(comm -12 "${temp_dir}/${dst}_exported_symbols.txt" "${temp_dir}/${src}_imported_symbols.txt") |
| 145 | + return $result |
| 146 | +} |
| 147 | + |
| 148 | +# Declare array to track errors which were suppressed. |
| 149 | +declare -A SUPPRESSED |
| 150 | + |
| 151 | +# Return whether error should be suppressed and record suppresssion in |
| 152 | +# SUPPRESSED array. |
| 153 | +check_suppress() { |
| 154 | + local src_obj="$1" |
| 155 | + local dst_obj="$2" |
| 156 | + local symbol="$3" |
| 157 | + for suppress in "${!SUPPRESS[@]}"; do |
| 158 | + read suppress_src suppress_dst suppress_pattern <<<"$suppress" |
| 159 | + if [[ "$src_obj" == "$suppress_src" && "$dst_obj" == "$suppress_dst" && "$symbol" =~ $suppress_pattern ]]; then |
| 160 | + SUPPRESSED["$suppress"]=1 |
| 161 | + return 0 |
| 162 | + fi |
| 163 | + done |
| 164 | + return 1 |
| 165 | +} |
| 166 | + |
| 167 | +# Warn about error which were supposed to be suppress, but were not encountered. |
| 168 | +check_not_suppressed() { |
| 169 | + for suppress in "${!SUPPRESS[@]}"; do |
| 170 | + if [[ ! -v SUPPRESSED[$suppress] ]]; then |
| 171 | + echo >&2 "Warning: suppression '$suppress' was ignored, consider deleting." |
| 172 | + fi |
| 173 | + done |
| 174 | +} |
| 175 | + |
| 176 | +# Check arguments. |
| 177 | +if [ "$#" = 0 ]; then |
| 178 | + BUILD_DIR="$(dirname "${BASH_SOURCE[0]}")/../../src" |
| 179 | +elif [ "$#" = 1 ]; then |
| 180 | + BUILD_DIR="$1" |
| 181 | +else |
| 182 | + echo >&2 "Error: wrong number of arguments." |
| 183 | + usage >&2 |
| 184 | + exit 1 |
| 185 | +fi |
| 186 | +if [ ! -f "$BUILD_DIR/Makefile" ]; then |
| 187 | + echo >&2 "Error: directory '$BUILD_DIR' does not contain a makefile, please specify path to build directory for library targets." |
| 188 | + usage >&2 |
| 189 | + exit 1 |
| 190 | +fi |
| 191 | + |
| 192 | +# Build libraries and run checks. |
| 193 | +cd "$BUILD_DIR" |
| 194 | +# shellcheck disable=SC2046 |
| 195 | +make -j"$(nproc)" $(lib_targets) |
| 196 | +TEMP_DIR="$(mktemp -d)" |
| 197 | +extract_symbols "$TEMP_DIR" |
| 198 | +if check_libraries "$TEMP_DIR"; then |
| 199 | + echo "Success! No unexpected dependencies were detected." |
| 200 | +else |
| 201 | + echo >&2 "Error: Unexpected dependencies were detected. Check previous output." |
| 202 | +fi |
| 203 | +rm -r "$TEMP_DIR" |
0 commit comments