test-dependencies.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. #!/bin/sh
  2. # Author: Martin Jansa <martin.jansa@gmail.com>
  3. #
  4. # Copyright (c) 2013 Martin Jansa <Martin.Jansa@gmail.com>
  5. # Used to detect missing dependencies or automagically
  6. # enabled dependencies which aren't explicitly enabled
  7. # or disabled.
  8. # It does 3 builds of <target>
  9. # 1st to populate sstate-cache directory and sysroot
  10. # 2nd to rebuild each recipe with every possible
  11. # dependency found in sysroot (which stays populated
  12. # from 1st build
  13. # 3rd to rebuild each recipe only with dependencies defined
  14. # in DEPENDS
  15. # 4th (optional) repeat build like 3rd to make sure that
  16. # minimal versions of dependencies defined in DEPENDS
  17. # is also enough
  18. # Global vars
  19. tmpdir=
  20. targets=
  21. recipes=
  22. buildhistory=
  23. buildtype=
  24. default_targets="world"
  25. default_buildhistory="buildhistory"
  26. default_buildtype="1 2 3 c"
  27. usage () {
  28. cat << EOF
  29. Welcome to utility to detect missing or autoenabled dependencies.
  30. WARNING: this utility will completely remove your tmpdir (make sure
  31. you don't have important buildhistory or persistent dir there).
  32. $0 <OPTION>
  33. Options:
  34. -h, --help
  35. Display this help and exit.
  36. --tmpdir=<tmpdir>
  37. Specify tmpdir, will use the environment variable TMPDIR if it is not specified.
  38. Something like /OE/oe-core/tmp-eglibc (no / at the end).
  39. --targets=<targets>
  40. List of targets separated by space, will use the environment variable TARGETS if it is not specified.
  41. It will run "bitbake <targets>" to populate sysroots.
  42. Default value is "world".
  43. --recipes=<recipes>
  44. File with list of recipes we want to rebuild with minimal and maximal sysroot.
  45. Will use the environment variable RECIPES if it is not specified.
  46. Default value will use all packages ever recorded in buildhistory directory.
  47. --buildhistory=<buildhistory>
  48. Path to buildhistory directory, it needs to be enabled in your config,
  49. because it's used to detect different dependencies and to create list
  50. of recipes to rebuild when it's not specified.
  51. Will use the environment variable BUILDHISTORY if it is not specified.
  52. Default value is "buildhistory"
  53. --buildtype=<buildtype>
  54. There are 4 types of build:
  55. 1: build to populate sstate-cache directory and sysroot
  56. 2: build to rebuild each recipe with every possible dep
  57. 3: build to rebuild each recipe with minimal dependencies
  58. 4: build to rebuild each recipe again with minimal dependencies
  59. c: compare buildhistory directories from build 2 and 3
  60. Will use the environment variable BUILDTYPE if it is not specified.
  61. Default value is "1 2 3 c", order is important, type 4 is optional.
  62. EOF
  63. }
  64. # Print error information and exit.
  65. echo_error () {
  66. echo "ERROR: $1" >&2
  67. exit 1
  68. }
  69. while [ -n "$1" ]; do
  70. case $1 in
  71. --tmpdir=*)
  72. tmpdir=`echo $1 | sed -e 's#^--tmpdir=##' | xargs readlink -e`
  73. [ -d "$tmpdir" ] || echo_error "Invalid argument to --tmpdir"
  74. shift
  75. ;;
  76. --targets=*)
  77. targets=`echo $1 | sed -e 's#^--targets="*\([^"]*\)"*#\1#'`
  78. shift
  79. ;;
  80. --recipes=*)
  81. recipes=`echo $1 | sed -e 's#^--recipes="*\([^"]*\)"*#\1#'`
  82. shift
  83. ;;
  84. --buildhistory=*)
  85. buildhistory=`echo $1 | sed -e 's#^--buildhistory="*\([^"]*\)"*#\1#'`
  86. shift
  87. ;;
  88. --buildtype=*)
  89. buildtype=`echo $1 | sed -e 's#^--buildtype="*\([^"]*\)"*#\1#'`
  90. shift
  91. ;;
  92. --help|-h)
  93. usage
  94. exit 0
  95. ;;
  96. *)
  97. echo "Invalid arguments $*"
  98. echo_error "Try '$0 -h' for more information."
  99. ;;
  100. esac
  101. done
  102. # tmpdir directory, use environment variable TMPDIR
  103. # if it was not specified, otherwise, error.
  104. [ -n "$tmpdir" ] || tmpdir=$TMPDIR
  105. [ -n "$tmpdir" ] || echo_error "No tmpdir found!"
  106. [ -d "$tmpdir" ] || echo_error "Invalid tmpdir \"$tmpdir\""
  107. [ -n "$targets" ] || targets=$TARGETS
  108. [ -n "$targets" ] || targets=$default_targets
  109. [ -n "$recipes" ] || recipes=$RECIPES
  110. [ -n "$recipes" -a ! -f "$recipes" ] && echo_error "Invalid file with list of recipes to rebuild"
  111. [ -n "$recipes" ] || echo "All packages ever recorded in buildhistory directory will be rebuilt"
  112. [ -n "$buildhistory" ] || buildhistory=$BUILDHISTORY
  113. [ -n "$buildhistory" ] || buildhistory=$default_buildhistory
  114. [ -d "$buildhistory" ] || echo_error "Invalid buildhistory directory \"$buildhistory\""
  115. [ -n "$buildtype" ] || buildtype=$BUILDTYPE
  116. [ -n "$buildtype" ] || buildtype=$default_buildtype
  117. echo "$buildtype" | grep -v '^[1234c ]*$' && echo_error "Invalid buildtype \"$buildtype\", only some combination of 1, 2, 3, 4, c separated by space is allowed"
  118. OUTPUT_BASE=test-dependencies/`date "+%s"`
  119. build_all() {
  120. echo "===== 1st build to populate sstate-cache directory and sysroot ====="
  121. OUTPUT1=${OUTPUT_BASE}/${TYPE}_all
  122. mkdir -p ${OUTPUT1}
  123. echo "Logs will be stored in ${OUTPUT1} directory"
  124. bitbake -k $targets 2>&1 | tee -a ${OUTPUT1}/complete.log
  125. }
  126. build_every_recipe() {
  127. if [ "${TYPE}" = "2" ] ; then
  128. echo "===== 2nd build to rebuild each recipe with every possible dep ====="
  129. OUTPUT_MAX=${OUTPUT_BASE}/${TYPE}_max
  130. OUTPUTB=${OUTPUT_MAX}
  131. else
  132. echo "===== 3rd or 4th build to rebuild each recipe with minimal dependencies ====="
  133. OUTPUT_MIN=${OUTPUT_BASE}/${TYPE}_min
  134. OUTPUTB=${OUTPUT_MIN}
  135. fi
  136. mkdir -p ${OUTPUTB} ${OUTPUTB}/failed ${OUTPUTB}/ok
  137. echo "Logs will be stored in ${OUTPUTB} directory"
  138. if [ -z "$recipes" ]; then
  139. ls -d $buildhistory/packages/*/* | xargs -n 1 basename | sort -u > ${OUTPUTB}/recipe.list
  140. recipes=${OUTPUTB}/recipe.list
  141. fi
  142. if [ "${TYPE}" != "2" ] ; then
  143. echo "!!!Removing tmpdir \"$tmpdir\"!!!"
  144. rm -rf $tmpdir/deploy $tmpdir/pkgdata $tmpdir/sstate-control $tmpdir/stamps $tmpdir/sysroots $tmpdir/work $tmpdir/work-shared 2>/dev/null
  145. fi
  146. i=1
  147. count=`cat $recipes | wc -l`
  148. for recipe in `cat $recipes`; do
  149. echo "Building recipe: ${recipe} ($i/$count)"
  150. bitbake -c cleansstate ${recipe} > ${OUTPUTB}/log.${recipe} 2>&1;
  151. bitbake ${recipe} >> ${OUTPUTB}/log.${recipe} 2>&1;
  152. grep "ERROR: Task.*failed" ${OUTPUTB}/log.${recipe} && mv ${OUTPUTB}/log.${recipe} ${OUTPUTB}/failed/${recipe} || mv ${OUTPUTB}/log.${recipe} ${OUTPUTB}/ok/${recipe}
  153. if [ "${TYPE}" != "2" ] ; then
  154. rm -rf $tmpdir/deploy $tmpdir/pkgdata $tmpdir/sstate-control $tmpdir/stamps $tmpdir/sysroots $tmpdir/work $tmpdir/work-shared 2>/dev/null
  155. fi
  156. i=`expr $i + 1`
  157. done
  158. echo "Copying buildhistory/packages to ${OUTPUTB}"
  159. cp -ra $buildhistory/packages ${OUTPUTB}
  160. # This will be usefull to see which library is pulling new dependency
  161. echo "Copying do_package logs to ${OUTPUTB}/do_package/"
  162. mkdir ${OUTPUTB}/do_package
  163. find $tmpdir/work/ -name log.do_package 2>/dev/null| while read f; do
  164. # pn is 3 levels back, but we don't know if there is just one log per pn (only one arch and version)
  165. # dest=`echo $f | sed 's#^.*/\([^/]*\)/\([^/]*\)/\([^/]*\)/log.do_package#\1#g'`
  166. dest=`echo $f | sed "s#$tmpdir/work/##g; s#/#_#g"`
  167. cp $f ${OUTPUTB}/do_package/$dest
  168. done
  169. grep "ERROR: Task.*failed" ${OUTPUTB}/failed/* 2>/dev/null
  170. ls -1 ${OUTPUTB}/failed/* >> ${OUTPUT_BASE}/failed.recipes 2>/dev/null
  171. }
  172. compare_deps() {
  173. # you can run just compare task with command like this
  174. # OUTPUT_BASE=test-dependencies/1373140172 \
  175. # OUTPUT_MAX=${OUTPUT_BASE}/2_max \
  176. # OUTPUT_MIN=${OUTPUT_BASE}/3_min \
  177. # openembedded-core/scripts/test-dependencies.sh --tmpdir=tmp-eglibc --targets=glib-2.0 --recipes=recipe_list --buildtype=c
  178. echo "===== Compare dependencies recorded in \"${OUTPUT_MAX}\" and \"${OUTPUT_MIN}\" ====="
  179. [ -n "${OUTPUTC}" ] || OUTPUTC=${OUTPUT_BASE}
  180. mkdir -p ${OUTPUTC}
  181. OUTPUT_FILE=${OUTPUTC}/dependency-changes
  182. echo "Differences will be stored in ${OUTPUT_FILE}, dot is shown for every 100 of checked packages"
  183. echo > ${OUTPUT_FILE}
  184. [ -d ${OUTPUT_MAX} ] || echo_error "Directory with output from build 2 \"${OUTPUT_MAX}\" does not exist"
  185. [ -d ${OUTPUT_MIN} ] || echo_error "Directory with output from build 3 \"${OUTPUT_MIN}\" does not exist"
  186. [ -d ${OUTPUT_MAX}/packages/ ] || echo_error "Directory with packages from build 2 \"${OUTPUT_MAX}/packages/\" does not exist"
  187. [ -d ${OUTPUT_MIN}/packages/ ] || echo_error "Directory with packages from build 3 \"${OUTPUT_MIN}/packages/\" does not exist"
  188. i=0
  189. find ${OUTPUT_MAX}/packages/ -name latest | sed "s#${OUTPUT_MAX}/##g" | while read pkg; do
  190. max_pkg=${OUTPUT_MAX}/${pkg}
  191. min_pkg=${OUTPUT_MIN}/${pkg}
  192. if [ ! -f "${min_pkg}" ] ; then
  193. echo "ERROR: ${min_pkg} doesn't exist" | tee -a ${OUTPUT_FILE}
  194. continue
  195. fi
  196. # strip version information in parenthesis
  197. max_deps=`grep "^RDEPENDS = " ${max_pkg} | sed 's/^RDEPENDS = / /g; s/$/ /g; s/([^(]*)//g'`
  198. min_deps=`grep "^RDEPENDS = " ${min_pkg} | sed 's/^RDEPENDS = / /g; s/$/ /g; s/([^(]*)//g'`
  199. if [ "$i" = 100 ] ; then
  200. echo -n "." # cheap progressbar
  201. i=0
  202. fi
  203. if [ "${max_deps}" = "${min_deps}" ] ; then
  204. # it's annoying long, but at least it's showing some progress, warnings are grepped at the end
  205. echo "NOTE: ${pkg} dependencies weren't changed" >> ${OUTPUT_FILE}
  206. else
  207. missing_deps=
  208. for dep in ${max_deps}; do
  209. echo "${min_deps}" | grep -q " ${dep} " || missing_deps="${missing_deps} ${dep}"
  210. done
  211. if [ -n "${missing_deps}" ] ; then
  212. echo # to get rid of dots on last line
  213. echo "WARN: ${pkg} lost dependency on ${missing_deps}" | tee -a ${OUTPUT_FILE}
  214. fi
  215. fi
  216. i=`expr $i + 1`
  217. done
  218. echo # to get rid of dots on last line
  219. echo "Found differences: "
  220. grep "^WARN: " ${OUTPUT_FILE} | tee ${OUTPUT_FILE}.warn
  221. echo "Found errors: "
  222. grep "^ERROR: " ${OUTPUT_FILE} | tee ${OUTPUT_FILE}.error
  223. # useful for reexecuting this script with only small subset of recipes with known issues
  224. sed 's#.*[ /]packages/\([^/]*\)/\([^/]*\)/.*#\2#g' ${OUTPUT_FILE}.warn ${OUTPUT_FILE}.error | sort -u >> ${OUTPUT_BASE}/failed.recipes
  225. }
  226. for TYPE in $buildtype; do
  227. case ${TYPE} in
  228. 1) build_all;;
  229. 2) build_every_recipe;;
  230. 3) build_every_recipe;;
  231. 4) build_every_recipe;;
  232. c) compare_deps;;
  233. *) echo_error "Invalid buildtype \"$TYPE\""
  234. esac
  235. done