diff options
author | Elliot Smith <elliot.smith@intel.com> | 2016-07-12 15:54:46 -0700 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2016-07-19 08:56:51 +0100 |
commit | 4125da7763ffc70cc77578c677bb7e5fc7ebaf57 (patch) | |
tree | 3c303f0070365d73217950b96ef234cfe9bcc022 /bitbake/lib/toaster | |
parent | e9808576daad137695bdedd0883ee0a35b7d870a (diff) | |
download | poky-4125da7763ffc70cc77578c677bb7e5fc7ebaf57.tar.gz |
bitbake: toaster: attach kernel artifacts to targets
The bzImage and modules files were previously attached to a build,
rather than to the target which produced them. This meant it was
not possible to determine which kernel artifact produced by a
build came from which target; which in turn made it difficult to
associate existing kernel artifact with targets when those
targets didn't produce artifacts (e.g. if the same machine + target
combination was built again and didn't produce a bzImage or modules
file because those files already existed).
By associating kernel artifacts with the target (via a new
TargetArtifactFile model), we make it possible to find all
the artifacts for a given machine + target combination. Then, in
cases where a build is completed but its targets don't produce
any artifacts, we can find a previous Target object with the same
machine + target and copy its artifacts to the targets for a
just-completed build.
Note that this doesn't cover SDK artifacts yet, which are still
retrieved in toaster.bbclass and show up as "Other artifacts",
lumped together for the whole build rather than by target.
[YOCTO #8556]
(Bitbake rev: 9b151416e428c2565a27d89116439f9a8d578e3d)
Signed-off-by: Elliot Smith <elliot.smith@intel.com>
Signed-off-by: bavery <brian.avery@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/toaster')
-rw-r--r-- | bitbake/lib/toaster/orm/migrations/0008_targetartifactfile.py | 23 | ||||
-rw-r--r-- | bitbake/lib/toaster/orm/models.py | 123 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/templates/builddashboard.html | 15 | ||||
-rwxr-xr-x | bitbake/lib/toaster/toastergui/views.py | 7 |
4 files changed, 165 insertions, 3 deletions
diff --git a/bitbake/lib/toaster/orm/migrations/0008_targetartifactfile.py b/bitbake/lib/toaster/orm/migrations/0008_targetartifactfile.py new file mode 100644 index 0000000000..9f1d9bf4ec --- /dev/null +++ b/bitbake/lib/toaster/orm/migrations/0008_targetartifactfile.py | |||
@@ -0,0 +1,23 @@ | |||
1 | # -*- coding: utf-8 -*- | ||
2 | from __future__ import unicode_literals | ||
3 | |||
4 | from django.db import migrations, models | ||
5 | |||
6 | |||
7 | class Migration(migrations.Migration): | ||
8 | |||
9 | dependencies = [ | ||
10 | ('orm', '0007_auto_20160523_1446'), | ||
11 | ] | ||
12 | |||
13 | operations = [ | ||
14 | migrations.CreateModel( | ||
15 | name='TargetArtifactFile', | ||
16 | fields=[ | ||
17 | ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), | ||
18 | ('file_name', models.FilePathField()), | ||
19 | ('file_size', models.IntegerField()), | ||
20 | ('target', models.ForeignKey(to='orm.Target')), | ||
21 | ], | ||
22 | ), | ||
23 | ] | ||
diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py index 40cdb9e5c5..9edbef3396 100644 --- a/bitbake/lib/toaster/orm/models.py +++ b/bitbake/lib/toaster/orm/models.py | |||
@@ -22,7 +22,7 @@ | |||
22 | from __future__ import unicode_literals | 22 | from __future__ import unicode_literals |
23 | 23 | ||
24 | from django.db import models, IntegrityError | 24 | from django.db import models, IntegrityError |
25 | from django.db.models import F, Q, Avg, Max, Sum | 25 | from django.db.models import F, Q, Avg, Max, Sum, Count |
26 | from django.utils import timezone | 26 | from django.utils import timezone |
27 | from django.utils.encoding import force_bytes | 27 | from django.utils.encoding import force_bytes |
28 | 28 | ||
@@ -438,7 +438,9 @@ class Build(models.Model): | |||
438 | 438 | ||
439 | def get_image_file_extensions(self): | 439 | def get_image_file_extensions(self): |
440 | """ | 440 | """ |
441 | Get list of file name extensions for images produced by this build | 441 | Get list of file name extensions for images produced by this build; |
442 | note that this is the actual list of extensions stored on Target objects | ||
443 | for this build, and not the value of IMAGE_FSTYPES. | ||
442 | """ | 444 | """ |
443 | extensions = [] | 445 | extensions = [] |
444 | 446 | ||
@@ -458,6 +460,15 @@ class Build(models.Model): | |||
458 | 460 | ||
459 | return ', '.join(extensions) | 461 | return ', '.join(extensions) |
460 | 462 | ||
463 | def get_image_fstypes(self): | ||
464 | """ | ||
465 | Get the IMAGE_FSTYPES variable value for this build as a de-duplicated | ||
466 | list of image file suffixes. | ||
467 | """ | ||
468 | image_fstypes = Variable.objects.get( | ||
469 | build=self, variable_name='IMAGE_FSTYPES').variable_value | ||
470 | return list(set(re.split(r' {1,}', image_fstypes))) | ||
471 | |||
461 | def get_sorted_target_list(self): | 472 | def get_sorted_target_list(self): |
462 | tgts = Target.objects.filter(build_id = self.id).order_by( 'target' ); | 473 | tgts = Target.objects.filter(build_id = self.id).order_by( 'target' ); |
463 | return( tgts ); | 474 | return( tgts ); |
@@ -612,6 +623,114 @@ class Target(models.Model): | |||
612 | def __unicode__(self): | 623 | def __unicode__(self): |
613 | return self.target | 624 | return self.target |
614 | 625 | ||
626 | def get_similar_targets(self): | ||
627 | """ | ||
628 | Get targets for the same machine, task and target name | ||
629 | (e.g. 'core-image-minimal') from a successful build for this project | ||
630 | (but excluding this target). | ||
631 | |||
632 | Note that we look for targets built by this project because projects | ||
633 | can have different configurations from each other, and put their | ||
634 | artifacts in different directories. | ||
635 | """ | ||
636 | query = ~Q(pk=self.pk) & \ | ||
637 | Q(target=self.target) & \ | ||
638 | Q(build__machine=self.build.machine) & \ | ||
639 | Q(build__outcome=Build.SUCCEEDED) & \ | ||
640 | Q(build__project=self.build.project) | ||
641 | |||
642 | return Target.objects.filter(query) | ||
643 | |||
644 | def get_similar_target_with_image_files(self): | ||
645 | """ | ||
646 | Get the most recent similar target with Target_Image_Files associated | ||
647 | with it, for the purpose of cloning those files onto this target. | ||
648 | """ | ||
649 | similar_target = None | ||
650 | |||
651 | candidates = self.get_similar_targets() | ||
652 | if candidates.count() < 1: | ||
653 | return similar_target | ||
654 | |||
655 | task_subquery = Q(task=self.task) | ||
656 | |||
657 | # we can look for a 'build' task if this task is a 'populate_sdk_ext' | ||
658 | # task, as it will have created images; and vice versa; note that | ||
659 | # 'build' targets can have their task set to ''; | ||
660 | # also note that 'populate_sdk' does not produce image files | ||
661 | image_tasks = [ | ||
662 | '', # aka 'build' | ||
663 | 'build', | ||
664 | 'populate_sdk_ext' | ||
665 | ] | ||
666 | if self.task in image_tasks: | ||
667 | task_subquery = Q(task__in=image_tasks) | ||
668 | |||
669 | query = task_subquery & Q(num_files__gt=0) | ||
670 | |||
671 | # annotate with the count of files, to exclude any targets which | ||
672 | # don't have associated files | ||
673 | candidates = candidates.annotate( | ||
674 | num_files=Count('target_image_file')) | ||
675 | |||
676 | candidates = candidates.filter(query) | ||
677 | |||
678 | if candidates.count() > 0: | ||
679 | candidates.order_by('build__completed_on') | ||
680 | similar_target = candidates.last() | ||
681 | |||
682 | return similar_target | ||
683 | |||
684 | def clone_artifacts_from(self, target): | ||
685 | """ | ||
686 | Make clones of the BuildArtifacts, Target_Image_Files and | ||
687 | TargetArtifactFile objects associated with Target target, then | ||
688 | associate them with this target. | ||
689 | |||
690 | Note that for Target_Image_Files, we only want files from the previous | ||
691 | build whose suffix matches one of the suffixes defined in this | ||
692 | target's build's IMAGE_FSTYPES configuration variable. This prevents the | ||
693 | Target_Image_File object for an ext4 image being associated with a | ||
694 | target for a project which didn't produce an ext4 image (for example). | ||
695 | |||
696 | Also sets the license_manifest_path of this target to the same path | ||
697 | as that of target being cloned from, as the license manifest path is | ||
698 | also a build artifact but is treated differently. | ||
699 | """ | ||
700 | |||
701 | image_fstypes = self.build.get_image_fstypes() | ||
702 | |||
703 | # filter out any image files whose suffixes aren't in the | ||
704 | # IMAGE_FSTYPES suffixes variable for this target's build | ||
705 | image_files = [target_image_file \ | ||
706 | for target_image_file in target.target_image_file_set.all() \ | ||
707 | if target_image_file.suffix in image_fstypes] | ||
708 | |||
709 | for image_file in image_files: | ||
710 | image_file.pk = None | ||
711 | image_file.target = self | ||
712 | image_file.save() | ||
713 | |||
714 | artifact_files = target.targetartifactfile_set.all() | ||
715 | for artifact_file in artifact_files: | ||
716 | artifact_file.pk = None | ||
717 | artifact_file.target = self | ||
718 | artifact_file.save() | ||
719 | |||
720 | self.license_manifest_path = target.license_manifest_path | ||
721 | self.save() | ||
722 | |||
723 | # an Artifact is anything that results from a target being built, and may | ||
724 | # be of interest to the user, and is not an image file | ||
725 | class TargetArtifactFile(models.Model): | ||
726 | target = models.ForeignKey(Target) | ||
727 | file_name = models.FilePathField() | ||
728 | file_size = models.IntegerField() | ||
729 | |||
730 | @property | ||
731 | def basename(self): | ||
732 | return os.path.basename(self.file_name) | ||
733 | |||
615 | class Target_Image_File(models.Model): | 734 | class Target_Image_File(models.Model): |
616 | # valid suffixes for image files produced by a build | 735 | # valid suffixes for image files produced by a build |
617 | SUFFIXES = { | 736 | SUFFIXES = { |
diff --git a/bitbake/lib/toaster/toastergui/templates/builddashboard.html b/bitbake/lib/toaster/toastergui/templates/builddashboard.html index dcda2a891f..f6d62b9951 100644 --- a/bitbake/lib/toaster/toastergui/templates/builddashboard.html +++ b/bitbake/lib/toaster/toastergui/templates/builddashboard.html | |||
@@ -74,7 +74,7 @@ | |||
74 | {% for target in targets %} | 74 | {% for target in targets %} |
75 | {% if target.target.is_image %} | 75 | {% if target.target.is_image %} |
76 | <div class="well well-transparent dashboard-section"> | 76 | <div class="well well-transparent dashboard-section"> |
77 | <h3><a href="{% url 'target' build.pk target.target.pk %}">{{target.target}}</a></h3> | 77 | <h3><a href="{% url 'target' build.pk target.target.pk %}">{{target.target.target}}</a></h3> |
78 | <dl class="dl-horizontal"> | 78 | <dl class="dl-horizontal"> |
79 | <dt>Packages included</dt> | 79 | <dt>Packages included</dt> |
80 | <dd><a href="{% url 'target' build.pk target.target.pk %}">{{target.npkg}}</a></dd> | 80 | <dd><a href="{% url 'target' build.pk target.target.pk %}">{{target.npkg}}</a></dd> |
@@ -124,6 +124,19 @@ | |||
124 | {% endfor %} | 124 | {% endfor %} |
125 | </ul> | 125 | </ul> |
126 | </dd> | 126 | </dd> |
127 | <dt> | ||
128 | Kernel artifacts | ||
129 | </dt> | ||
130 | <dd> | ||
131 | <ul class="list-unstyled"> | ||
132 | {% for artifact in target.target_artifacts|dictsort:"basename" %} | ||
133 | <li> | ||
134 | <a href="{% url 'build_artifact' build.id 'targetartifactfile' artifact.id %}">{{artifact.basename}}</a> | ||
135 | ({{artifact.file_size|filtered_filesizeformat}}) | ||
136 | </li> | ||
137 | {% endfor %} | ||
138 | </ul> | ||
139 | </dd> | ||
127 | </dl> | 140 | </dl> |
128 | {% endif %} | 141 | {% endif %} |
129 | </div> | 142 | </div> |
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index ad85fafb4d..0ec88d9a77 100755 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py | |||
@@ -31,6 +31,7 @@ from django.shortcuts import render, redirect, get_object_or_404 | |||
31 | from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable | 31 | from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable |
32 | from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency | 32 | from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency |
33 | from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact, CustomImagePackage | 33 | from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact, CustomImagePackage |
34 | from orm.models import TargetArtifactFile | ||
34 | from orm.models import BitbakeVersion, CustomImageRecipe | 35 | from orm.models import BitbakeVersion, CustomImageRecipe |
35 | from bldcontrol import bbcontroller | 36 | from bldcontrol import bbcontroller |
36 | from django.views.decorators.cache import cache_control | 37 | from django.views.decorators.cache import cache_control |
@@ -509,6 +510,8 @@ def builddashboard( request, build_id ): | |||
509 | targetHasNoImages = True | 510 | targetHasNoImages = True |
510 | elem[ 'imageFiles' ] = imageFiles | 511 | elem[ 'imageFiles' ] = imageFiles |
511 | elem[ 'targetHasNoImages' ] = targetHasNoImages | 512 | elem[ 'targetHasNoImages' ] = targetHasNoImages |
513 | elem['target_artifacts'] = t.targetartifactfile_set.all() | ||
514 | |||
512 | targets.append( elem ) | 515 | targets.append( elem ) |
513 | 516 | ||
514 | ## | 517 | ## |
@@ -2294,6 +2297,10 @@ if True: | |||
2294 | elif artifact_type == "buildartifact": | 2297 | elif artifact_type == "buildartifact": |
2295 | file_name = BuildArtifact.objects.get(build = build, pk = artifact_id).file_name | 2298 | file_name = BuildArtifact.objects.get(build = build, pk = artifact_id).file_name |
2296 | 2299 | ||
2300 | elif artifact_type == "targetartifactfile": | ||
2301 | target = TargetArtifactFile.objects.get(pk=artifact_id) | ||
2302 | file_name = target.file_name | ||
2303 | |||
2297 | elif artifact_type == "licensemanifest": | 2304 | elif artifact_type == "licensemanifest": |
2298 | file_name = Target.objects.get(build = build, pk = artifact_id).license_manifest_path | 2305 | file_name = Target.objects.get(build = build, pk = artifact_id).license_manifest_path |
2299 | 2306 | ||