blob: 581d532693cc8b7735a98366564eada5fc4c2d30 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#
2# Records history of build output in order to detect regressions
3#
4# Based in part on testlab.bbclass and packagehistory.bbclass
5#
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05006# Copyright (C) 2011-2016 Intel Corporation
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007# Copyright (C) 2007-2011 Koen Kooi <koen@openembedded.org>
8#
9
10BUILDHISTORY_FEATURES ?= "image package sdk"
11BUILDHISTORY_DIR ?= "${TOPDIR}/buildhistory"
12BUILDHISTORY_DIR_IMAGE = "${BUILDHISTORY_DIR}/images/${MACHINE_ARCH}/${TCLIBC}/${IMAGE_BASENAME}"
13BUILDHISTORY_DIR_PACKAGE = "${BUILDHISTORY_DIR}/packages/${MULTIMACH_TARGET_SYS}/${PN}"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050014
15# Setting this to non-empty will remove the old content of the buildhistory as part of
16# the current bitbake invocation and replace it with information about what was built
17# during the build.
18#
19# This is meant to be used in continuous integration (CI) systems when invoking bitbake
20# for full world builds. The effect in that case is that information about packages
21# that no longer get build also gets removed from the buildhistory, which is not
22# the case otherwise.
23#
24# The advantage over manually cleaning the buildhistory outside of bitbake is that
25# the "version-going-backwards" check still works. When relying on that, be careful
26# about failed world builds: they will lead to incomplete information in the
27# buildhistory because information about packages that could not be built will
28# also get removed. A CI system should handle that by discarding the buildhistory
29# of failed builds.
30#
31# The expected usage is via auto.conf, but passing via the command line also works
32# with: BB_ENV_EXTRAWHITE=BUILDHISTORY_RESET BUILDHISTORY_RESET=1
33BUILDHISTORY_RESET ?= ""
34
35BUILDHISTORY_OLD_DIR = "${BUILDHISTORY_DIR}/${@ "old" if "${BUILDHISTORY_RESET}" else ""}"
36BUILDHISTORY_OLD_DIR_PACKAGE = "${BUILDHISTORY_OLD_DIR}/packages/${MULTIMACH_TARGET_SYS}/${PN}"
37BUILDHISTORY_DIR_SDK = "${BUILDHISTORY_DIR}/sdk/${SDK_NAME}${SDK_EXT}/${IMAGE_BASENAME}"
Patrick Williamsc124f4f2015-09-15 14:41:29 -050038BUILDHISTORY_IMAGE_FILES ?= "/etc/passwd /etc/group"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050039BUILDHISTORY_SDK_FILES ?= "conf/local.conf conf/bblayers.conf conf/auto.conf conf/locked-sigs.inc conf/devtool.conf"
Patrick Williamsc124f4f2015-09-15 14:41:29 -050040BUILDHISTORY_COMMIT ?= "0"
41BUILDHISTORY_COMMIT_AUTHOR ?= "buildhistory <buildhistory@${DISTRO}>"
42BUILDHISTORY_PUSH_REPO ?= ""
43
44SSTATEPOSTINSTFUNCS_append = " buildhistory_emit_pkghistory"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050045# We want to avoid influencing the signatures of sstate tasks - first the function itself:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050046sstate_install[vardepsexclude] += "buildhistory_emit_pkghistory"
47# then the value added to SSTATEPOSTINSTFUNCS:
48SSTATEPOSTINSTFUNCS[vardepvalueexclude] .= "| buildhistory_emit_pkghistory"
49
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050050# All items excepts those listed here will be removed from a recipe's
51# build history directory by buildhistory_emit_pkghistory(). This is
52# necessary because some of these items (package directories, files that
53# we no longer emit) might be obsolete.
Patrick Williamsc124f4f2015-09-15 14:41:29 -050054#
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050055# When extending build history, derive your class from buildhistory.bbclass
56# and extend this list here with the additional files created by the derived
57# class.
58BUILDHISTORY_PRESERVE = "latest latest_srcrev"
59
60#
61# Write out metadata about this package for comparison when writing future packages
Patrick Williamsc124f4f2015-09-15 14:41:29 -050062#
63python buildhistory_emit_pkghistory() {
64 if not d.getVar('BB_CURRENTTASK', True) in ['packagedata', 'packagedata_setscene']:
65 return 0
66
67 if not "package" in (d.getVar('BUILDHISTORY_FEATURES', True) or "").split():
68 return 0
69
70 import re
71 import json
72 import errno
73
74 pkghistdir = d.getVar('BUILDHISTORY_DIR_PACKAGE', True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050075 oldpkghistdir = d.getVar('BUILDHISTORY_OLD_DIR_PACKAGE', True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050076
77 class RecipeInfo:
78 def __init__(self, name):
79 self.name = name
80 self.pe = "0"
81 self.pv = "0"
82 self.pr = "r0"
83 self.depends = ""
84 self.packages = ""
85 self.srcrev = ""
86
87
88 class PackageInfo:
89 def __init__(self, name):
90 self.name = name
91 self.pe = "0"
92 self.pv = "0"
93 self.pr = "r0"
94 # pkg/pkge/pkgv/pkgr should be empty because we want to be able to default them
95 self.pkg = ""
96 self.pkge = ""
97 self.pkgv = ""
98 self.pkgr = ""
99 self.size = 0
100 self.depends = ""
101 self.rprovides = ""
102 self.rdepends = ""
103 self.rrecommends = ""
104 self.rsuggests = ""
105 self.rreplaces = ""
106 self.rconflicts = ""
107 self.files = ""
108 self.filelist = ""
109 # Variables that need to be written to their own separate file
110 self.filevars = dict.fromkeys(['pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm'])
111
112 # Should check PACKAGES here to see if anything removed
113
114 def readPackageInfo(pkg, histfile):
115 pkginfo = PackageInfo(pkg)
116 with open(histfile, "r") as f:
117 for line in f:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500118 lns = line.split('=', 1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500119 name = lns[0].strip()
120 value = lns[1].strip(" \t\r\n").strip('"')
121 if name == "PE":
122 pkginfo.pe = value
123 elif name == "PV":
124 pkginfo.pv = value
125 elif name == "PR":
126 pkginfo.pr = value
127 elif name == "PKG":
128 pkginfo.pkg = value
129 elif name == "PKGE":
130 pkginfo.pkge = value
131 elif name == "PKGV":
132 pkginfo.pkgv = value
133 elif name == "PKGR":
134 pkginfo.pkgr = value
135 elif name == "RPROVIDES":
136 pkginfo.rprovides = value
137 elif name == "RDEPENDS":
138 pkginfo.rdepends = value
139 elif name == "RRECOMMENDS":
140 pkginfo.rrecommends = value
141 elif name == "RSUGGESTS":
142 pkginfo.rsuggests = value
143 elif name == "RREPLACES":
144 pkginfo.rreplaces = value
145 elif name == "RCONFLICTS":
146 pkginfo.rconflicts = value
147 elif name == "PKGSIZE":
148 pkginfo.size = long(value)
149 elif name == "FILES":
150 pkginfo.files = value
151 elif name == "FILELIST":
152 pkginfo.filelist = value
153 # Apply defaults
154 if not pkginfo.pkg:
155 pkginfo.pkg = pkginfo.name
156 if not pkginfo.pkge:
157 pkginfo.pkge = pkginfo.pe
158 if not pkginfo.pkgv:
159 pkginfo.pkgv = pkginfo.pv
160 if not pkginfo.pkgr:
161 pkginfo.pkgr = pkginfo.pr
162 return pkginfo
163
164 def getlastpkgversion(pkg):
165 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500166 histfile = os.path.join(oldpkghistdir, pkg, "latest")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500167 return readPackageInfo(pkg, histfile)
168 except EnvironmentError:
169 return None
170
171 def sortpkglist(string):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500172 pkgiter = re.finditer(r'[a-zA-Z0-9.+-]+( \([><=]+[^)]+\))?', string, 0)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500173 pkglist = [p.group(0) for p in pkgiter]
174 pkglist.sort()
175 return ' '.join(pkglist)
176
177 def sortlist(string):
178 items = string.split(' ')
179 items.sort()
180 return ' '.join(items)
181
182 pn = d.getVar('PN', True)
183 pe = d.getVar('PE', True) or "0"
184 pv = d.getVar('PV', True)
185 pr = d.getVar('PR', True)
186
187 pkgdata_dir = d.getVar('PKGDATA_DIR', True)
188 packages = ""
189 try:
190 with open(os.path.join(pkgdata_dir, pn)) as f:
191 for line in f.readlines():
192 if line.startswith('PACKAGES: '):
193 packages = oe.utils.squashspaces(line.split(': ', 1)[1])
194 break
195 except IOError as e:
196 if e.errno == errno.ENOENT:
197 # Probably a -cross recipe, just ignore
198 return 0
199 else:
200 raise
201
202 packagelist = packages.split()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500203 preserve = d.getVar('BUILDHISTORY_PRESERVE', True).split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500204 if not os.path.exists(pkghistdir):
205 bb.utils.mkdirhier(pkghistdir)
206 else:
207 # Remove files for packages that no longer exist
208 for item in os.listdir(pkghistdir):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500209 if item not in preserve:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500210 if item not in packagelist:
211 itempath = os.path.join(pkghistdir, item)
212 if os.path.isdir(itempath):
213 for subfile in os.listdir(itempath):
214 os.unlink(os.path.join(itempath, subfile))
215 os.rmdir(itempath)
216 else:
217 os.unlink(itempath)
218
219 rcpinfo = RecipeInfo(pn)
220 rcpinfo.pe = pe
221 rcpinfo.pv = pv
222 rcpinfo.pr = pr
223 rcpinfo.depends = sortlist(oe.utils.squashspaces(d.getVar('DEPENDS', True) or ""))
224 rcpinfo.packages = packages
225 write_recipehistory(rcpinfo, d)
226
227 pkgdest = d.getVar('PKGDEST', True)
228 for pkg in packagelist:
229 pkgdata = {}
230 with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
231 for line in f.readlines():
232 item = line.rstrip('\n').split(': ', 1)
233 key = item[0]
234 if key.endswith('_' + pkg):
235 key = key[:-len(pkg)-1]
236 pkgdata[key] = item[1].decode('utf-8').decode('string_escape')
237
238 pkge = pkgdata.get('PKGE', '0')
239 pkgv = pkgdata['PKGV']
240 pkgr = pkgdata['PKGR']
241 #
242 # Find out what the last version was
243 # Make sure the version did not decrease
244 #
245 lastversion = getlastpkgversion(pkg)
246 if lastversion:
247 last_pkge = lastversion.pkge
248 last_pkgv = lastversion.pkgv
249 last_pkgr = lastversion.pkgr
250 r = bb.utils.vercmp((pkge, pkgv, pkgr), (last_pkge, last_pkgv, last_pkgr))
251 if r < 0:
252 msg = "Package version for package %s went backwards which would break package feeds from (%s:%s-%s to %s:%s-%s)" % (pkg, last_pkge, last_pkgv, last_pkgr, pkge, pkgv, pkgr)
253 package_qa_handle_error("version-going-backwards", msg, d)
254
255 pkginfo = PackageInfo(pkg)
256 # Apparently the version can be different on a per-package basis (see Python)
257 pkginfo.pe = pkgdata.get('PE', '0')
258 pkginfo.pv = pkgdata['PV']
259 pkginfo.pr = pkgdata['PR']
260 pkginfo.pkg = pkgdata['PKG']
261 pkginfo.pkge = pkge
262 pkginfo.pkgv = pkgv
263 pkginfo.pkgr = pkgr
264 pkginfo.rprovides = sortpkglist(oe.utils.squashspaces(pkgdata.get('RPROVIDES', "")))
265 pkginfo.rdepends = sortpkglist(oe.utils.squashspaces(pkgdata.get('RDEPENDS', "")))
266 pkginfo.rrecommends = sortpkglist(oe.utils.squashspaces(pkgdata.get('RRECOMMENDS', "")))
267 pkginfo.rsuggests = sortpkglist(oe.utils.squashspaces(pkgdata.get('RSUGGESTS', "")))
268 pkginfo.rreplaces = sortpkglist(oe.utils.squashspaces(pkgdata.get('RREPLACES', "")))
269 pkginfo.rconflicts = sortpkglist(oe.utils.squashspaces(pkgdata.get('RCONFLICTS', "")))
270 pkginfo.files = oe.utils.squashspaces(pkgdata.get('FILES', ""))
271 for filevar in pkginfo.filevars:
272 pkginfo.filevars[filevar] = pkgdata.get(filevar, "")
273
274 # Gather information about packaged files
275 val = pkgdata.get('FILES_INFO', '')
276 dictval = json.loads(val)
277 filelist = dictval.keys()
278 filelist.sort()
279 pkginfo.filelist = " ".join(filelist)
280
281 pkginfo.size = int(pkgdata['PKGSIZE'])
282
283 write_pkghistory(pkginfo, d)
284
285 # Create files-in-<package-name>.txt files containing a list of files of each recipe's package
286 bb.build.exec_func("buildhistory_list_pkg_files", d)
287}
288
289
290def write_recipehistory(rcpinfo, d):
291 import codecs
292
293 bb.debug(2, "Writing recipe history")
294
295 pkghistdir = d.getVar('BUILDHISTORY_DIR_PACKAGE', True)
296
297 infofile = os.path.join(pkghistdir, "latest")
298 with codecs.open(infofile, "w", encoding='utf8') as f:
299 if rcpinfo.pe != "0":
300 f.write(u"PE = %s\n" % rcpinfo.pe)
301 f.write(u"PV = %s\n" % rcpinfo.pv)
302 f.write(u"PR = %s\n" % rcpinfo.pr)
303 f.write(u"DEPENDS = %s\n" % rcpinfo.depends)
304 f.write(u"PACKAGES = %s\n" % rcpinfo.packages)
305
306
307def write_pkghistory(pkginfo, d):
308 import codecs
309
310 bb.debug(2, "Writing package history for package %s" % pkginfo.name)
311
312 pkghistdir = d.getVar('BUILDHISTORY_DIR_PACKAGE', True)
313
314 pkgpath = os.path.join(pkghistdir, pkginfo.name)
315 if not os.path.exists(pkgpath):
316 bb.utils.mkdirhier(pkgpath)
317
318 infofile = os.path.join(pkgpath, "latest")
319 with codecs.open(infofile, "w", encoding='utf8') as f:
320 if pkginfo.pe != "0":
321 f.write(u"PE = %s\n" % pkginfo.pe)
322 f.write(u"PV = %s\n" % pkginfo.pv)
323 f.write(u"PR = %s\n" % pkginfo.pr)
324
325 pkgvars = {}
326 pkgvars['PKG'] = pkginfo.pkg if pkginfo.pkg != pkginfo.name else ''
327 pkgvars['PKGE'] = pkginfo.pkge if pkginfo.pkge != pkginfo.pe else ''
328 pkgvars['PKGV'] = pkginfo.pkgv if pkginfo.pkgv != pkginfo.pv else ''
329 pkgvars['PKGR'] = pkginfo.pkgr if pkginfo.pkgr != pkginfo.pr else ''
330 for pkgvar in pkgvars:
331 val = pkgvars[pkgvar]
332 if val:
333 f.write(u"%s = %s\n" % (pkgvar, val))
334
335 f.write(u"RPROVIDES = %s\n" % pkginfo.rprovides)
336 f.write(u"RDEPENDS = %s\n" % pkginfo.rdepends)
337 f.write(u"RRECOMMENDS = %s\n" % pkginfo.rrecommends)
338 if pkginfo.rsuggests:
339 f.write(u"RSUGGESTS = %s\n" % pkginfo.rsuggests)
340 if pkginfo.rreplaces:
341 f.write(u"RREPLACES = %s\n" % pkginfo.rreplaces)
342 if pkginfo.rconflicts:
343 f.write(u"RCONFLICTS = %s\n" % pkginfo.rconflicts)
344 f.write(u"PKGSIZE = %d\n" % pkginfo.size)
345 f.write(u"FILES = %s\n" % pkginfo.files)
346 f.write(u"FILELIST = %s\n" % pkginfo.filelist)
347
348 for filevar in pkginfo.filevars:
349 filevarpath = os.path.join(pkgpath, "latest.%s" % filevar)
350 val = pkginfo.filevars[filevar]
351 if val:
352 with codecs.open(filevarpath, "w", encoding='utf8') as f:
353 f.write(val)
354 else:
355 if os.path.exists(filevarpath):
356 os.unlink(filevarpath)
357
358#
359# rootfs_type can be: image, sdk_target, sdk_host
360#
361def buildhistory_list_installed(d, rootfs_type="image"):
362 from oe.rootfs import image_list_installed_packages
363 from oe.sdk import sdk_list_installed_packages
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500364 from oe.utils import format_pkg_list
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500365
366 process_list = [('file', 'bh_installed_pkgs.txt'),\
367 ('deps', 'bh_installed_pkgs_deps.txt')]
368
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500369 if rootfs_type == "image":
370 pkgs = image_list_installed_packages(d)
371 else:
372 pkgs = sdk_list_installed_packages(d, rootfs_type == "sdk_target")
373
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500374 for output_type, output_file in process_list:
375 output_file_full = os.path.join(d.getVar('WORKDIR', True), output_file)
376
377 with open(output_file_full, 'w') as output:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500378 output.write(format_pkg_list(pkgs, output_type))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500379
380python buildhistory_list_installed_image() {
381 buildhistory_list_installed(d)
382}
383
384python buildhistory_list_installed_sdk_target() {
385 buildhistory_list_installed(d, "sdk_target")
386}
387
388python buildhistory_list_installed_sdk_host() {
389 buildhistory_list_installed(d, "sdk_host")
390}
391
392buildhistory_get_installed() {
393 mkdir -p $1
394
395 # Get list of installed packages
396 pkgcache="$1/installed-packages.tmp"
397 cat ${WORKDIR}/bh_installed_pkgs.txt | sort > $pkgcache && rm ${WORKDIR}/bh_installed_pkgs.txt
398
399 cat $pkgcache | awk '{ print $1 }' > $1/installed-package-names.txt
400 if [ -s $pkgcache ] ; then
401 cat $pkgcache | awk '{ print $2 }' | xargs -n1 basename > $1/installed-packages.txt
402 else
403 printf "" > $1/installed-packages.txt
404 fi
405
406 # Produce dependency graph
407 # First, quote each name to handle characters that cause issues for dot
408 sed 's:\([^| ]*\):"\1":g' ${WORKDIR}/bh_installed_pkgs_deps.txt > $1/depends.tmp && \
409 rm ${WORKDIR}/bh_installed_pkgs_deps.txt
410 # Change delimiter from pipe to -> and set style for recommend lines
411 sed -i -e 's:|: -> :' -e 's:"\[REC\]":[style=dotted]:' -e 's:$:;:' $1/depends.tmp
412 # Add header, sorted and de-duped contents and footer and then delete the temp file
413 printf "digraph depends {\n node [shape=plaintext]\n" > $1/depends.dot
414 cat $1/depends.tmp | sort | uniq >> $1/depends.dot
415 echo "}" >> $1/depends.dot
416 rm $1/depends.tmp
417
418 # Produce installed package sizes list
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500419 oe-pkgdata-util -p ${PKGDATA_DIR} read-value "PKGSIZE" -n -f $pkgcache > $1/installed-package-sizes.tmp
420 cat $1/installed-package-sizes.tmp | awk '{print $2 "\tKiB " $1}' | sort -n -r > $1/installed-package-sizes.txt
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500421 rm $1/installed-package-sizes.tmp
422
423 # We're now done with the cache, delete it
424 rm $pkgcache
425
426 if [ "$2" != "sdk" ] ; then
427 # Produce some cut-down graphs (for readability)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500428 grep -v kernel-image $1/depends.dot | grep -v kernel-3 | grep -v kernel-4 > $1/depends-nokernel.dot
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500429 grep -v libc6 $1/depends-nokernel.dot | grep -v libgcc > $1/depends-nokernel-nolibc.dot
430 grep -v update- $1/depends-nokernel-nolibc.dot > $1/depends-nokernel-nolibc-noupdate.dot
431 grep -v kernel-module $1/depends-nokernel-nolibc-noupdate.dot > $1/depends-nokernel-nolibc-noupdate-nomodules.dot
432 fi
433
434 # add complementary package information
435 if [ -e ${WORKDIR}/complementary_pkgs.txt ]; then
436 cp ${WORKDIR}/complementary_pkgs.txt $1
437 fi
438}
439
440buildhistory_get_image_installed() {
441 # Anything requiring the use of the packaging system should be done in here
442 # in case the packaging files are going to be removed for this image
443
444 if [ "${@bb.utils.contains('BUILDHISTORY_FEATURES', 'image', '1', '0', d)}" = "0" ] ; then
445 return
446 fi
447
448 buildhistory_get_installed ${BUILDHISTORY_DIR_IMAGE}
449}
450
451buildhistory_get_sdk_installed() {
452 # Anything requiring the use of the packaging system should be done in here
453 # in case the packaging files are going to be removed for this SDK
454
455 if [ "${@bb.utils.contains('BUILDHISTORY_FEATURES', 'sdk', '1', '0', d)}" = "0" ] ; then
456 return
457 fi
458
459 buildhistory_get_installed ${BUILDHISTORY_DIR_SDK}/$1 sdk
460}
461
462buildhistory_get_sdk_installed_host() {
463 buildhistory_get_sdk_installed host
464}
465
466buildhistory_get_sdk_installed_target() {
467 buildhistory_get_sdk_installed target
468}
469
470buildhistory_list_files() {
471 # List the files in the specified directory, but exclude date/time etc.
472 # This awk script is somewhat messy, but handles where the size is not printed for device files under pseudo
473 if [ "$3" = "fakeroot" ] ; then
474 ( cd $1 && ${FAKEROOTENV} ${FAKEROOTCMD} find . ! -path . -printf "%M %-10u %-10g %10s %p -> %l\n" | sort -k5 | sed 's/ * -> $//' > $2 )
475 else
476 ( cd $1 && find . ! -path . -printf "%M %-10u %-10g %10s %p -> %l\n" | sort -k5 | sed 's/ * -> $//' > $2 )
477 fi
478}
479
480buildhistory_list_pkg_files() {
481 # Create individual files-in-package for each recipe's package
482 for pkgdir in $(find ${PKGDEST}/* -maxdepth 0 -type d); do
483 pkgname=$(basename $pkgdir)
484 outfolder="${BUILDHISTORY_DIR_PACKAGE}/$pkgname"
485 outfile="$outfolder/files-in-package.txt"
486 # Make sure the output folder exists so we can create the file
487 if [ ! -d $outfolder ] ; then
488 bbdebug 2 "Folder $outfolder does not exist, file $outfile not created"
489 continue
490 fi
491 buildhistory_list_files $pkgdir $outfile fakeroot
492 done
493}
494
495buildhistory_get_imageinfo() {
496 if [ "${@bb.utils.contains('BUILDHISTORY_FEATURES', 'image', '1', '0', d)}" = "0" ] ; then
497 return
498 fi
499
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500500 mkdir -p ${BUILDHISTORY_DIR_IMAGE}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500501 buildhistory_list_files ${IMAGE_ROOTFS} ${BUILDHISTORY_DIR_IMAGE}/files-in-image.txt
502
503 # Collect files requested in BUILDHISTORY_IMAGE_FILES
504 rm -rf ${BUILDHISTORY_DIR_IMAGE}/image-files
505 for f in ${BUILDHISTORY_IMAGE_FILES}; do
506 if [ -f ${IMAGE_ROOTFS}/$f ] ; then
507 mkdir -p ${BUILDHISTORY_DIR_IMAGE}/image-files/`dirname $f`
508 cp ${IMAGE_ROOTFS}/$f ${BUILDHISTORY_DIR_IMAGE}/image-files/$f
509 fi
510 done
511
512 # Record some machine-readable meta-information about the image
513 printf "" > ${BUILDHISTORY_DIR_IMAGE}/image-info.txt
514 cat >> ${BUILDHISTORY_DIR_IMAGE}/image-info.txt <<END
515${@buildhistory_get_imagevars(d)}
516END
517 imagesize=`du -ks ${IMAGE_ROOTFS} | awk '{ print $1 }'`
518 echo "IMAGESIZE = $imagesize" >> ${BUILDHISTORY_DIR_IMAGE}/image-info.txt
519
520 # Add some configuration information
521 echo "${MACHINE}: ${IMAGE_BASENAME} configured for ${DISTRO} ${DISTRO_VERSION}" > ${BUILDHISTORY_DIR_IMAGE}/build-id.txt
522
523 cat >> ${BUILDHISTORY_DIR_IMAGE}/build-id.txt <<END
524${@buildhistory_get_build_id(d)}
525END
526}
527
528buildhistory_get_sdkinfo() {
529 if [ "${@bb.utils.contains('BUILDHISTORY_FEATURES', 'sdk', '1', '0', d)}" = "0" ] ; then
530 return
531 fi
532
533 buildhistory_list_files ${SDK_OUTPUT} ${BUILDHISTORY_DIR_SDK}/files-in-sdk.txt
534
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500535 # Collect files requested in BUILDHISTORY_SDK_FILES
536 rm -rf ${BUILDHISTORY_DIR_SDK}/sdk-files
537 for f in ${BUILDHISTORY_SDK_FILES}; do
538 if [ -f ${SDK_OUTPUT}/${SDKPATH}/$f ] ; then
539 mkdir -p ${BUILDHISTORY_DIR_SDK}/sdk-files/`dirname $f`
540 cp ${SDK_OUTPUT}/${SDKPATH}/$f ${BUILDHISTORY_DIR_SDK}/sdk-files/$f
541 fi
542 done
543
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500544 # Record some machine-readable meta-information about the SDK
545 printf "" > ${BUILDHISTORY_DIR_SDK}/sdk-info.txt
546 cat >> ${BUILDHISTORY_DIR_SDK}/sdk-info.txt <<END
547${@buildhistory_get_sdkvars(d)}
548END
549 sdksize=`du -ks ${SDK_OUTPUT} | awk '{ print $1 }'`
550 echo "SDKSIZE = $sdksize" >> ${BUILDHISTORY_DIR_SDK}/sdk-info.txt
551}
552
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500553python buildhistory_get_extra_sdkinfo() {
554 import operator
555 import math
556 if d.getVar('BB_CURRENTTASK', True) == 'populate_sdk_ext':
557 tasksizes = {}
558 filesizes = {}
559 for root, _, files in os.walk(d.expand('${SDK_OUTPUT}/${SDKPATH}/sstate-cache')):
560 for fn in files:
561 if fn.endswith('.tgz'):
562 fsize = int(math.ceil(float(os.path.getsize(os.path.join(root, fn))) / 1024))
563 task = fn.rsplit(':', 1)[1].split('_', 1)[1].split('.')[0]
564 origtotal = tasksizes.get(task, 0)
565 tasksizes[task] = origtotal + fsize
566 filesizes[fn] = fsize
567 with open(d.expand('${BUILDHISTORY_DIR_SDK}/sstate-package-sizes.txt'), 'w') as f:
568 filesizes_sorted = sorted(filesizes.items(), key=operator.itemgetter(1), reverse=True)
569 for fn, size in filesizes_sorted:
570 f.write('%10d KiB %s\n' % (size, fn))
571 with open(d.expand('${BUILDHISTORY_DIR_SDK}/sstate-task-sizes.txt'), 'w') as f:
572 tasksizes_sorted = sorted(tasksizes.items(), key=operator.itemgetter(1), reverse=True)
573 for task, size in tasksizes_sorted:
574 f.write('%10d KiB %s\n' % (size, task))
575}
576
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500577# By using ROOTFS_POSTUNINSTALL_COMMAND we get in after uninstallation of
578# unneeded packages but before the removal of packaging files
579ROOTFS_POSTUNINSTALL_COMMAND += " buildhistory_list_installed_image ;\
580 buildhistory_get_image_installed ; "
581
582IMAGE_POSTPROCESS_COMMAND += " buildhistory_get_imageinfo ; "
583
584# We want these to be the last run so that we get called after complementary package installation
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500585POPULATE_SDK_POST_TARGET_COMMAND_append = " buildhistory_list_installed_sdk_target;"
586POPULATE_SDK_POST_TARGET_COMMAND_append = " buildhistory_get_sdk_installed_target;"
587POPULATE_SDK_POST_TARGET_COMMAND[vardepvalueexclude] .= "| buildhistory_list_installed_sdk_target;| buildhistory_get_sdk_installed_target;"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500588
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500589POPULATE_SDK_POST_HOST_COMMAND_append = " buildhistory_list_installed_sdk_host;"
590POPULATE_SDK_POST_HOST_COMMAND_append = " buildhistory_get_sdk_installed_host;"
591POPULATE_SDK_POST_HOST_COMMAND[vardepvalueexclude] .= "| buildhistory_list_installed_sdk_host;| buildhistory_get_sdk_installed_host;"
592
593SDK_POSTPROCESS_COMMAND_append = " buildhistory_get_sdkinfo ; buildhistory_get_extra_sdkinfo; "
594SDK_POSTPROCESS_COMMAND[vardepvalueexclude] .= "| buildhistory_get_sdkinfo ; buildhistory_get_extra_sdkinfo; "
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500595
596def buildhistory_get_build_id(d):
597 if d.getVar('BB_WORKERCONTEXT', True) != '1':
598 return ""
599 localdata = bb.data.createCopy(d)
600 bb.data.update_data(localdata)
601 statuslines = []
602 for func in oe.data.typed_value('BUILDCFG_FUNCS', localdata):
603 g = globals()
604 if func not in g:
605 bb.warn("Build configuration function '%s' does not exist" % func)
606 else:
607 flines = g[func](localdata)
608 if flines:
609 statuslines.extend(flines)
610
611 statusheader = d.getVar('BUILDCFG_HEADER', True)
612 return('\n%s\n%s\n' % (statusheader, '\n'.join(statuslines)))
613
614def buildhistory_get_metadata_revs(d):
615 # We want an easily machine-readable format here, so get_layers_branch_rev isn't quite what we want
616 layers = (d.getVar("BBLAYERS", True) or "").split()
617 medadata_revs = ["%-17s = %s:%s" % (os.path.basename(i), \
618 base_get_metadata_git_branch(i, None).strip(), \
619 base_get_metadata_git_revision(i, None)) \
620 for i in layers]
621 return '\n'.join(medadata_revs)
622
623def outputvars(vars, listvars, d):
624 vars = vars.split()
625 listvars = listvars.split()
626 ret = ""
627 for var in vars:
628 value = d.getVar(var, True) or ""
629 if var in listvars:
630 # Squash out spaces
631 value = oe.utils.squashspaces(value)
632 ret += "%s = %s\n" % (var, value)
633 return ret.rstrip('\n')
634
635def buildhistory_get_imagevars(d):
636 if d.getVar('BB_WORKERCONTEXT', True) != '1':
637 return ""
638 imagevars = "DISTRO DISTRO_VERSION USER_CLASSES IMAGE_CLASSES IMAGE_FEATURES IMAGE_LINGUAS IMAGE_INSTALL BAD_RECOMMENDATIONS NO_RECOMMENDATIONS PACKAGE_EXCLUDE ROOTFS_POSTPROCESS_COMMAND IMAGE_POSTPROCESS_COMMAND"
639 listvars = "USER_CLASSES IMAGE_CLASSES IMAGE_FEATURES IMAGE_LINGUAS IMAGE_INSTALL BAD_RECOMMENDATIONS PACKAGE_EXCLUDE"
640 return outputvars(imagevars, listvars, d)
641
642def buildhistory_get_sdkvars(d):
643 if d.getVar('BB_WORKERCONTEXT', True) != '1':
644 return ""
645 sdkvars = "DISTRO DISTRO_VERSION SDK_NAME SDK_VERSION SDKMACHINE SDKIMAGE_FEATURES BAD_RECOMMENDATIONS NO_RECOMMENDATIONS PACKAGE_EXCLUDE"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500646 if d.getVar('BB_CURRENTTASK', True) == 'populate_sdk_ext':
647 # Extensible SDK uses some additional variables
648 sdkvars += " SDK_LOCAL_CONF_WHITELIST SDK_LOCAL_CONF_BLACKLIST SDK_INHERIT_BLACKLIST SDK_UPDATE_URL SDK_EXT_TYPE SDK_RECRDEP_TASKS"
649 listvars = "SDKIMAGE_FEATURES BAD_RECOMMENDATIONS PACKAGE_EXCLUDE SDK_LOCAL_CONF_WHITELIST SDK_LOCAL_CONF_BLACKLIST SDK_INHERIT_BLACKLIST"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500650 return outputvars(sdkvars, listvars, d)
651
652
653def buildhistory_get_cmdline(d):
654 if sys.argv[0].endswith('bin/bitbake'):
655 bincmd = 'bitbake'
656 else:
657 bincmd = sys.argv[0]
658 return '%s %s' % (bincmd, ' '.join(sys.argv[1:]))
659
660
661buildhistory_single_commit() {
662 if [ "$3" = "" ] ; then
663 commitopts="${BUILDHISTORY_DIR}/ --allow-empty"
664 item="No changes"
665 else
666 commitopts="$3 metadata-revs"
667 item="$3"
668 fi
669 if [ "${BUILDHISTORY_BUILD_FAILURES}" = "0" ] ; then
670 result="succeeded"
671 else
672 result="failed"
673 fi
674 case ${BUILDHISTORY_BUILD_INTERRUPTED} in
675 1)
676 result="$result (interrupted)"
677 ;;
678 2)
679 result="$result (force interrupted)"
680 ;;
681 esac
682 commitmsgfile=`mktemp`
683 cat > $commitmsgfile << END
684$item: Build ${BUILDNAME} of ${DISTRO} ${DISTRO_VERSION} for machine ${MACHINE} on $2
685
686cmd: $1
687
688result: $result
689
690metadata revisions:
691END
692 cat ${BUILDHISTORY_DIR}/metadata-revs >> $commitmsgfile
693 git commit $commitopts -F $commitmsgfile --author "${BUILDHISTORY_COMMIT_AUTHOR}" > /dev/null
694 rm $commitmsgfile
695}
696
697buildhistory_commit() {
698 if [ ! -d ${BUILDHISTORY_DIR} ] ; then
699 # Code above that creates this dir never executed, so there can't be anything to commit
700 return
701 fi
702
703 # Create a machine-readable list of metadata revisions for each layer
704 cat > ${BUILDHISTORY_DIR}/metadata-revs <<END
705${@buildhistory_get_metadata_revs(d)}
706END
707
708 ( cd ${BUILDHISTORY_DIR}/
709 # Initialise the repo if necessary
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500710 if [ ! -e .git ] ; then
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500711 git init -q
712 else
713 git tag -f build-minus-3 build-minus-2 > /dev/null 2>&1 || true
714 git tag -f build-minus-2 build-minus-1 > /dev/null 2>&1 || true
715 git tag -f build-minus-1 > /dev/null 2>&1 || true
716 fi
717 # If the user hasn't set up their name/email, set some defaults
718 # just for this repo (otherwise the commit will fail with older
719 # versions of git)
720 if ! git config user.email > /dev/null ; then
721 git config --local user.email "buildhistory@${DISTRO}"
722 fi
723 if ! git config user.name > /dev/null ; then
724 git config --local user.name "buildhistory"
725 fi
726 # Check if there are new/changed files to commit (other than metadata-revs)
727 repostatus=`git status --porcelain | grep -v " metadata-revs$"`
728 HOSTNAME=`hostname 2>/dev/null || echo unknown`
729 CMDLINE="${@buildhistory_get_cmdline(d)}"
730 if [ "$repostatus" != "" ] ; then
731 git add -A .
732 # porcelain output looks like "?? packages/foo/bar"
733 # Ensure we commit metadata-revs with the first commit
734 for entry in `echo "$repostatus" | awk '{print $2}' | awk -F/ '{print $1}' | sort | uniq` ; do
735 buildhistory_single_commit "$CMDLINE" "$HOSTNAME" "$entry"
736 done
737 git gc --auto --quiet
738 else
739 buildhistory_single_commit "$CMDLINE" "$HOSTNAME"
740 fi
741 if [ "${BUILDHISTORY_PUSH_REPO}" != "" ] ; then
742 git push -q ${BUILDHISTORY_PUSH_REPO}
743 fi) || true
744}
745
746python buildhistory_eventhandler() {
747 if e.data.getVar('BUILDHISTORY_FEATURES', True).strip():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500748 reset = e.data.getVar("BUILDHISTORY_RESET", True)
749 olddir = e.data.getVar("BUILDHISTORY_OLD_DIR", True)
750 if isinstance(e, bb.event.BuildStarted):
751 if reset:
752 import shutil
753 # Clean up after potentially interrupted build.
754 if os.path.isdir(olddir):
755 shutil.rmtree(olddir)
756 rootdir = e.data.getVar("BUILDHISTORY_DIR", True)
757 entries = [ x for x in os.listdir(rootdir) if not x.startswith('.') ]
758 bb.utils.mkdirhier(olddir)
759 for entry in entries:
760 os.rename(os.path.join(rootdir, entry),
761 os.path.join(olddir, entry))
762 elif isinstance(e, bb.event.BuildCompleted):
763 if reset:
764 import shutil
765 shutil.rmtree(olddir)
766 if e.data.getVar("BUILDHISTORY_COMMIT", True) == "1":
767 bb.note("Writing buildhistory")
768 localdata = bb.data.createCopy(e.data)
769 localdata.setVar('BUILDHISTORY_BUILD_FAILURES', str(e._failures))
770 interrupted = getattr(e, '_interrupted', 0)
771 localdata.setVar('BUILDHISTORY_BUILD_INTERRUPTED', str(interrupted))
772 bb.build.exec_func("buildhistory_commit", localdata)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500773}
774
775addhandler buildhistory_eventhandler
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500776buildhistory_eventhandler[eventmask] = "bb.event.BuildCompleted bb.event.BuildStarted"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500777
778
779# FIXME this ought to be moved into the fetcher
780def _get_srcrev_values(d):
781 """
782 Return the version strings for the current recipe
783 """
784
785 scms = []
786 fetcher = bb.fetch.Fetch(d.getVar('SRC_URI', True).split(), d)
787 urldata = fetcher.ud
788 for u in urldata:
789 if urldata[u].method.supports_srcrev():
790 scms.append(u)
791
792 autoinc_templ = 'AUTOINC+'
793 dict_srcrevs = {}
794 dict_tag_srcrevs = {}
795 for scm in scms:
796 ud = urldata[scm]
797 for name in ud.names:
798 try:
799 rev = ud.method.sortable_revision(ud, d, name)
800 except TypeError:
801 # support old bitbake versions
802 rev = ud.method.sortable_revision(scm, ud, d, name)
803 # Clean this up when we next bump bitbake version
804 if type(rev) != str:
805 autoinc, rev = rev
806 elif rev.startswith(autoinc_templ):
807 rev = rev[len(autoinc_templ):]
808 dict_srcrevs[name] = rev
809 if 'tag' in ud.parm:
810 tag = ud.parm['tag'];
811 key = name+'_'+tag
812 dict_tag_srcrevs[key] = rev
813 return (dict_srcrevs, dict_tag_srcrevs)
814
815do_fetch[postfuncs] += "write_srcrev"
816do_fetch[vardepsexclude] += "write_srcrev"
817python write_srcrev() {
818 pkghistdir = d.getVar('BUILDHISTORY_DIR_PACKAGE', True)
819 srcrevfile = os.path.join(pkghistdir, 'latest_srcrev')
820
821 srcrevs, tag_srcrevs = _get_srcrev_values(d)
822 if srcrevs:
823 if not os.path.exists(pkghistdir):
824 bb.utils.mkdirhier(pkghistdir)
825 old_tag_srcrevs = {}
826 if os.path.exists(srcrevfile):
827 with open(srcrevfile) as f:
828 for line in f:
829 if line.startswith('# tag_'):
830 key, value = line.split("=", 1)
831 key = key.replace('# tag_', '').strip()
832 value = value.replace('"', '').strip()
833 old_tag_srcrevs[key] = value
834 with open(srcrevfile, 'w') as f:
835 orig_srcrev = d.getVar('SRCREV', False) or 'INVALID'
836 if orig_srcrev != 'INVALID':
837 f.write('# SRCREV = "%s"\n' % orig_srcrev)
838 if len(srcrevs) > 1:
839 for name, srcrev in srcrevs.items():
840 orig_srcrev = d.getVar('SRCREV_%s' % name, False)
841 if orig_srcrev:
842 f.write('# SRCREV_%s = "%s"\n' % (name, orig_srcrev))
843 f.write('SRCREV_%s = "%s"\n' % (name, srcrev))
844 else:
845 f.write('SRCREV = "%s"\n' % srcrevs.itervalues().next())
846 if len(tag_srcrevs) > 0:
847 for name, srcrev in tag_srcrevs.items():
848 f.write('# tag_%s = "%s"\n' % (name, srcrev))
849 if name in old_tag_srcrevs and old_tag_srcrevs[name] != srcrev:
850 pkg = d.getVar('PN', True)
851 bb.warn("Revision for tag %s in package %s was changed since last build (from %s to %s)" % (name, pkg, old_tag_srcrevs[name], srcrev))
852
853 else:
854 if os.path.exists(srcrevfile):
855 os.remove(srcrevfile)
856}