blob: 3a5bc2c3e3cbef6e1a515d32deac7cec0fa811b6 [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
Patrick Williamsc0f7c042017-02-23 20:41:17 -060060PATCH_GIT_USER_EMAIL ?= "buildhistory@oe"
61PATCH_GIT_USER_NAME ?= "OpenEmbedded"
62
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050063#
64# Write out metadata about this package for comparison when writing future packages
Patrick Williamsc124f4f2015-09-15 14:41:29 -050065#
66python buildhistory_emit_pkghistory() {
67 if not d.getVar('BB_CURRENTTASK', True) in ['packagedata', 'packagedata_setscene']:
68 return 0
69
70 if not "package" in (d.getVar('BUILDHISTORY_FEATURES', True) or "").split():
71 return 0
72
73 import re
74 import json
75 import errno
76
77 pkghistdir = d.getVar('BUILDHISTORY_DIR_PACKAGE', True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050078 oldpkghistdir = d.getVar('BUILDHISTORY_OLD_DIR_PACKAGE', True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050079
80 class RecipeInfo:
81 def __init__(self, name):
82 self.name = name
83 self.pe = "0"
84 self.pv = "0"
85 self.pr = "r0"
86 self.depends = ""
87 self.packages = ""
88 self.srcrev = ""
89
90
91 class PackageInfo:
92 def __init__(self, name):
93 self.name = name
94 self.pe = "0"
95 self.pv = "0"
96 self.pr = "r0"
97 # pkg/pkge/pkgv/pkgr should be empty because we want to be able to default them
98 self.pkg = ""
99 self.pkge = ""
100 self.pkgv = ""
101 self.pkgr = ""
102 self.size = 0
103 self.depends = ""
104 self.rprovides = ""
105 self.rdepends = ""
106 self.rrecommends = ""
107 self.rsuggests = ""
108 self.rreplaces = ""
109 self.rconflicts = ""
110 self.files = ""
111 self.filelist = ""
112 # Variables that need to be written to their own separate file
113 self.filevars = dict.fromkeys(['pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm'])
114
115 # Should check PACKAGES here to see if anything removed
116
117 def readPackageInfo(pkg, histfile):
118 pkginfo = PackageInfo(pkg)
119 with open(histfile, "r") as f:
120 for line in f:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500121 lns = line.split('=', 1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500122 name = lns[0].strip()
123 value = lns[1].strip(" \t\r\n").strip('"')
124 if name == "PE":
125 pkginfo.pe = value
126 elif name == "PV":
127 pkginfo.pv = value
128 elif name == "PR":
129 pkginfo.pr = value
130 elif name == "PKG":
131 pkginfo.pkg = value
132 elif name == "PKGE":
133 pkginfo.pkge = value
134 elif name == "PKGV":
135 pkginfo.pkgv = value
136 elif name == "PKGR":
137 pkginfo.pkgr = value
138 elif name == "RPROVIDES":
139 pkginfo.rprovides = value
140 elif name == "RDEPENDS":
141 pkginfo.rdepends = value
142 elif name == "RRECOMMENDS":
143 pkginfo.rrecommends = value
144 elif name == "RSUGGESTS":
145 pkginfo.rsuggests = value
146 elif name == "RREPLACES":
147 pkginfo.rreplaces = value
148 elif name == "RCONFLICTS":
149 pkginfo.rconflicts = value
150 elif name == "PKGSIZE":
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600151 pkginfo.size = int(value)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500152 elif name == "FILES":
153 pkginfo.files = value
154 elif name == "FILELIST":
155 pkginfo.filelist = value
156 # Apply defaults
157 if not pkginfo.pkg:
158 pkginfo.pkg = pkginfo.name
159 if not pkginfo.pkge:
160 pkginfo.pkge = pkginfo.pe
161 if not pkginfo.pkgv:
162 pkginfo.pkgv = pkginfo.pv
163 if not pkginfo.pkgr:
164 pkginfo.pkgr = pkginfo.pr
165 return pkginfo
166
167 def getlastpkgversion(pkg):
168 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500169 histfile = os.path.join(oldpkghistdir, pkg, "latest")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500170 return readPackageInfo(pkg, histfile)
171 except EnvironmentError:
172 return None
173
174 def sortpkglist(string):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500175 pkgiter = re.finditer(r'[a-zA-Z0-9.+-]+( \([><=]+[^)]+\))?', string, 0)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500176 pkglist = [p.group(0) for p in pkgiter]
177 pkglist.sort()
178 return ' '.join(pkglist)
179
180 def sortlist(string):
181 items = string.split(' ')
182 items.sort()
183 return ' '.join(items)
184
185 pn = d.getVar('PN', True)
186 pe = d.getVar('PE', True) or "0"
187 pv = d.getVar('PV', True)
188 pr = d.getVar('PR', True)
189
190 pkgdata_dir = d.getVar('PKGDATA_DIR', True)
191 packages = ""
192 try:
193 with open(os.path.join(pkgdata_dir, pn)) as f:
194 for line in f.readlines():
195 if line.startswith('PACKAGES: '):
196 packages = oe.utils.squashspaces(line.split(': ', 1)[1])
197 break
198 except IOError as e:
199 if e.errno == errno.ENOENT:
200 # Probably a -cross recipe, just ignore
201 return 0
202 else:
203 raise
204
205 packagelist = packages.split()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500206 preserve = d.getVar('BUILDHISTORY_PRESERVE', True).split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500207 if not os.path.exists(pkghistdir):
208 bb.utils.mkdirhier(pkghistdir)
209 else:
210 # Remove files for packages that no longer exist
211 for item in os.listdir(pkghistdir):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500212 if item not in preserve:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500213 if item not in packagelist:
214 itempath = os.path.join(pkghistdir, item)
215 if os.path.isdir(itempath):
216 for subfile in os.listdir(itempath):
217 os.unlink(os.path.join(itempath, subfile))
218 os.rmdir(itempath)
219 else:
220 os.unlink(itempath)
221
222 rcpinfo = RecipeInfo(pn)
223 rcpinfo.pe = pe
224 rcpinfo.pv = pv
225 rcpinfo.pr = pr
226 rcpinfo.depends = sortlist(oe.utils.squashspaces(d.getVar('DEPENDS', True) or ""))
227 rcpinfo.packages = packages
228 write_recipehistory(rcpinfo, d)
229
230 pkgdest = d.getVar('PKGDEST', True)
231 for pkg in packagelist:
232 pkgdata = {}
233 with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
234 for line in f.readlines():
235 item = line.rstrip('\n').split(': ', 1)
236 key = item[0]
237 if key.endswith('_' + pkg):
238 key = key[:-len(pkg)-1]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600239 pkgdata[key] = item[1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500240
241 pkge = pkgdata.get('PKGE', '0')
242 pkgv = pkgdata['PKGV']
243 pkgr = pkgdata['PKGR']
244 #
245 # Find out what the last version was
246 # Make sure the version did not decrease
247 #
248 lastversion = getlastpkgversion(pkg)
249 if lastversion:
250 last_pkge = lastversion.pkge
251 last_pkgv = lastversion.pkgv
252 last_pkgr = lastversion.pkgr
253 r = bb.utils.vercmp((pkge, pkgv, pkgr), (last_pkge, last_pkgv, last_pkgr))
254 if r < 0:
255 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)
256 package_qa_handle_error("version-going-backwards", msg, d)
257
258 pkginfo = PackageInfo(pkg)
259 # Apparently the version can be different on a per-package basis (see Python)
260 pkginfo.pe = pkgdata.get('PE', '0')
261 pkginfo.pv = pkgdata['PV']
262 pkginfo.pr = pkgdata['PR']
263 pkginfo.pkg = pkgdata['PKG']
264 pkginfo.pkge = pkge
265 pkginfo.pkgv = pkgv
266 pkginfo.pkgr = pkgr
267 pkginfo.rprovides = sortpkglist(oe.utils.squashspaces(pkgdata.get('RPROVIDES', "")))
268 pkginfo.rdepends = sortpkglist(oe.utils.squashspaces(pkgdata.get('RDEPENDS', "")))
269 pkginfo.rrecommends = sortpkglist(oe.utils.squashspaces(pkgdata.get('RRECOMMENDS', "")))
270 pkginfo.rsuggests = sortpkglist(oe.utils.squashspaces(pkgdata.get('RSUGGESTS', "")))
271 pkginfo.rreplaces = sortpkglist(oe.utils.squashspaces(pkgdata.get('RREPLACES', "")))
272 pkginfo.rconflicts = sortpkglist(oe.utils.squashspaces(pkgdata.get('RCONFLICTS', "")))
273 pkginfo.files = oe.utils.squashspaces(pkgdata.get('FILES', ""))
274 for filevar in pkginfo.filevars:
275 pkginfo.filevars[filevar] = pkgdata.get(filevar, "")
276
277 # Gather information about packaged files
278 val = pkgdata.get('FILES_INFO', '')
279 dictval = json.loads(val)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600280 filelist = list(dictval.keys())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500281 filelist.sort()
282 pkginfo.filelist = " ".join(filelist)
283
284 pkginfo.size = int(pkgdata['PKGSIZE'])
285
286 write_pkghistory(pkginfo, d)
287
288 # Create files-in-<package-name>.txt files containing a list of files of each recipe's package
289 bb.build.exec_func("buildhistory_list_pkg_files", d)
290}
291
292
293def write_recipehistory(rcpinfo, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500294 bb.debug(2, "Writing recipe history")
295
296 pkghistdir = d.getVar('BUILDHISTORY_DIR_PACKAGE', True)
297
298 infofile = os.path.join(pkghistdir, "latest")
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600299 with open(infofile, "w") as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500300 if rcpinfo.pe != "0":
301 f.write(u"PE = %s\n" % rcpinfo.pe)
302 f.write(u"PV = %s\n" % rcpinfo.pv)
303 f.write(u"PR = %s\n" % rcpinfo.pr)
304 f.write(u"DEPENDS = %s\n" % rcpinfo.depends)
305 f.write(u"PACKAGES = %s\n" % rcpinfo.packages)
306
307
308def write_pkghistory(pkginfo, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500309 bb.debug(2, "Writing package history for package %s" % pkginfo.name)
310
311 pkghistdir = d.getVar('BUILDHISTORY_DIR_PACKAGE', True)
312
313 pkgpath = os.path.join(pkghistdir, pkginfo.name)
314 if not os.path.exists(pkgpath):
315 bb.utils.mkdirhier(pkgpath)
316
317 infofile = os.path.join(pkgpath, "latest")
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600318 with open(infofile, "w") as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319 if pkginfo.pe != "0":
320 f.write(u"PE = %s\n" % pkginfo.pe)
321 f.write(u"PV = %s\n" % pkginfo.pv)
322 f.write(u"PR = %s\n" % pkginfo.pr)
323
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600324 if pkginfo.pkg != pkginfo.name:
325 f.write(u"PKG = %s\n" % pkginfo.pkg)
326 if pkginfo.pkge != pkginfo.pe:
327 f.write(u"PKGE = %s\n" % pkginfo.pkge)
328 if pkginfo.pkgv != pkginfo.pv:
329 f.write(u"PKGV = %s\n" % pkginfo.pkgv)
330 if pkginfo.pkgr != pkginfo.pr:
331 f.write(u"PKGR = %s\n" % pkginfo.pkgr)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500332 f.write(u"RPROVIDES = %s\n" % pkginfo.rprovides)
333 f.write(u"RDEPENDS = %s\n" % pkginfo.rdepends)
334 f.write(u"RRECOMMENDS = %s\n" % pkginfo.rrecommends)
335 if pkginfo.rsuggests:
336 f.write(u"RSUGGESTS = %s\n" % pkginfo.rsuggests)
337 if pkginfo.rreplaces:
338 f.write(u"RREPLACES = %s\n" % pkginfo.rreplaces)
339 if pkginfo.rconflicts:
340 f.write(u"RCONFLICTS = %s\n" % pkginfo.rconflicts)
341 f.write(u"PKGSIZE = %d\n" % pkginfo.size)
342 f.write(u"FILES = %s\n" % pkginfo.files)
343 f.write(u"FILELIST = %s\n" % pkginfo.filelist)
344
345 for filevar in pkginfo.filevars:
346 filevarpath = os.path.join(pkgpath, "latest.%s" % filevar)
347 val = pkginfo.filevars[filevar]
348 if val:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600349 with open(filevarpath, "w") as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500350 f.write(val)
351 else:
352 if os.path.exists(filevarpath):
353 os.unlink(filevarpath)
354
355#
356# rootfs_type can be: image, sdk_target, sdk_host
357#
358def buildhistory_list_installed(d, rootfs_type="image"):
359 from oe.rootfs import image_list_installed_packages
360 from oe.sdk import sdk_list_installed_packages
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500361 from oe.utils import format_pkg_list
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500362
363 process_list = [('file', 'bh_installed_pkgs.txt'),\
364 ('deps', 'bh_installed_pkgs_deps.txt')]
365
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500366 if rootfs_type == "image":
367 pkgs = image_list_installed_packages(d)
368 else:
369 pkgs = sdk_list_installed_packages(d, rootfs_type == "sdk_target")
370
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500371 for output_type, output_file in process_list:
372 output_file_full = os.path.join(d.getVar('WORKDIR', True), output_file)
373
374 with open(output_file_full, 'w') as output:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500375 output.write(format_pkg_list(pkgs, output_type))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500376
377python buildhistory_list_installed_image() {
378 buildhistory_list_installed(d)
379}
380
381python buildhistory_list_installed_sdk_target() {
382 buildhistory_list_installed(d, "sdk_target")
383}
384
385python buildhistory_list_installed_sdk_host() {
386 buildhistory_list_installed(d, "sdk_host")
387}
388
389buildhistory_get_installed() {
390 mkdir -p $1
391
392 # Get list of installed packages
393 pkgcache="$1/installed-packages.tmp"
394 cat ${WORKDIR}/bh_installed_pkgs.txt | sort > $pkgcache && rm ${WORKDIR}/bh_installed_pkgs.txt
395
396 cat $pkgcache | awk '{ print $1 }' > $1/installed-package-names.txt
397 if [ -s $pkgcache ] ; then
398 cat $pkgcache | awk '{ print $2 }' | xargs -n1 basename > $1/installed-packages.txt
399 else
400 printf "" > $1/installed-packages.txt
401 fi
402
403 # Produce dependency graph
404 # First, quote each name to handle characters that cause issues for dot
405 sed 's:\([^| ]*\):"\1":g' ${WORKDIR}/bh_installed_pkgs_deps.txt > $1/depends.tmp && \
406 rm ${WORKDIR}/bh_installed_pkgs_deps.txt
407 # Change delimiter from pipe to -> and set style for recommend lines
408 sed -i -e 's:|: -> :' -e 's:"\[REC\]":[style=dotted]:' -e 's:$:;:' $1/depends.tmp
409 # Add header, sorted and de-duped contents and footer and then delete the temp file
410 printf "digraph depends {\n node [shape=plaintext]\n" > $1/depends.dot
411 cat $1/depends.tmp | sort | uniq >> $1/depends.dot
412 echo "}" >> $1/depends.dot
413 rm $1/depends.tmp
414
415 # Produce installed package sizes list
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500416 oe-pkgdata-util -p ${PKGDATA_DIR} read-value "PKGSIZE" -n -f $pkgcache > $1/installed-package-sizes.tmp
417 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 -0500418 rm $1/installed-package-sizes.tmp
419
420 # We're now done with the cache, delete it
421 rm $pkgcache
422
423 if [ "$2" != "sdk" ] ; then
424 # Produce some cut-down graphs (for readability)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500425 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 -0500426 grep -v libc6 $1/depends-nokernel.dot | grep -v libgcc > $1/depends-nokernel-nolibc.dot
427 grep -v update- $1/depends-nokernel-nolibc.dot > $1/depends-nokernel-nolibc-noupdate.dot
428 grep -v kernel-module $1/depends-nokernel-nolibc-noupdate.dot > $1/depends-nokernel-nolibc-noupdate-nomodules.dot
429 fi
430
431 # add complementary package information
432 if [ -e ${WORKDIR}/complementary_pkgs.txt ]; then
433 cp ${WORKDIR}/complementary_pkgs.txt $1
434 fi
435}
436
437buildhistory_get_image_installed() {
438 # Anything requiring the use of the packaging system should be done in here
439 # in case the packaging files are going to be removed for this image
440
441 if [ "${@bb.utils.contains('BUILDHISTORY_FEATURES', 'image', '1', '0', d)}" = "0" ] ; then
442 return
443 fi
444
445 buildhistory_get_installed ${BUILDHISTORY_DIR_IMAGE}
446}
447
448buildhistory_get_sdk_installed() {
449 # Anything requiring the use of the packaging system should be done in here
450 # in case the packaging files are going to be removed for this SDK
451
452 if [ "${@bb.utils.contains('BUILDHISTORY_FEATURES', 'sdk', '1', '0', d)}" = "0" ] ; then
453 return
454 fi
455
456 buildhistory_get_installed ${BUILDHISTORY_DIR_SDK}/$1 sdk
457}
458
459buildhistory_get_sdk_installed_host() {
460 buildhistory_get_sdk_installed host
461}
462
463buildhistory_get_sdk_installed_target() {
464 buildhistory_get_sdk_installed target
465}
466
467buildhistory_list_files() {
468 # List the files in the specified directory, but exclude date/time etc.
469 # This awk script is somewhat messy, but handles where the size is not printed for device files under pseudo
470 if [ "$3" = "fakeroot" ] ; then
471 ( cd $1 && ${FAKEROOTENV} ${FAKEROOTCMD} find . ! -path . -printf "%M %-10u %-10g %10s %p -> %l\n" | sort -k5 | sed 's/ * -> $//' > $2 )
472 else
473 ( cd $1 && find . ! -path . -printf "%M %-10u %-10g %10s %p -> %l\n" | sort -k5 | sed 's/ * -> $//' > $2 )
474 fi
475}
476
477buildhistory_list_pkg_files() {
478 # Create individual files-in-package for each recipe's package
479 for pkgdir in $(find ${PKGDEST}/* -maxdepth 0 -type d); do
480 pkgname=$(basename $pkgdir)
481 outfolder="${BUILDHISTORY_DIR_PACKAGE}/$pkgname"
482 outfile="$outfolder/files-in-package.txt"
483 # Make sure the output folder exists so we can create the file
484 if [ ! -d $outfolder ] ; then
485 bbdebug 2 "Folder $outfolder does not exist, file $outfile not created"
486 continue
487 fi
488 buildhistory_list_files $pkgdir $outfile fakeroot
489 done
490}
491
492buildhistory_get_imageinfo() {
493 if [ "${@bb.utils.contains('BUILDHISTORY_FEATURES', 'image', '1', '0', d)}" = "0" ] ; then
494 return
495 fi
496
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500497 mkdir -p ${BUILDHISTORY_DIR_IMAGE}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500498 buildhistory_list_files ${IMAGE_ROOTFS} ${BUILDHISTORY_DIR_IMAGE}/files-in-image.txt
499
500 # Collect files requested in BUILDHISTORY_IMAGE_FILES
501 rm -rf ${BUILDHISTORY_DIR_IMAGE}/image-files
502 for f in ${BUILDHISTORY_IMAGE_FILES}; do
503 if [ -f ${IMAGE_ROOTFS}/$f ] ; then
504 mkdir -p ${BUILDHISTORY_DIR_IMAGE}/image-files/`dirname $f`
505 cp ${IMAGE_ROOTFS}/$f ${BUILDHISTORY_DIR_IMAGE}/image-files/$f
506 fi
507 done
508
509 # Record some machine-readable meta-information about the image
510 printf "" > ${BUILDHISTORY_DIR_IMAGE}/image-info.txt
511 cat >> ${BUILDHISTORY_DIR_IMAGE}/image-info.txt <<END
512${@buildhistory_get_imagevars(d)}
513END
514 imagesize=`du -ks ${IMAGE_ROOTFS} | awk '{ print $1 }'`
515 echo "IMAGESIZE = $imagesize" >> ${BUILDHISTORY_DIR_IMAGE}/image-info.txt
516
517 # Add some configuration information
518 echo "${MACHINE}: ${IMAGE_BASENAME} configured for ${DISTRO} ${DISTRO_VERSION}" > ${BUILDHISTORY_DIR_IMAGE}/build-id.txt
519
520 cat >> ${BUILDHISTORY_DIR_IMAGE}/build-id.txt <<END
521${@buildhistory_get_build_id(d)}
522END
523}
524
525buildhistory_get_sdkinfo() {
526 if [ "${@bb.utils.contains('BUILDHISTORY_FEATURES', 'sdk', '1', '0', d)}" = "0" ] ; then
527 return
528 fi
529
530 buildhistory_list_files ${SDK_OUTPUT} ${BUILDHISTORY_DIR_SDK}/files-in-sdk.txt
531
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500532 # Collect files requested in BUILDHISTORY_SDK_FILES
533 rm -rf ${BUILDHISTORY_DIR_SDK}/sdk-files
534 for f in ${BUILDHISTORY_SDK_FILES}; do
535 if [ -f ${SDK_OUTPUT}/${SDKPATH}/$f ] ; then
536 mkdir -p ${BUILDHISTORY_DIR_SDK}/sdk-files/`dirname $f`
537 cp ${SDK_OUTPUT}/${SDKPATH}/$f ${BUILDHISTORY_DIR_SDK}/sdk-files/$f
538 fi
539 done
540
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500541 # Record some machine-readable meta-information about the SDK
542 printf "" > ${BUILDHISTORY_DIR_SDK}/sdk-info.txt
543 cat >> ${BUILDHISTORY_DIR_SDK}/sdk-info.txt <<END
544${@buildhistory_get_sdkvars(d)}
545END
546 sdksize=`du -ks ${SDK_OUTPUT} | awk '{ print $1 }'`
547 echo "SDKSIZE = $sdksize" >> ${BUILDHISTORY_DIR_SDK}/sdk-info.txt
548}
549
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500550python buildhistory_get_extra_sdkinfo() {
551 import operator
552 import math
553 if d.getVar('BB_CURRENTTASK', True) == 'populate_sdk_ext':
554 tasksizes = {}
555 filesizes = {}
556 for root, _, files in os.walk(d.expand('${SDK_OUTPUT}/${SDKPATH}/sstate-cache')):
557 for fn in files:
558 if fn.endswith('.tgz'):
559 fsize = int(math.ceil(float(os.path.getsize(os.path.join(root, fn))) / 1024))
560 task = fn.rsplit(':', 1)[1].split('_', 1)[1].split('.')[0]
561 origtotal = tasksizes.get(task, 0)
562 tasksizes[task] = origtotal + fsize
563 filesizes[fn] = fsize
564 with open(d.expand('${BUILDHISTORY_DIR_SDK}/sstate-package-sizes.txt'), 'w') as f:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600565 filesizes_sorted = sorted(filesizes.items(), key=operator.itemgetter(1, 0), reverse=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500566 for fn, size in filesizes_sorted:
567 f.write('%10d KiB %s\n' % (size, fn))
568 with open(d.expand('${BUILDHISTORY_DIR_SDK}/sstate-task-sizes.txt'), 'w') as f:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600569 tasksizes_sorted = sorted(tasksizes.items(), key=operator.itemgetter(1, 0), reverse=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500570 for task, size in tasksizes_sorted:
571 f.write('%10d KiB %s\n' % (size, task))
572}
573
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500574# By using ROOTFS_POSTUNINSTALL_COMMAND we get in after uninstallation of
575# unneeded packages but before the removal of packaging files
576ROOTFS_POSTUNINSTALL_COMMAND += " buildhistory_list_installed_image ;\
577 buildhistory_get_image_installed ; "
578
579IMAGE_POSTPROCESS_COMMAND += " buildhistory_get_imageinfo ; "
580
581# We want these to be the last run so that we get called after complementary package installation
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500582POPULATE_SDK_POST_TARGET_COMMAND_append = " buildhistory_list_installed_sdk_target;"
583POPULATE_SDK_POST_TARGET_COMMAND_append = " buildhistory_get_sdk_installed_target;"
584POPULATE_SDK_POST_TARGET_COMMAND[vardepvalueexclude] .= "| buildhistory_list_installed_sdk_target;| buildhistory_get_sdk_installed_target;"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500585
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500586POPULATE_SDK_POST_HOST_COMMAND_append = " buildhistory_list_installed_sdk_host;"
587POPULATE_SDK_POST_HOST_COMMAND_append = " buildhistory_get_sdk_installed_host;"
588POPULATE_SDK_POST_HOST_COMMAND[vardepvalueexclude] .= "| buildhistory_list_installed_sdk_host;| buildhistory_get_sdk_installed_host;"
589
590SDK_POSTPROCESS_COMMAND_append = " buildhistory_get_sdkinfo ; buildhistory_get_extra_sdkinfo; "
591SDK_POSTPROCESS_COMMAND[vardepvalueexclude] .= "| buildhistory_get_sdkinfo ; buildhistory_get_extra_sdkinfo; "
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500592
593def buildhistory_get_build_id(d):
594 if d.getVar('BB_WORKERCONTEXT', True) != '1':
595 return ""
596 localdata = bb.data.createCopy(d)
597 bb.data.update_data(localdata)
598 statuslines = []
599 for func in oe.data.typed_value('BUILDCFG_FUNCS', localdata):
600 g = globals()
601 if func not in g:
602 bb.warn("Build configuration function '%s' does not exist" % func)
603 else:
604 flines = g[func](localdata)
605 if flines:
606 statuslines.extend(flines)
607
608 statusheader = d.getVar('BUILDCFG_HEADER', True)
609 return('\n%s\n%s\n' % (statusheader, '\n'.join(statuslines)))
610
611def buildhistory_get_metadata_revs(d):
612 # We want an easily machine-readable format here, so get_layers_branch_rev isn't quite what we want
613 layers = (d.getVar("BBLAYERS", True) or "").split()
614 medadata_revs = ["%-17s = %s:%s" % (os.path.basename(i), \
615 base_get_metadata_git_branch(i, None).strip(), \
616 base_get_metadata_git_revision(i, None)) \
617 for i in layers]
618 return '\n'.join(medadata_revs)
619
620def outputvars(vars, listvars, d):
621 vars = vars.split()
622 listvars = listvars.split()
623 ret = ""
624 for var in vars:
625 value = d.getVar(var, True) or ""
626 if var in listvars:
627 # Squash out spaces
628 value = oe.utils.squashspaces(value)
629 ret += "%s = %s\n" % (var, value)
630 return ret.rstrip('\n')
631
632def buildhistory_get_imagevars(d):
633 if d.getVar('BB_WORKERCONTEXT', True) != '1':
634 return ""
635 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"
636 listvars = "USER_CLASSES IMAGE_CLASSES IMAGE_FEATURES IMAGE_LINGUAS IMAGE_INSTALL BAD_RECOMMENDATIONS PACKAGE_EXCLUDE"
637 return outputvars(imagevars, listvars, d)
638
639def buildhistory_get_sdkvars(d):
640 if d.getVar('BB_WORKERCONTEXT', True) != '1':
641 return ""
642 sdkvars = "DISTRO DISTRO_VERSION SDK_NAME SDK_VERSION SDKMACHINE SDKIMAGE_FEATURES BAD_RECOMMENDATIONS NO_RECOMMENDATIONS PACKAGE_EXCLUDE"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500643 if d.getVar('BB_CURRENTTASK', True) == 'populate_sdk_ext':
644 # Extensible SDK uses some additional variables
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600645 sdkvars += " SDK_LOCAL_CONF_WHITELIST SDK_LOCAL_CONF_BLACKLIST SDK_INHERIT_BLACKLIST SDK_UPDATE_URL SDK_EXT_TYPE SDK_RECRDEP_TASKS SDK_INCLUDE_PKGDATA SDK_INCLUDE_TOOLCHAIN"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500646 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 -0500647 return outputvars(sdkvars, listvars, d)
648
649
650def buildhistory_get_cmdline(d):
651 if sys.argv[0].endswith('bin/bitbake'):
652 bincmd = 'bitbake'
653 else:
654 bincmd = sys.argv[0]
655 return '%s %s' % (bincmd, ' '.join(sys.argv[1:]))
656
657
658buildhistory_single_commit() {
659 if [ "$3" = "" ] ; then
660 commitopts="${BUILDHISTORY_DIR}/ --allow-empty"
661 item="No changes"
662 else
663 commitopts="$3 metadata-revs"
664 item="$3"
665 fi
666 if [ "${BUILDHISTORY_BUILD_FAILURES}" = "0" ] ; then
667 result="succeeded"
668 else
669 result="failed"
670 fi
671 case ${BUILDHISTORY_BUILD_INTERRUPTED} in
672 1)
673 result="$result (interrupted)"
674 ;;
675 2)
676 result="$result (force interrupted)"
677 ;;
678 esac
679 commitmsgfile=`mktemp`
680 cat > $commitmsgfile << END
681$item: Build ${BUILDNAME} of ${DISTRO} ${DISTRO_VERSION} for machine ${MACHINE} on $2
682
683cmd: $1
684
685result: $result
686
687metadata revisions:
688END
689 cat ${BUILDHISTORY_DIR}/metadata-revs >> $commitmsgfile
690 git commit $commitopts -F $commitmsgfile --author "${BUILDHISTORY_COMMIT_AUTHOR}" > /dev/null
691 rm $commitmsgfile
692}
693
694buildhistory_commit() {
695 if [ ! -d ${BUILDHISTORY_DIR} ] ; then
696 # Code above that creates this dir never executed, so there can't be anything to commit
697 return
698 fi
699
700 # Create a machine-readable list of metadata revisions for each layer
701 cat > ${BUILDHISTORY_DIR}/metadata-revs <<END
702${@buildhistory_get_metadata_revs(d)}
703END
704
705 ( cd ${BUILDHISTORY_DIR}/
706 # Initialise the repo if necessary
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500707 if [ ! -e .git ] ; then
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500708 git init -q
709 else
710 git tag -f build-minus-3 build-minus-2 > /dev/null 2>&1 || true
711 git tag -f build-minus-2 build-minus-1 > /dev/null 2>&1 || true
712 git tag -f build-minus-1 > /dev/null 2>&1 || true
713 fi
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600714
715 check_git_config
716
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500717 # Check if there are new/changed files to commit (other than metadata-revs)
718 repostatus=`git status --porcelain | grep -v " metadata-revs$"`
719 HOSTNAME=`hostname 2>/dev/null || echo unknown`
720 CMDLINE="${@buildhistory_get_cmdline(d)}"
721 if [ "$repostatus" != "" ] ; then
722 git add -A .
723 # porcelain output looks like "?? packages/foo/bar"
724 # Ensure we commit metadata-revs with the first commit
725 for entry in `echo "$repostatus" | awk '{print $2}' | awk -F/ '{print $1}' | sort | uniq` ; do
726 buildhistory_single_commit "$CMDLINE" "$HOSTNAME" "$entry"
727 done
728 git gc --auto --quiet
729 else
730 buildhistory_single_commit "$CMDLINE" "$HOSTNAME"
731 fi
732 if [ "${BUILDHISTORY_PUSH_REPO}" != "" ] ; then
733 git push -q ${BUILDHISTORY_PUSH_REPO}
734 fi) || true
735}
736
737python buildhistory_eventhandler() {
738 if e.data.getVar('BUILDHISTORY_FEATURES', True).strip():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500739 reset = e.data.getVar("BUILDHISTORY_RESET", True)
740 olddir = e.data.getVar("BUILDHISTORY_OLD_DIR", True)
741 if isinstance(e, bb.event.BuildStarted):
742 if reset:
743 import shutil
744 # Clean up after potentially interrupted build.
745 if os.path.isdir(olddir):
746 shutil.rmtree(olddir)
747 rootdir = e.data.getVar("BUILDHISTORY_DIR", True)
748 entries = [ x for x in os.listdir(rootdir) if not x.startswith('.') ]
749 bb.utils.mkdirhier(olddir)
750 for entry in entries:
751 os.rename(os.path.join(rootdir, entry),
752 os.path.join(olddir, entry))
753 elif isinstance(e, bb.event.BuildCompleted):
754 if reset:
755 import shutil
756 shutil.rmtree(olddir)
757 if e.data.getVar("BUILDHISTORY_COMMIT", True) == "1":
758 bb.note("Writing buildhistory")
759 localdata = bb.data.createCopy(e.data)
760 localdata.setVar('BUILDHISTORY_BUILD_FAILURES', str(e._failures))
761 interrupted = getattr(e, '_interrupted', 0)
762 localdata.setVar('BUILDHISTORY_BUILD_INTERRUPTED', str(interrupted))
763 bb.build.exec_func("buildhistory_commit", localdata)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500764}
765
766addhandler buildhistory_eventhandler
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500767buildhistory_eventhandler[eventmask] = "bb.event.BuildCompleted bb.event.BuildStarted"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500768
769
770# FIXME this ought to be moved into the fetcher
771def _get_srcrev_values(d):
772 """
773 Return the version strings for the current recipe
774 """
775
776 scms = []
777 fetcher = bb.fetch.Fetch(d.getVar('SRC_URI', True).split(), d)
778 urldata = fetcher.ud
779 for u in urldata:
780 if urldata[u].method.supports_srcrev():
781 scms.append(u)
782
783 autoinc_templ = 'AUTOINC+'
784 dict_srcrevs = {}
785 dict_tag_srcrevs = {}
786 for scm in scms:
787 ud = urldata[scm]
788 for name in ud.names:
789 try:
790 rev = ud.method.sortable_revision(ud, d, name)
791 except TypeError:
792 # support old bitbake versions
793 rev = ud.method.sortable_revision(scm, ud, d, name)
794 # Clean this up when we next bump bitbake version
795 if type(rev) != str:
796 autoinc, rev = rev
797 elif rev.startswith(autoinc_templ):
798 rev = rev[len(autoinc_templ):]
799 dict_srcrevs[name] = rev
800 if 'tag' in ud.parm:
801 tag = ud.parm['tag'];
802 key = name+'_'+tag
803 dict_tag_srcrevs[key] = rev
804 return (dict_srcrevs, dict_tag_srcrevs)
805
806do_fetch[postfuncs] += "write_srcrev"
807do_fetch[vardepsexclude] += "write_srcrev"
808python write_srcrev() {
809 pkghistdir = d.getVar('BUILDHISTORY_DIR_PACKAGE', True)
810 srcrevfile = os.path.join(pkghistdir, 'latest_srcrev')
811
812 srcrevs, tag_srcrevs = _get_srcrev_values(d)
813 if srcrevs:
814 if not os.path.exists(pkghistdir):
815 bb.utils.mkdirhier(pkghistdir)
816 old_tag_srcrevs = {}
817 if os.path.exists(srcrevfile):
818 with open(srcrevfile) as f:
819 for line in f:
820 if line.startswith('# tag_'):
821 key, value = line.split("=", 1)
822 key = key.replace('# tag_', '').strip()
823 value = value.replace('"', '').strip()
824 old_tag_srcrevs[key] = value
825 with open(srcrevfile, 'w') as f:
826 orig_srcrev = d.getVar('SRCREV', False) or 'INVALID'
827 if orig_srcrev != 'INVALID':
828 f.write('# SRCREV = "%s"\n' % orig_srcrev)
829 if len(srcrevs) > 1:
830 for name, srcrev in srcrevs.items():
831 orig_srcrev = d.getVar('SRCREV_%s' % name, False)
832 if orig_srcrev:
833 f.write('# SRCREV_%s = "%s"\n' % (name, orig_srcrev))
834 f.write('SRCREV_%s = "%s"\n' % (name, srcrev))
835 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600836 f.write('SRCREV = "%s"\n' % srcrevs.values())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500837 if len(tag_srcrevs) > 0:
838 for name, srcrev in tag_srcrevs.items():
839 f.write('# tag_%s = "%s"\n' % (name, srcrev))
840 if name in old_tag_srcrevs and old_tag_srcrevs[name] != srcrev:
841 pkg = d.getVar('PN', True)
842 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))
843
844 else:
845 if os.path.exists(srcrevfile):
846 os.remove(srcrevfile)
847}