summaryrefslogtreecommitdiffstats
path: root/tools/check-lib-dependencies.sh
blob: b2ae9acdcf244fa9ede4319e5a192197d2d39cd5 (plain)
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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
#!/bin/bash

# extract folder name containing file
#
# param ${1} file name
# return folder name
extract_folder_name() {
	# return name of the file until last '/'
	echo "$(echo "${1}" | rev | cut -d '/' -f 2- | rev)"
}

# extract all includes found in source files found in the same folder as specified Makefile.am
#
# param ${1} path to a Makefile.am
# return all dependencies libs in the order of compilation
extract_includes() {
	# extract folder name from current library Makefile.am
	CURRENT_FOLDER=$(extract_folder_name "${1}")"/"
	# select only files in current folder
	SEARCH_FILES=$(echo "${FILE_LIST}" | grep "${CURRENT_FOLDER}")
	# select all lines containing '#include ' directive
	RAW_INCLUDES_LIST=$(echo "${SEARCH_FILES}" | xargs grep "^#include " 2>/dev/null)
	# filter only included dependencies found in other libraries by using the form 'other_lib_name/header_file.h'
	# to do this it is required to select the string between '<' and '>', searching for '/' character and returning the name until last '/'
	RAW_INCLUDES_LIST=$(echo "${RAW_INCLUDES_LIST}" | cut -d "#" -f 2 | tr "\"" " " | cut -d "<" -f 2 | cut -d ">" -f 1 | grep "\/" | rev | cut -d "/" -f 2 | rev | sort | uniq)
	# filter includes that are not compiled by the project's Makefiles
	INCLUDES_LIST=
	for i in ${LIBRARIES_LIST}; do
		for j in ${RAW_INCLUDES_LIST}; do
			if test "${j}" = "${i}"; then
				INCLUDES_LIST="${i} ${INCLUDES_LIST}"
				break
			fi
		done
	done
	# remove empty spaces
	INCLUDES_LIST=$(echo ${INCLUDES_LIST} | tr -s " ")
	# order dependencies in the order of compilation
	FILTERED_INCLUDES_LIST=
	for i in ${LIBRARIES_LIST}; do
		if test $(echo "${INCLUDES_LIST}" | grep "\b${i}\b" | wc -l) -ne 0; then
			FILTERED_INCLUDES_LIST="${i} ${FILTERED_INCLUDES_LIST}"
		fi
	done
	echo "${FILTERED_INCLUDES_LIST}"
}

# extract all header only files and headers and source files found in the external library required by specified library
# param ${1} name of the current library
# param ${2} name of the external dependency library required by current library
# return the list of header only files as 'HEADERS: heaser1.h header2.h' and header and source files as 'HEADERS_AND_SOURCES: source1.h source1.cc source2.h source2.cpp'
extract_non_include_files() {
	# extract folder name for current library Makefile.am
	CURRENT_FOLDER=$(extract_folder_name "src/lib/${1}/Makefile.am")"/"
	# extract folder name for external dependency library Makefile.am
	EXTERNAL_FOLDER=$(extract_folder_name "src/lib/${2}/Makefile.am")"/"
	# select only files in current folder
	SEARCH_FILES=$(echo "${FILE_LIST}" | grep "${CURRENT_FOLDER}")
	HEADERS_LIST=
	NON_HEADERS_LIST=
	# select all lines containing '#include ' directive
	RAW_INCLUDES_LIST=$(echo "${SEARCH_FILES}" | xargs grep "^#include " 2>/dev/null)
	# filter only included headers found in other libraries by using the form 'other_lib_name/header_file.h'
	# to do this it is required to select the string between '<' and '>', searching for '/' character, search for the extension marker '.' and returning the name after last '/'
	RAW_INCLUDES_LIST=$(echo "${RAW_INCLUDES_LIST}" | cut -d "#" -f 2 | tr "\"" " " | cut -d "<" -f 2 | cut -d ">" -f 1 | grep "\/" | grep "\b${2}\b" | cut -d "/" -f 2 | grep "\." | sort | uniq)
	# select only files in dependency library folder and strip full path
	RELATIVE_SEARCH_FILES=$(echo "${FILE_LIST}" | grep "${EXTERNAL_FOLDER}" | sed -e "s#${REPO_FOLDER}${EXTERNAL_FOLDER}##g")
	# search for the header file but also for source files
	for i in ${RAW_INCLUDES_LIST}; do
		# filter by name only (no extension)
		FILTER=$(echo "${i}" | cut -d "." -f 1)
		# filter non header files with exact name of the header file without the extension
		NON_HEADER=$(echo "${RELATIVE_SEARCH_FILES}" | grep "\b${FILTER}\." | grep -v "${i}")
		if test $(echo "${NON_HEADER}" | wc -w) -ne 0; then
			# append header and source file names
			NON_HEADERS_LIST="${i} ${NON_HEADER} ${NON_HEADERS_LIST}"
		else
			# append header only file name
			HEADERS_LIST="${i} ${HEADERS_LIST}"
		fi
	done
	# sort header only files
	HEADERS_LIST=$(echo ${HEADERS_LIST} | tr -s " " | sort | uniq)
	# sort header and source files
	NON_HEADERS_LIST=$(echo ${NON_HEADERS_LIST} | tr -s " " | sort | uniq)
	echo "HEADERS_AND_SOURCES:${NON_HEADERS_LIST}"
	echo "HEADERS:${HEADERS_LIST}"
}

