diff options
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 | ||