# extract all valid dependencies of a specified library
#
# param ${1} list of all libraries in the reverse compilation order
# param ${2} library name for which the dependency list is computed
# return the list of dependencies for specified library in the reverse compilation order 
extract_dependencies() {
	echo "${1}" | grep -Eo "\b${2}\b.*$"
}

# extract computed dependency for specified library
#
# param ${1} library name for which the dependency list is retrieved
# param ${2} library path for which the dependency list is retrieved
# return stored value of computed dependencies or 'NONE' if dependencies have not been computed yet
extract_computed_dependencies() {
	PATH_TO_NAME=$(echo "${2}" | tr -s "/" "_")
	NAME="COMPUTED_DEPENDENCIES_${PATH_TO_NAME}_${1}"
	if test -n "${!NAME+x}"; then
		echo "${!NAME}"
	else
		echo "NONE"
	fi
}

# extract library directive
#
# param ${1} artifact path
extract_library_directive() {
	ARTIFACT_PATH="${1}"
	echo `cat ${ARTIFACT_PATH}/Makefile.am | grep "LIBADD\|LDADD" | sort | tr -s ' ' | cut -d " " -f 1 | sort -u`
}

# extract library name
#
# param ${1} artifact path
extract_library_name() {
	ARTIFACT_PATH="${1}"
	echo `cat ${ARTIFACT_PATH}/Makefile.am | grep "LIBRARIES" | tr -s ' ' | cut -d " " -f 3`
}

# compute artifact dependencies
#
# param ${1} artifact name
# param ${2} artifact path
compute_dependencies() {
	ARTIFACT="${1}"
	ARTIFACT_PATH="${2}"
	echo ""
	echo "########################################"
	echo "### ${ARTIFACT_PATH}/${ARTIFACT}"
	echo "########################################"
	echo ""
	# all valid dependencies that can be added by each dependency library
	echo "${ARTIFACT_PATH}/${ARTIFACT} valid dependencies:"
	echo "${VALID_LIST}"
	# detect dependencies errors by searching for dependencies that are compiled after the current library and can generate missing symbols
	NON_RECURSIVE_BASE_DEPENDENCIES=
	for j in ${BASE_DEPENDENCIES}; do
		# only add the dependency if it is in the valid dependencies list to prevent infinite recursion and log the error otherwise
		if test $(echo "${VALID_LIST}" | grep "\b${j}\b" | wc -l) -eq 0; then
			# search for external header and source files
			INVALID_EXTERNAL_DEPENDENCIES=$(extract_non_include_files "${ARTIFACT}" "${j}")
			# filter header only external files
			EXTERNAL_HEADERS=$(echo "${INVALID_EXTERNAL_DEPENDENCIES}" | grep "HEADERS:" | cut -d ":" -f 2)
			# filter header and source external files
			EXTERNAL_ALL=$(echo "${INVALID_EXTERNAL_DEPENDENCIES}" | grep "HEADERS_AND_SOURCES:" | cut -d ":" -f 2)
			echo "### ERROR ### dependencies ERROR for ${ARTIFACT_PATH}/${ARTIFACT} on ${j} with:"
			# if there are any header only external files
			if test $(echo "${EXTERNAL_ALL}" | wc -w) -ne 0; then
				echo "non header only files: ${EXTERNAL_ALL}"
			fi
			# if there are any header and source external files
			if test $(echo "${EXTERNAL_HEADERS}" | wc -w) -ne 0; then
				echo "header only files: ${EXTERNAL_HEADERS}"
			fi
		else
			# don't add current library to it's dependencies list
			if test ${j} != ${ARTIFACT}; then
				NON_RECURSIVE_BASE_DEPENDENCIES="${NON_RECURSIVE_BASE_DEPENDENCIES} ${j}"
			fi
		fi
	done
	# all found dependencies in the reverse compilation order
	BASE_DEPENDENCIES=$(echo "${BASE_DEPENDENCIES}" | xargs)
	# all found and valid dependencies in the reverse compilation order
	NON_RECURSIVE_BASE_DEPENDENCIES=$(echo "${NON_RECURSIVE_BASE_DEPENDENCIES}" | xargs)
	echo "${ARTIFACT_PATH}/${ARTIFACT} base dependencies:"
	echo "${BASE_DEPENDENCIES}"
	echo "${ARTIFACT_PATH}/${ARTIFACT} non recursive dependencies:"
	echo "${NON_RECURSIVE_BASE_DEPENDENCIES}"
	# minimum set of dependencies for current library
	DEPENDENCIES=
	for j in ${NON_RECURSIVE_BASE_DEPENDENCIES}; do
		NEW_DEPENDENCIES=$(extract_computed_dependencies "${j}" "src/lib")
		if test "${NEW_DEPENDENCIES}" == "NONE"; then
			echo "### ERROR ### computed dependency not found for ${j}"
		else
			DEPENDENCIES="${NEW_DEPENDENCIES} ${DEPENDENCIES}"
		fi
	done
	DEPENDENCIES=$(echo "${DEPENDENCIES} ${NON_RECURSIVE_BASE_DEPENDENCIES}" | tr -s " " "\n" | sort | uniq | xargs)
	# order dependencies in the order of compilation
	SORTED_DEPENDENCIES=
	for j in ${LIBRARIES_LIST}; do
		if test $(echo "${DEPENDENCIES}" | grep "\b${j}\b" | wc -l) -ne 0; then
			SORTED_DEPENDENCIES="${j} ${SORTED_DEPENDENCIES}"
		fi
	done
	echo "${ARTIFACT_PATH}/${ARTIFACT} minimum dependencies:"
	echo "${SORTED_DEPENDENCIES}"
	echo ""
	echo "++++++++++++++++++++++++++++++++++++++++"
	ARTIFACT_DIRECTIVE=$(extract_library_directive ${ARTIFACT_PATH}/${ARTIFACT})
	for j in ${SORTED_DEPENDENCIES}; do
		DEPENDENCY_LIBRARY_NAME=$(extract_library_name "src/lib/${j}")
		echo "${ARTIFACT_DIRECTIVE} += \$(top_builddir)/src/lib/${j}/${DEPENDENCY_LIBRARY_NAME}"
	done
	echo "++++++++++++++++++++++++++++++++++++++++"
	echo "########################################"
	echo ""
}

# if wrong number of parameters print usage
if test ${#} -ne 1; then
	echo "Usage: ${0} path/to/kea/repo"
	exit
fi

# folder containing full repo
REPO_FOLDER=${1}

if test $(echo -n ${REPO_FOLDER} | tail -c 1) != "/"; then
	REPO_FOLDER="${REPO_FOLDER}/"
fi

# filter all Makefile.am files
MAKEFILES_LIST=$(find ${REPO_FOLDER} | grep "Makefile\.am" | sed -e "s#${REPO_FOLDER}##g" | grep "src\/" | sort)

# if no Makefile.am found exit
if test -z "${MAKEFILES_LIST}"; then
	echo "invalid repo path: no Makefile.am file found"
	exit
fi

echo "list of Makefile.am:"
echo "${MAKEFILES_LIST}"

# base Makefile.am for all sources is in src/lib/Makefile.am
BASE_MAKEFILE=$(echo "${MAKEFILES_LIST}" | grep "src\/lib\/Makefile.am")

# if no src/lib/Makefile.am found exit
if test -z ${BASE_MAKEFILE}; then
	echo "invalid repo path: no src/lib/Makefile.am file found"
	exit
fi

echo "base Makefile.am:"
echo "${BASE_MAKEFILE}"

# generate the list of libraries in the compilation order
LIBRARIES_LIST=
RAW_LIBRARIES_LIST=$(cat "${REPO_FOLDER}${BASE_MAKEFILE}" | grep "SUBDIRS")
for i in ${RAW_LIBRARIES_LIST}; do
	LIBRARIES_LIST="${LIBRARIES_LIST} $(echo ${i} | grep -v "SUBDIRS" | grep -v '=')"
done

# remove empty spaces
LIBRARIES_LIST=$(echo "${LIBRARIES_LIST}" | tr -s ' ' | xargs)

# generate the list of libraries in the reverse compilation order
REVERSE_LIBRARIES_LIST=
for i in ${LIBRARIES_LIST}; do
	REVERSE_LIBRARIES_LIST="${i} ${REVERSE_LIBRARIES_LIST}"
done

echo "list of libraries:"
echo "${LIBRARIES_LIST}"

echo "reverse list of libraries:"
echo "${REVERSE_LIBRARIES_LIST}"

# filter all files of interest ignoring irrelevant ones
# ignore .git, .libs, .deps doc folders and .o .lo .Plo .Po .gcno .gcda .m4 .dox .json .mes files
FILE_LIST=$(find "${REPO_FOLDER}" 2>/dev/null | grep -v "\.git" | grep -v "\/\.libs\/" | grep -v "\.o$" | grep -v "\/\.deps\/" | grep -v "\.lo$" | grep -v "\.Plo$" | grep -v "\.Po$" | grep -v "\.gcno$" | grep -v "gcda" | grep -v "\.m4$" | grep -v "\.dox$" | grep -v "\.json$" | grep -v "\/doc\/" | grep -v "\.mes$" | sort)

#echo "files:"
#echo "${FILE_LIST}"

BASE_LIBRARIES_MAKEFILES=

# generate the list of dependencies for all libraries in src/lib
for i in ${LIBRARIES_LIST}; do
	# generate current library Makefile.am path
	BASE_LIBRARIES_MAKEFILES="${BASE_LIBRARIES_MAKEFILES} src/lib/${i}/Makefile.am"
	# extract dependencies found in the library folder
	BASE_DEPENDENCIES=$(extract_includes "src/lib/${i}/Makefile.am")
	# generate the list of valid dependencies for the current library (take compilation order into account)
	VALID_LIST=$(extract_dependencies "${REVERSE_LIBRARIES_LIST}" "${i}")
	compute_dependencies "${i}" "src/lib"
	PATH_TO_NAME=$(echo "src/lib" | tr -s "/" "_")
	declare COMPUTED_DEPENDENCIES_${PATH_TO_NAME}_${i}="${SORTED_DEPENDENCIES}"
done

# remove empty spaces
BASE_LIBRARIES_MAKEFILES=$(echo "${BASE_LIBRARIES_MAKEFILES}" | xargs | tr -s " " "\n")

echo "base Makefiles.am files:"
echo "${BASE_LIBRARIES_MAKEFILES}"

OTHER_MAKEFILES=$(echo "${MAKEFILES_LIST}" | tr -s " " "\n" | grep -v "src/lib/" | grep -v "src/share/" | grep -v "src/Makefile.am")
# remove empty spaces
OTHER_MAKEFILES=$(echo "${OTHER_MAKEFILES}" | xargs | tr -s " " "\n")

echo "remaining Makefile.am files:"
echo "${OTHER_MAKEFILES}"

for i in ${OTHER_MAKEFILES}; do
	# extract dependencies found in the artifact folder
	BASE_DEPENDENCIES=$(extract_includes "${i}")
	# generate the list of valid dependencies for the current artifact (take compilation order into account)
	VALID_LIST="${REVERSE_LIBRARIES_LIST}"
	ARTIFACT=$(echo "${i}" | rev | cut -d "/" -f 2 | rev)
	ARTIFACT_PATH=$(echo "${i}" | rev | cut -d "/" -f 3- | rev)
	compute_dependencies "${ARTIFACT}" "${ARTIFACT_PATH}"
	PATH_TO_NAME=$(echo "${ARTIFACT_PATH}" | tr -s "/" "_")
	declare COMPUTED_DEPENDENCIES_${PATH_TO_NAME}_${ARTIFACT}="${SORTED_DEPENDENCIES}"
done

exit