Skip to contentSkip to conversation

Toaster patchset with new features and bugfixes

PATCH Superseded Alex DAMIAN 2 participants 14 messages Dec 10, 2014 – Dec 11, 2014

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore
#0 lore raw

cover Toaster patchset with new features and bugfixes

From: Alexandru DAMIAN <[email protected]>

Hello,

This is a patchset with new features and bug fixes. Patches have been individually reviewed on the toaster mailing list.

Among the features submitted, we have the import layers pages, the download artifacts patchset, a new command line option for bitbake to write event log files, and corresponding command to import log files into toaster, as well as new debug options.

We also have fixes around the layout and refactoring to make code cleaner.

Can you please pull at your convenience ?

Thanks,
Alex

The following changes since commit 9e27bb2869e8ec6781d2f68d0585ebbf9ca6f3d8:

bitbake-user-manual-metadata.xml: Updated do_package_write example (2014-12-09 22:25:11 +0000)

are available in the git repository at:

git://git.yoctoproject.org/poky-contrib adamian/20141210-submission
http://git.yoctoproject.org/cgit.cgi/poky-contrib/log/?h=adamian/20141210-submission

Alexandru DAMIAN (6):
add build artifacts table and other improvements
toastergui: implement UI changes to allow file download add option to write offline event log file
add POST endpoint for uploading eventlog files
toasterui: add extra debug and development infos
toasterui: refactor project layer finding logic

Belen Barros Pena (2):
toaster: base Only show change project icon when > one project
toaster: Initialise the 'change' icon tooltips

Michael Wood (3):
toaster: libtoaster Add a error handler to GET in makeTypehead
toaster: libtoaster Add editProject and getLayerDepsForProject
toaster: Add import layer feature.

 bin/bitbake                                        |   7 +-
 bin/toaster-eventreplay                            | 179 +++++++++++
 lib/bb/cooker.py                                   |  75 ++++-
 lib/bb/cookerdata.py                               |   1 +
 lib/bb/ui/buildinfohelper.py                       |  83 +++--
 lib/bb/ui/toasterui.py                             |  25 +-
 lib/toaster/bldcontrol/localhostbecontroller.py    |  27 +-
 .../management/commands/checksettings.py           |   2 +-
 .../bldcontrol/management/commands/runbuilds.py    |  13 +-
 lib/toaster/bldcontrol/models.py                   |  10 +-
 .../orm/migrations/0019_auto__add_buildartifact.py | 342 +++++++++++++++++++++
 lib/toaster/orm/models.py                          |  50 ++-
 lib/toaster/orm/urls.py                            |  27 ++
 lib/toaster/orm/views.py                           |  60 ++++
 lib/toaster/toastergui/static/js/base.js           |   4 +
 lib/toaster/toastergui/static/js/importlayer.js    | 190 ++++++++++++
 lib/toaster/toastergui/static/js/libtoaster.js     |  57 ++++
 lib/toaster/toastergui/static/js/projectapp.js     |   6 +
 lib/toaster/toastergui/templates/base.html         |  18 +-
 lib/toaster/toastergui/templates/build.html        |  29 +-
 .../toastergui/templates/builddashboard.html       | 131 +++++---
 .../toastergui/templates/configuration.html        |   6 +-
 lib/toaster/toastergui/templates/importlayer.html  |  80 +++--
 .../toastergui/templates/layers_dep_modal.html     |  68 ++++
 .../toastergui/templates/package_detail_base.html  |   3 +
 lib/toaster/toastergui/templates/recipe.html       |  11 +-
 lib/toaster/toastergui/templates/recipes.html      |   7 +-
 lib/toaster/toastergui/templates/target.html       |   8 +-
 lib/toaster/toastergui/templates/task.html         |  18 +-
 lib/toaster/toastergui/templates/tasks.html        |  12 +-
 lib/toaster/toastergui/urls.py                     |   2 +
 lib/toaster/toastergui/views.py                    | 293 ++++++++++++------
 lib/toaster/toastermain/settings.py                |  27 +-
 lib/toaster/toastermain/urls.py                    |  10 +-
 34 files changed, 1630 insertions(+), 251 deletions(-)
 create mode 100755 bin/toaster-eventreplay
 create mode 100644 lib/toaster/orm/migrations/0019_auto__add_buildartifact.py
 create mode 100644 lib/toaster/orm/urls.py
 create mode 100644 lib/toaster/orm/views.py
 create mode 100644 lib/toaster/toastergui/static/js/importlayer.js
 create mode 100644 lib/toaster/toastergui/templates/layers_dep_modal.html
Signature · 1 line
1.9.1
#1 lore raw

PATCH 1/11 add build artifacts table and other improvements

From: Alexandru DAMIAN <[email protected]>

We add a BuildArtifacts class to store data about files discovered during the build process and not stored anywhere else.

Small cosmetic changes in the toasterui.

Add model methods to return file path display data relative to the build environment instead of absolute file paths.

[YOCTO #6834]

---
 lib/bb/ui/buildinfohelper.py                       |  26 +-
 lib/bb/ui/toasterui.py                             |   4 +
 lib/toaster/bldcontrol/models.py                   |  10 +-
 .../orm/migrations/0019_auto__add_buildartifact.py | 342 +++++++++++++++++++++
 lib/toaster/orm/models.py                          |  29 ++
 5 files changed, 404 insertions(+), 7 deletions(-)
 create mode 100644 lib/toaster/orm/migrations/0019_auto__add_buildartifact.py
lib/bb/ui/buildinfohelper.py +20 -6
index 9aa8fe3..6f4f568 100644
@@ -26,7 +26,7 @@ os.environ["DJANGO_SETTINGS_MODULE"] = "toaster.toastermain.settings"
 
 import toaster.toastermain.settings as toaster_django_settings
 from toaster.orm.models import Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage, HelpText
-from toaster.orm.models import Target_Image_File
+from toaster.orm.models import Target_Image_File, BuildArtifact
 from toaster.orm.models import Variable, VariableHistory
 from toaster.orm.models import Package, Package_File, Target_Installed_Package, Target_File
 from toaster.orm.models import Task_Dependency, Package_Dependency
@@ -156,8 +156,7 @@ class ORMWrapper(object):
         build.outcome = outcome
         build.save()
 
-    def update_target_object(self, target, license_manifest_path):
-
+    def update_target_set_license_manifest(self, target, license_manifest_path):
         target.license_manifest_path = license_manifest_path
         target.save()
 
@@ -447,7 +446,17 @@ class ORMWrapper(object):
         target_image_file = Target_Image_File.objects.create( target = target_obj,
                             file_name = file_name,
                             file_size = file_size)
-        target_image_file.save()
+
+    def save_artifact_information(self, build_obj, file_name, file_size):
+        # we skip the image files from other builds
+        if Target_Image_File.objects.filter(file_name = file_name).count() > 0:
+            return
+
+        # do not update artifacts found in other builds
+        if BuildArtifact.objects.filter(file_name = file_name).count() > 0:
+            return
+
+        BuildArtifact.objects.create(build = build_obj, file_name = file_name, file_size = file_size)
 
     def create_logmessage(self, log_information):
         assert 'build' in log_information
@@ -752,6 +761,11 @@ class BuildInfoHelper(object):
                     if t.target in output and output.split('.rootfs.')[1] in image_fstypes:
                         self.orm_wrapper.save_target_image_file_information(t, output, evdata[output])
 
+    def update_artifact_image_file(self, event):
+        evdata = BuildInfoHelper._get_data_from_event(event)
+        for artifact_path in evdata.keys():
+            self.orm_wrapper.save_artifact_information(self.internal_state['build'], artifact_path, evdata[artifact_path])
+
     def update_build_information(self, event, errors, warnings, taskfailures):
         if 'build' in self.internal_state:
             self.orm_wrapper.update_build_object(self.internal_state['build'], errors, warnings, taskfailures)
@@ -760,10 +774,10 @@ class BuildInfoHelper(object):
     def store_license_manifest_path(self, event):
         deploy_dir = BuildInfoHelper._get_data_from_event(event)['deploy_dir']
         image_name = BuildInfoHelper._get_data_from_event(event)['image_name']
-        path = deploy_dir + "/licenses/" + image_name + "/"
+        path = deploy_dir + "/licenses/" + image_name + "/license.manifest"
         for target in self.internal_state['targets']:
             if target.target in image_name:
-                self.orm_wrapper.update_target_object(target, path)
+                self.orm_wrapper.update_target_set_license_manifest(target, path)
 
 
     def store_started_task(self, event):
lib/bb/ui/toasterui.py +4 -0
index 3a6104b..9aff489 100644
@@ -261,8 +261,12 @@ def main(server, eventHandler, params ):
                     buildinfohelper.store_missed_state_tasks(event)
                 elif event.type == "ImageFileSize":
                     buildinfohelper.update_target_image_file(event)
+                elif event.type == "ArtifactFileSize":
+                    buildinfohelper.update_artifact_image_file(event)
                 elif event.type == "LicenseManifestPath":
                     buildinfohelper.store_license_manifest_path(event)
+                else:
+                    logger.error("Unprocessed MetadataEvent %s " % str(event))
                 continue
 
             if isinstance(event, bb.cooker.CookerExit):
lib/toaster/bldcontrol/models.py +9 -1
index e643d08..cab4463 100644
@@ -70,11 +70,19 @@ class BuildEnvironment(models.Model):
                 return "binary/octet-stream"
             except ImportError:
                 return "binary/octet-stream"
+        raise Exception("FIXME: artifact type not implemented for build environment type %s" % be.get_betype_display())
+
 
     def get_artifact(self, path):
         if self.betype == BuildEnvironment.TYPE_LOCAL:
             return open(path, "r")
-        raise Exception("FIXME: not implemented")
+        raise Exception("FIXME: artifact download not implemented for build environment type %s" % be.get_betype_display())
+
+    def has_artifact(self, path):
+        import os
+        if self.betype == BuildRequest.TYPE_LOCAL:
+            return os.path.exists(path)
+        raise Exception("FIXME: has artifact not implemented for build environment type %s" % be.get_betype_display())
 
 # a BuildRequest is a request that the scheduler will build using a BuildEnvironment
 # the build request queue is the table itself, ordered by state
lib/toaster/orm/migrations/0019_auto__add_buildartifact.py new file +342 -0

343 lines not on this page · raw message

lib/toaster/orm/models.py +29 -0
index 34d3754..f5c600b 100644
@@ -25,6 +25,7 @@ from django.utils import timezone
 
 
 from django.core import validators
+from django.conf import settings
 
 class GitURLValidator(validators.URLValidator):
     import re
@@ -183,6 +184,28 @@ class Build(models.Model):
         return self.logmessage_set.filter(level=LogMessage.EXCEPTION)
 
 
+# an Artifact is anything that results from a Build, and may be of interest to the user, and is not stored elsewhere
+class BuildArtifact(models.Model):
+    build = models.ForeignKey(Build)
+    file_name = models.FilePathField()
+    file_size = models.IntegerField()
+
+
+    def get_local_file_name(self):
+        try:
+            deploydir = Variable.objects.get(build = self.build, variable_name="DEPLOY_DIR").variable_value
+            return  self.file_name[len(deploydir)+1:]
+        except:
+            raise
+
+        return self.file_name
+
+
+    def is_available(self):
+        if settings.MANAGED and build.project is not None:
+            return build.buildrequest.environment.has_artifact(file_path)
+        return False
+
 class ProjectTarget(models.Model):
     project = models.ForeignKey(Project)
     target = models.CharField(max_length=100)
@@ -457,6 +480,12 @@ class Recipe(models.Model):
     def __unicode__(self):
         return "Recipe " + self.name + ":" + self.version
 
+    def get_local_path(self):
+        if settings.MANAGED and self.layer_version.build.project is not None:
+            return self.file_path[len(self.layer_version.layer.local_path)+1:]
+
+        return self.file_path
+
     class Meta:
         unique_together = ("layer_version", "file_path")
 
Signature · 1 line
1.9.1
#2 lore raw

PATCH 2/11 toastergui: implement UI changes to allow file download

From: Alexandru DAMIAN <[email protected]>

This patchset adds download links in the build analisys pages if toaster runs in managed mode. This allows the user to access data directly from the web interface.

[YOCTO #6837]

---
 lib/toaster/toastergui/templates/base.html         |  18 +--
 lib/toaster/toastergui/templates/build.html        |  29 +++-
 .../toastergui/templates/builddashboard.html       | 131 +++++++++++-----
 .../toastergui/templates/configuration.html        |   6 +-
 .../toastergui/templates/package_detail_base.html  |   3 +
 lib/toaster/toastergui/templates/recipe.html       |  11 +-
 lib/toaster/toastergui/templates/recipes.html      |   7 +-
 lib/toaster/toastergui/templates/target.html       |   8 +-
 lib/toaster/toastergui/templates/task.html         |  18 ++-
 lib/toaster/toastergui/templates/tasks.html        |  12 +-
 lib/toaster/toastergui/views.py                    | 168 +++++++++++++--------
 11 files changed, 276 insertions(+), 135 deletions(-)
lib/toaster/toastergui/templates/base.html +9 -9
index 594c495..bc7a0ee 100644
@@ -3,14 +3,14 @@
 <html lang="en">
     <head>
         <title>{% if objectname %} {{objectname|title}} - {% endif %}Toaster</title>
-<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" type="text/css">
-<link rel="stylesheet" href="{% static 'css/bootstrap-responsive.min.css' %}" type='text/css'>
-<link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" type='text/css'>
-<link rel="stylesheet" href="{% static 'css/prettify.css' %}" type='text/css'>
-<link rel="stylesheet" href="{% static 'css/default.css' %}" type='text/css'>
+<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" type="text/css"/>
+<link rel="stylesheet" href="{% static 'css/bootstrap-responsive.min.css' %}" type='text/css'/>
+<link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" type='text/css'/>
+<link rel="stylesheet" href="{% static 'css/prettify.css' %}" type='text/css'/>
+<link rel="stylesheet" href="{% static 'css/default.css' %}" type='text/css'/>
 
 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
+<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
 <script src="{% static 'js/jquery-2.0.3.min.js' %}">
 </script>
 <script src="{% static 'js/jquery.cookie.js' %}">
@@ -64,7 +64,7 @@
             </div>
             <!-- New build popover -->
             <div class="btn-group pull-right" id="new-build-button">
-              <button class="btn dropdown-toggle" data-toggle="dropdown" href="#">
+              <button class="btn dropdown-toggle" data-toggle="dropdown">
                 New build
                 <i class="icon-caret-down"></i>
               </button>
@@ -78,7 +78,7 @@
                   </span>
                   <form id="change-project-form" style="display:none;">
                     <div class="input-append">
-                      <input type="text" class="input-medium" id="project-name-input" placeholder="Type a project name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead">
+                      <input type="text" class="input-medium" id="project-name-input" placeholder="Type a project name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead"/>
                         <button id="save-project-button" class="btn" type="button">Save</button>
                         <a href="#" id="cancel-change-project" class="btn btn-link">Cancel</a>
                       </div>
@@ -92,7 +92,7 @@
                   <li id="targets-form">
                     <h6>Target(s):</h6>
                     <form>
-                      <input type="text" class="input-xlarge" id="build-target-input" placeholder="Type a target name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead" >
+                      <input type="text" class="input-xlarge" id="build-target-input" placeholder="Type a target name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead" />
                       <div>
                         <a class="btn btn-primary" id="build-button" disabled="disabled" data-project-id="{{project.id}}">Build</a>
                       </div>
lib/toaster/toastergui/templates/build.html +26 -3
index f20f041..e71e38f 100644
@@ -45,11 +45,34 @@
             <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td>
             <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td>
             <td class="completed_on"><a href="{% url "builddashboard" build.id %}">{{build.completed_on|date:"d/m/y H:i"}}</a></td>
-            <td class="failed_tasks error">{% query build.task_build outcome=4 order__gt=0 as exectask%}{% if exectask.count == 1 %}<a href="{% url "task" build.id exectask.0.id %}">{{exectask.0.recipe.name}}.{{exectask.0.task_name}}</a>{% elif exectask.count > 1%}<a href="{% url "tasks" build.id %}?filter=outcome%3A4">{{exectask.count}}</a>{%endif%}</td>
-            <td class="errors_no">{% if  build.errors_no %}<a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>{%endif%}</td>
+            <td class="failed_tasks error">
+                {% query build.task_build outcome=4 order__gt=0 as exectask%}
+                    {% if exectask.count == 1 %}
+                        <a href="{% url "task" build.id exectask.0.id %}">{{exectask.0.recipe.name}}.{{exectask.0.task_name}}</a>
+                      {% if MANAGED and build.project %}
+                        <a href="{% url 'build_artifact' build.id "tasklogfile" exectask.0.id %}">
+                            <i class="icon-download-alt" title="" data-original-title="Download task log file"></i>
+                        </a>
+                      {% endif %}
+                    {% elif exectask.count > 1%}
+                        <a href="{% url "tasks" build.id %}?filter=outcome%3A4">{{exectask.count}} task{{exectask.count|pluralize}}</a>
+                    {%endif%}
+            </td>
+            <td class="errors_no">
+                {% if  build.errors_no %}
+                    <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>
+                    {% if MANAGED and build.project %}
+                        <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}">
+                            <i class="icon-download-alt" title="" data-original-title="Download build log"></i>
+                        </a>
+                    {% endif %}
+                {%endif%}
+            </td>
             <td class="warnings_no">{% if  build.warnings_no %}<a class="warnings_no warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td>
             <td class="time"><a href="{% url "buildtime" build.id %}">{{build.timespent|sectohms}}</a></td>
-            <td class="log">{{build.cooker_log_path}}</td>
+            {% if not MANAGED or not build.project %}
+                <td class="log">{{build.cooker_log_path}}</td>
+            {% endif %}
             <td class="output">
               {% if build.outcome == build.SUCCEEDED %}
               <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a>
lib/toaster/toastergui/templates/builddashboard.html +91 -40

188 lines not on this page · raw message

lib/toaster/toastergui/templates/configuration.html +5 -1
index 49a6a89..d3b34a2 100644
@@ -50,7 +50,9 @@
           <th>Layer</th>
           <th>Layer branch</th>
           <th>Layer commit</th>
-          <th>Layer directory</th>
+          {% if not MANAGED or not build.project %}
+              <th>Layer directory</th>
+          {% endif %}
         </tr>
       </thead>
       <tbody>{% for lv in build.layer_version_build.all|dictsort:"layer.name" %}
@@ -61,7 +63,9 @@
                 <li>{{lv.commit}}</li> </ul>">
                     {{lv.commit|truncatechars:13}}
                 </a></td>
+          {% if not MANAGED or not build.project %}
             <td>{{lv.layer.local_path}}</td>
+          {% endif %}
         </tr>{% endfor %}
       </tbody>
     </table>
lib/toaster/toastergui/templates/package_detail_base.html +3 -0
index cd015d3..dfeba55 100644
@@ -135,11 +135,14 @@
             </dt>
 
             <dd class="iscommit">{{package.recipe.layer_version.commit}}</dd>
+
+          {% if not MANAGED or not build.project %}
             <dt>
                 Layer directory
                 <i class="icon-question-sign get-help" title="Path to the layer providing the recipe that builds this package"></i>
             </dt>
             <dd><code>{{package.recipe.layer_version.layer.local_path}}</code></dd>
+          {% endif %}
         </dl>
     </div> <!-- row4 well -->
     {% endblock twocolumns %}
lib/toaster/toastergui/templates/recipe.html +10 -1
index a830ba9..b20c65e 100644
@@ -52,16 +52,19 @@
                     Layer
                 </dt>
                 <dd>{{layer.name}}</dd>
+
+          {% if not MANAGED or not build.project %}
                 <dt>
                     <i class="icon-question-sign get-help" title="Path to the layer providing the recipe"></i>
                     Layer directory
                 </dt>
                 <dd><code>{{layer.local_path}}</code></dd>
+          {% endif %}
                 <dt>
                     <i class="icon-question-sign get-help" title="Path to the recipe .bb file"></i>
                     Recipe file
                 </dt>
-                <dd><code>{{object.file_path}}</code></dd>
+                <dd><code>{{object.get_local_path}}</code></dd>
                 {% if layer_version.branch %}
                 <dt>
                     <i class="icon-question-sign get-help" title="The Git branch of the layer providing the recipe"></i>
@@ -126,6 +129,12 @@
                     <td>
                         {% ifnotequal task.sstate_result task.SSTATE_NA %}
                         <a {{ task|task_color }} href="{% url "task" build.pk task.pk %}">{{task.get_sstate_result_display}}</a>
+                    {% if MANAGED and build.project and task.outcome = task.OUTCOME_FAILED %}
+                        <a href="{% url 'build_artifact' build.pk "tasklogfile" task.pk %}">
+                            <i class="icon-download-alt" title="" data-original-title="Download task log file"></i>
+                        </a>
+                    {% endif %}
+
                         {% endifnotequal %}
                     </td>
 
lib/toaster/toastergui/templates/recipes.html +5 -2
index 791a487..889e676 100644
@@ -98,8 +98,11 @@
                 {{recipe.layer_version.commit|truncatechars:13}}
             </a>
         </td>
-        <!-- Layer directory -->
-        <td class="layer_version__layer__local_path">{{recipe.layer_version.layer.local_path}}</td>
+
+        {% if not MANAGED or not build.project %}
+            <!-- Layer directory -->
+            <td class="layer_version__layer__local_path">{{recipe.layer_version.layer.local_path}}</td>
+        {% endif %}
     </tr>
 
     {% endfor %}
lib/toaster/toastergui/templates/target.html +5 -3
index c879c39d..1309b52 100644
@@ -152,9 +152,11 @@
                 {{package.recipe.layer_version.commit|truncatechars:13}}
             </a>
         </td>
-        <td class="layer_directory">
-            {{ package.recipe.layer_version.layer.local_path }}
-        </td>
+        {% if not MANAGED or not build.project %}
+            <td class="layer_directory">
+                {{ package.recipe.layer_version.layer.local_path }}
+            </td>
+        {% endif %}
     </tr>
     {% endfor %}
 
lib/toaster/toastergui/templates/task.html +11 -7
index 1b27042..09fd25b 100644
@@ -24,17 +24,17 @@
     {# executed tasks outcome #}
     <dl class="dl-horizontal">
 	{% if task.logfile %}
+	{% if MANAGED and build.project %}
+            <a class="btn btn-large" href="{% url 'build_artifact' build.id "tasklogfile" task.pk %}" style="margin:15px;">Download task log</a>
+	{% else %}
         <dt>
             <i class="icon-question-sign get-help" title="Path the task log file"></i> Log file
         </dt>
         <dd>
-	{% if MANAGED %}
-            <code><a href="{% url 'build_artifact' build.pk 'tasklogfile' task.pk %}" target="_blanc">{{task.logfile}}</a></code>
-	{% else %}
-            <code>{{task.logfile}}</code>
-	{% endif %}
+             <code>{{task.logfile}}</code>
         </dd>
 	{% endif %}
+	{% endif %}
         {# show stack trace for failed task #}
         {% if task.outcome == task.OUTCOME_FAILED and log_head %}
             <h3>Python stack trace</h3>
@@ -191,6 +191,9 @@
             <strong>Failed</strong> to restore output from sstate cache. The file was found but could not be unpacked.
         </div>
         <dl class="dl-horizontal">
+          {% if MANAGED and build.project %}
+            <a href="{% url 'build_artifact' build.id "tasklogfile" task.pk %}" style="margin:15px;">Download log</a>
+          {% else %}
             <dt>
                 <i class="icon-question-sign get-help" title="Path to the cache attempt log file"></i>
                 Log file
@@ -201,6 +204,7 @@
                 Time (secs)
             </dt>
             <dd>{{task.elapsed_time|format_none_and_zero}}</dd>
+          {% endif %}
         </dl>
         <div class="alert alert-info">
             Running the real task instead.
@@ -268,8 +272,8 @@
             Time (secs)
         </dt>
         <dd>{{task.elapsed_time|format_none_and_zero|floatformat:2}}</dd>
-		{% endif %}	
-		{% if task.cpu_usage > 0 %}	
+		{% endif %}
+		{% if task.cpu_usage > 0 %}
         <dt>
             <i class="icon-question-sign get-help" title="The percentage of task CPU utilization"></i>
             CPU usage
lib/toaster/toastergui/templates/tasks.html +10 -2
index d0c6f4e..4cbcc5e 100644
@@ -94,6 +94,11 @@
             <td class="outcome">
                 <a href="{%url "task" build.pk task.pk%} ">{{task.get_outcome_display}} </a>
                 <i class="icon-question-sign get-help hover-help" title="{{task.get_outcome_help}}"></i>
+                    {% if MANAGED and build.project and task.outcome = task.OUTCOME_FAILED %}
+                        <a href="{% url 'build_artifact' build.pk "tasklogfile" task.pk %}">
+                            <i class="icon-download-alt" title="" data-original-title="Download task log file"></i>
+                        </a>
+                    {% endif %}
             </td>
             <td class="cache_attempt">
                 <a href="{%url "task" build.pk task.pk%} ">{{task.get_sstate_result_display|format_none_and_zero}}</a>
@@ -107,9 +112,12 @@
             <td class="disk_io">
                 {{task.disk_io|format_none_and_zero}}
             </td>
+
+        {% if not MANAGED or not build.project %}
             <td class="task_log">
                 {{task.logfile}}
             </td>
+        {% endif %}
         </tr>
     {% endfor %}
 
@@ -124,10 +132,10 @@
         // enable blue hightlight animation for the order link
         if (location.href.search('#') > -1) {
             var task_order = location.href.split('#')[1];
-            $("#" + task_order).addClass("highlight"); 
+            $("#" + task_order).addClass("highlight");
         }
     });
-    
+
 </script>
 
 {% endblock %}
lib/toaster/toastergui/views.py +101 -67

301 lines not on this page · raw message

Signature · 1 line
1.9.1
#3 lore raw

PATCH 3/11 add option to write offline event log file

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

This patch adds a "-w/--write-log" option to bitbake that writes an event log file for the current build.

The name of the file is hardcoded to "bitbake_eventlog.json"

We add a script, toater-eventreplay, that reads an event log file and loads the data into a Toaster database, creating a build entry.

We modify the toasterui to fix minor issues with reading events from an event log file.

Performance impact is undetectable under no-task executed builds.

---
 bin/bitbake                  |   7 +-
 bin/toaster-eventreplay      | 179 +++++++++++++++++++++++++++++++++++++++++++
 lib/bb/cooker.py             |  75 +++++++++++++++++-
 lib/bb/cookerdata.py         |   1 +
 lib/bb/ui/buildinfohelper.py |  45 +++++++----
 lib/bb/ui/toasterui.py       |   2 +-
 6 files changed, 290 insertions(+), 19 deletions(-)
 create mode 100755 bin/toaster-eventreplay
bin/bitbake +6 -1
index 7f8449c..52a4d2b 100755
@@ -196,6 +196,9 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters):
         parser.add_option("", "--status-only", help = "Check the status of the remote bitbake server.",
                    action = "store_true", dest = "status_only", default = False)
 
+        parser.add_option("-w", "--write-log", help = "Writes the event log of the build to the bitbake_eventlog.json file.",
+                   action = "store_true", dest = "writeeventlog", default = False)
+
         options, targets = parser.parse_args(sys.argv)
 
         # some environmental variables set also configuration options
@@ -206,6 +209,9 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters):
         if "BBTOKEN" in os.environ:
             options.xmlrpctoken = os.environ["BBTOKEN"]
 
+        if "TOASTER_EVENTLOG" is os.environ:
+            options.writeeventlog = True
+
         # if BBSERVER says to autodetect, let's do that
         if options.remote_server:
             [host, port] = options.remote_server.split(":", 2)
@@ -266,7 +272,6 @@ def start_server(servermodule, configParams, configuration, features):
     return server
 
 
-
 def main():
 
     configParams = BitBakeConfigParameters()
bin/toaster-eventreplay new file +179 -0

179 lines not on this page · raw message

lib/bb/cooker.py +72 -3

95 lines not on this page · raw message

lib/bb/cookerdata.py +1 -0
index 470d538..2ceed2d 100644
@@ -139,6 +139,7 @@ class CookerConfiguration(object):
         self.dry_run = False
         self.tracking = False
         self.interface = []
+        self.writeeventlog = False
 
         self.env = {}
 
lib/bb/ui/buildinfohelper.py +31 -14

80 lines not on this page · raw message

lib/bb/ui/toasterui.py +1 -1
index 9aff489..d84b256 100644
@@ -308,7 +308,7 @@ def main(server, eventHandler, params ):
             try:
                 buildinfohelper.store_log_exception("%s\n%s" % (str(e), exception_data))
             except Exception as ce:
-                print("CRITICAL: failed to to save toaster exception to the database: %s" % str(ce))
+                logger.error("CRITICAL - Failed to to save toaster exception to the database: %s" % str(ce))
 
             pass
 
Signature · 1 line
1.9.1
#4 lore raw

Re: [PATCH 03/11] add option to write offline event log file

Quoted 7 lines · Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

This patch adds a "-w/--write-log" option to bitbake that writes an event log file for the current build.

The name of the file is hardcoded to "bitbake_eventlog.json"

Quoted 8 lines

We add a script, toater-eventreplay, that reads an event log file and loads the data into a Toaster database, creating a build entry.

We modify the toasterui to fix minor issues with reading events from an event log file.

Performance impact is undetectable under no-task executed builds.

I'm very much in favour of doing this as its going to allow better build diagnostics and in many ways I'd like to see this enabled by default.

I'm a little confused with this patch however, I can't decide whether this is generic bitbake infrastructure or toaster specific.

"TOASTER_EVENTLOG" is toaster specific for example. I'm guessing you also need to inherit a class for this to work and right now, that class is part of OE-Core, not bitbake.

The option name is however generic and you'd get no warning if the class wasn't enabled?

I did also idly wonder why we were hardcoding the name "bitbake_eventlog.json" when we could probably make it a parameter. Or perhaps we should append a datetime stamp?

Alternatively, could we make inclusion of the class generate a log automatically and remove the need for the bitbake option?

Cheers,

Richard

Quoted 448 lines
---
 bin/bitbake                  |   7 +-
 bin/toaster-eventreplay      | 179 +++++++++++++++++++++++++++++++++++++++++++
 lib/bb/cooker.py             |  75 +++++++++++++++++-
 lib/bb/cookerdata.py         |   1 +
 lib/bb/ui/buildinfohelper.py |  45 +++++++----
 lib/bb/ui/toasterui.py       |   2 +-
 6 files changed, 290 insertions(+), 19 deletions(-)
 create mode 100755 bin/toaster-eventreplay
bin/bitbake +6 -1
index 7f8449c..52a4d2b 100755
@@ -196,6 +196,9 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters):
         parser.add_option("", "--status-only", help = "Check the status of the remote bitbake server.",
                    action = "store_true", dest = "status_only", default = False)
 
+        parser.add_option("-w", "--write-log", help = "Writes the event log of the build to the bitbake_eventlog.json file.",
+                   action = "store_true", dest = "writeeventlog", default = False)
+
         options, targets = parser.parse_args(sys.argv)
 
         # some environmental variables set also configuration options
@@ -206,6 +209,9 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters):
         if "BBTOKEN" in os.environ:
             options.xmlrpctoken = os.environ["BBTOKEN"]
 
+        if "TOASTER_EVENTLOG" is os.environ:
+            options.writeeventlog = True
+
         # if BBSERVER says to autodetect, let's do that
         if options.remote_server:
             [host, port] = options.remote_server.split(":", 2)
@@ -266,7 +272,6 @@ def start_server(servermodule, configParams, configuration, features):
     return server
 
 
-
 def main():
 
     configParams = BitBakeConfigParameters()
bin/toaster-eventreplay new file +179 -0
new file mode 100755
index 0000000..624829a
@@ -0,0 +1,179 @@
+#!/usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2014        Alex Damian
+#
+# This file re-uses code spread throughout other Bitbake source files.
+# As such, all other copyrights belong to their own right holders.
+#
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+# This command takes a filename as a single parameter. The filename is read
+# as a build eventlog, and the ToasterUI is used to process events in the file
+# and log data in the database
+
+import os
+import sys, logging
+
+# mangle syspath to allow easy import of modules
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
+                                'lib'))
+
+
+import bb.cooker
+from bb.ui import toasterui
+import sys
+import logging
+
+logger = logging.getLogger(__name__)
+console = logging.StreamHandler(sys.stdout)
+format_str = "%(levelname)s: %(message)s"
+logging.basicConfig(format=format_str)
+
+
+import json, pickle
+
+
+class FileReadEventsServerConnection():
+    """  Emulates a connection to a bitbake server that feeds
+        events coming actually read from a saved log file.
+    """
+
+    class MockConnection():
+        """ fill-in for the proxy to the server. we just return generic data
+        """
+        def __init__(self, sc):
+            self._sc = sc
+
+        def runCommand(self, commandArray):
+            """ emulates running a command on the server; only read-only commands are accepted """
+            command_name = commandArray[0]
+
+            if command_name == "getVariable":
+                if commandArray[1] in self._sc._variables:
+                    return (self._sc._variables[commandArray[1]]['v'], None)
+                return (None, "Missing variable")
+
+            elif command_name == "getAllKeysWithFlags":
+                dump = {}
+                flaglist = commandArray[1]
+                for k in self._sc._variables.keys():
+                    try:
+                        if not k.startswith("__"):
+                            v = self._sc._variables[k]['v']
+                            dump[k] = {
+                                'v' : v ,
+                                'history' : self._sc._variables[k]['history'],
+                            }
+                            for d in flaglist:
+                                dump[k][d] = self._sc._variables[k][d]
+                    except Exception as e:
+                        print(e)
+                return (dump, None)
+            else:
+                raise Exception("Command %s not implemented" % commandArray[0])
+
+        def terminateServer(self):
+            """ do not do anything """
+            pass
+
+
+
+    class EventReader():
+        def __init__(self, sc):
+            self._sc = sc
+            self.firstraise = 0
+
+        def _create_event(self, line):
+            def _import_class(name):
+                assert len(name) > 0
+                assert "." in name, name
+
+                components = name.strip().split(".")
+                modulename = ".".join(components[:-1])
+                moduleklass = components[-1]
+
+                module = __import__(modulename, fromlist=[str(moduleklass)])
+                return getattr(module, moduleklass)
+
+            # we build a toaster event out of current event log line
+            try:
+                event_data = json.loads(line.strip())
+                event_class = _import_class(event_data['class'])
+                event_object = pickle.loads(json.loads(event_data['vars']))
+            except ValueError as e:
+                print("Failed loading ", line)
+                raise e
+
+            if not isinstance(event_object, event_class):
+                raise Exception("Error loading objects %s class %s ", event_object, event_class)
+
+            return event_object
+
+        def waitEvent(self, timeout):
+
+            nextline = self._sc._eventfile.readline()
+            if len(nextline) == 0:
+                # the build data ended, while toasterui still waits for events.
+                # this happens when the server was abruptly stopped, so we simulate this
+                self.firstraise += 1
+                if self.firstraise == 1:
+                    raise KeyboardInterrupt()
+                else:
+                    return None
+            else:
+                self._sc.lineno += 1
+            return self._create_event(nextline)
+
+
+    def _readVariables(self, variableline):
+        self._variables = json.loads(variableline.strip())['allvariables']
+
+
+    def __init__(self, file_name):
+        self.connection = FileReadEventsServerConnection.MockConnection(self)
+        self._eventfile = open(file_name, "r")
+
+        # we expect to have the variable dump at the start of the file
+        self.lineno = 1
+        self._readVariables(self._eventfile.readline())
+
+        self.events = FileReadEventsServerConnection.EventReader(self)
+
+
+
+
+
+class MockConfigParameters():
+    """ stand-in for cookerdata.ConfigParameters; as we don't really config a cooker, this
+        serves just to supply needed interfaces for the toaster ui to work """
+    def __init__(self):
+        self.observe_only = True            # we can only read files
+
+
+# run toaster ui on our mock bitbake class
+if __name__ == "__main__":
+    if len(sys.argv) < 2:
+        logger.error("Usage: %s event.log " % sys.argv[0])
+        sys.exit(1)
+
+    file_name = sys.argv[-1]
+    mock_connection = FileReadEventsServerConnection(file_name)
+    configParams = MockConfigParameters()
+
+    # run the main program
+    toasterui.main(mock_connection.connection, mock_connection.events, configParams)
lib/bb/cooker.py +72 -3
index df9a0ca..468d06d 100644
@@ -205,6 +205,75 @@ class BBCooker:
         self.data = self.databuilder.data
         self.data_hash = self.databuilder.data_hash
 
+
+        # we log all events to a file if so directed
+        if self.configuration.writeeventlog:
+            import json, pickle
+            DEFAULT_EVENTFILE = "bitbake_eventlog.json"
+            class EventLogWriteHandler():
+
+                class EventWriter():
+                    def __init__(self, cooker):
+                        self.file_inited = None
+                        self.cooker = cooker
+                        self.event_queue = []
+
+                    def init_file(self):
+                        try:
+                            # delete the old log
+                            os.remove(DEFAULT_EVENTFILE)
+                        except:
+                            pass
+
+                        # write current configuration data
+                        with open(DEFAULT_EVENTFILE, "w") as f:
+                            f.write("%s\n" % json.dumps({ "allvariables" : self.cooker.getAllKeysWithFlags(["doc", "func"])}))
+
+                    def write_event(self, event):
+                        with open(DEFAULT_EVENTFILE, "a") as f:
+                            try:
+                                f.write("%s\n" % json.dumps({"class":event.__module__ + "." + event.__class__.__name__, "vars":json.dumps(pickle.dumps(event)) }))
+                            except Exception as e:
+                                import traceback
+                                print(e, traceback.format_exc(e))
+
+
+                    def send(self, event):
+                        event_class = event.__module__ + "." + event.__class__.__name__
+
+                        # init on bb.event.BuildStarted
+                        if self.file_inited is None:
+                            if  event_class == "bb.event.BuildStarted":
+                                self.init_file()
+                                self.file_inited = True
+
+                                # write pending events
+                                for e in self.event_queue:
+                                    self.write_event(e)
+
+                                # also write the current event
+                                self.write_event(event)
+
+                            else:
+                                # queue all events until the file is inited
+                                self.event_queue.append(event)
+
+                        else:
+                            # we have the file, just write the event
+                            self.write_event(event)
+
+                # set our handler's event processor
+                event = EventWriter(self)       # self is the cooker here
+
+
+            # set up cooker features for this mock UI handler
+
+            # we need to write the dependency tree in the log
+            self.featureset.setFeature(CookerFeatures.SEND_DEPENDS_TREE)
+            # register the log file writer as UI Handler
+            bb.event.register_UIHhandler(EventLogWriteHandler())
+
+
         #
         # Special updated configuration we use for firing events
         #
@@ -505,7 +574,7 @@ class BBCooker:
         taskdata, runlist, pkgs_to_build = self.buildTaskData(pkgs_to_build, task, False)
 
         return runlist, taskdata
-    
+
     ######## WARNING : this function requires cache_extra to be enabled ########
 
     def generateTaskDepTreeData(self, pkgs_to_build, task):
@@ -1550,10 +1619,10 @@ class CookerCollectFiles(object):
         for p in pkgfns:
             realfn, cls = bb.cache.Cache.virtualfn2realfn(p)
             priorities[p] = self.calc_bbfile_priority(realfn, matched)
- 
+
         # Don't show the warning if the BBFILE_PATTERN did match .bbappend files
         unmatched = set()
-        for _, _, regex, pri in self.bbfile_config_priorities:        
+        for _, _, regex, pri in self.bbfile_config_priorities:
             if not regex in matched:
                 unmatched.add(regex)
 
lib/bb/cookerdata.py +1 -0
index 470d538..2ceed2d 100644
@@ -139,6 +139,7 @@ class CookerConfiguration(object):
         self.dry_run = False
         self.tracking = False
         self.interface = []
+        self.writeeventlog = False
 
         self.env = {}
 
lib/bb/ui/buildinfohelper.py +31 -14
index 6f4f568..e6ea7cd 100644
@@ -549,7 +549,6 @@ class ORMWrapper(object):
         assert isinstance(build_obj, Build)
 
         helptext_objects = []
-
         for k in vardump:
             desc = vardump[k]['doc']
             if desc is None:
@@ -724,7 +723,6 @@ class BuildInfoHelper(object):
 
     def store_started_build(self, event):
         assert '_pkgs' in vars(event)
-        assert 'lvs' in self.internal_state, "Layer version information not found; Check if the bitbake server was configured to inherit toaster.bbclass."
         build_information = self._get_build_information()
 
         build_obj = self.orm_wrapper.create_build_object(build_information, self.brbe)
@@ -732,10 +730,13 @@ class BuildInfoHelper(object):
         self.internal_state['build'] = build_obj
 
         # save layer version information for this build
-        for layer_obj in self.internal_state['lvs']:
-            self.orm_wrapper.get_update_layer_version_object(build_obj, layer_obj, self.internal_state['lvs'][layer_obj])
+        if not 'lvs' in self.internal_state:
+            logger.error("Layer version information not found; Check if the bitbake server was configured to inherit toaster.bbclass.")
+        else:
+            for layer_obj in self.internal_state['lvs']:
+                self.orm_wrapper.get_update_layer_version_object(build_obj, layer_obj, self.internal_state['lvs'][layer_obj])
 
-        del self.internal_state['lvs']
+            del self.internal_state['lvs']
 
         # create target information
         target_information = {}
@@ -745,7 +746,8 @@ class BuildInfoHelper(object):
         self.internal_state['targets'] = self.orm_wrapper.create_target_objects(target_information)
 
         # Save build configuration
-        self.orm_wrapper.save_build_variables(build_obj, self.server.runCommand(["getAllKeysWithFlags", ["doc", "func"]])[0])
+        data = self.server.runCommand(["getAllKeysWithFlags", ["doc", "func"]])[0]
+        self.orm_wrapper.save_build_variables(build_obj, [])
 
         return self.brbe
 
@@ -972,14 +974,29 @@ class BuildInfoHelper(object):
 
             recipe_info = {}
             recipe_info['name'] = pn
-            recipe_info['version'] = event._depgraph['pn'][pn]['version'].lstrip(":")
             recipe_info['layer_version'] = layer_version_obj
-            recipe_info['summary'] = event._depgraph['pn'][pn]['summary']
-            recipe_info['license'] = event._depgraph['pn'][pn]['license']
-            recipe_info['description'] = event._depgraph['pn'][pn]['description']
-            recipe_info['section'] = event._depgraph['pn'][pn]['section']
-            recipe_info['homepage'] = event._depgraph['pn'][pn]['homepage']
-            recipe_info['bugtracker'] = event._depgraph['pn'][pn]['bugtracker']
+
+            if 'version' in event._depgraph['pn'][pn]:
+                recipe_info['version'] = event._depgraph['pn'][pn]['version'].lstrip(":")
+
+            if 'summary' in event._depgraph['pn'][pn]:
+                recipe_info['summary'] = event._depgraph['pn'][pn]['summary']
+
+            if 'license' in event._depgraph['pn'][pn]:
+                recipe_info['license'] = event._depgraph['pn'][pn]['license']
+
+            if 'description' in event._depgraph['pn'][pn]:
+                recipe_info['description'] = event._depgraph['pn'][pn]['description']
+
+            if 'section' in event._depgraph['pn'][pn]:
+                recipe_info['section'] = event._depgraph['pn'][pn]['section']
+
+            if 'homepage' in event._depgraph['pn'][pn]:
+                recipe_info['homepage'] = event._depgraph['pn'][pn]['homepage']
+
+            if 'bugtracker' in event._depgraph['pn'][pn]:
+                recipe_info['bugtracker'] = event._depgraph['pn'][pn]['bugtracker']
+
             recipe_info['file_path'] = file_name
             recipe = self.orm_wrapper.get_update_recipe_object(recipe_info)
             recipe.is_image = False
@@ -1138,4 +1155,4 @@ class BuildInfoHelper(object):
 
         if 'backlog' in self.internal_state:
             for event in self.internal_state['backlog']:
-                   print "NOTE: Unsaved log: ", event.msg
+                   logger.error("Unsaved log: %s", event.msg)
lib/bb/ui/toasterui.py +1 -1
index 9aff489..d84b256 100644
@@ -308,7 +308,7 @@ def main(server, eventHandler, params ):
             try:
                 buildinfohelper.store_log_exception("%s\n%s" % (str(e), exception_data))
             except Exception as ce:
-                print("CRITICAL: failed to to save toaster exception to the database: %s" % str(ce))
+                logger.error("CRITICAL - Failed to to save toaster exception to the database: %s" % str(ce))
 
             pass
 
Signature · 1 line
1.9.1
#5 lore raw

PATCH 4/11 add POST endpoint for uploading eventlog files

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

This patch adds a simple UI-less POST endpoint, where bitbake_eventlog.json files generated by a bitbake run can be uploaded to the running toaster instance for insertion into the database.

---
 lib/toaster/orm/urls.py  | 27 ++++++++++++++++++++++
 lib/toaster/orm/views.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 87 insertions(+)
 create mode 100644 lib/toaster/orm/urls.py
 create mode 100644 lib/toaster/orm/views.py
lib/toaster/orm/urls.py new file +27 -0
new file mode 100644
index 0000000..961bc19
@@ -0,0 +1,27 @@
+#
+# BitBake Toaster Implementation
+#
+# Copyright (C) 2014        Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+from django.conf.urls import patterns, include, url
+from django.views.generic import RedirectView
+
+urlpatterns = patterns('orm.views',
+        # landing point for pushing a bitbake_eventlog.json file to this toaster instace
+        url(r'^eventfile$', 'eventfile', name='eventfile'),
+        )
+
lib/toaster/orm/views.py new file +60 -0

60 lines not on this page · raw message

Signature · 1 line
1.9.1
#6 lore raw

PATCH 5/11 toasterui: add extra debug and development infos

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

We update and add logs throughout the code in order to help with development. The extra logging is turned off by default, but it can be enabled by using environment variables.

All logging happens through the Python logging facilities.

The toaster UI will save a log of all incoming events if the TOASTER_EVENTLOG variable is set.

If TOASTER_SQLDEBUG is set all DB queries will be logged.

If TOASTER_DEVEL is set and the django-fresh module is available, the module is enabled to allow auto-reload of pages when the source is changed.

---
 lib/bb/ui/buildinfohelper.py                       | 12 ++++++++--
 lib/bb/ui/toasterui.py                             | 19 +++++++--------
 lib/toaster/bldcontrol/localhostbecontroller.py    | 27 ++++++++++++++--------
 .../management/commands/checksettings.py           |  2 +-
 .../bldcontrol/management/commands/runbuilds.py    | 13 +++++++----
 lib/toaster/toastermain/settings.py                | 27 ++++++++++++++++++++--
 lib/toaster/toastermain/urls.py                    | 10 +++++---
 7 files changed, 79 insertions(+), 31 deletions(-)
lib/bb/ui/buildinfohelper.py +10 -2
index e6ea7cd..85d7b28 100644
@@ -33,6 +33,11 @@ from toaster.orm.models import Task_Dependency, Package_Dependency
 from toaster.orm.models import Recipe_Dependency
 from bb.msg import BBLogFormatter as format
 from django.db import models
+import logging
+
+
+logger = logging.getLogger("BitBake")
+
 
 class NotExisting(Exception):
     pass
@@ -115,7 +120,9 @@ class ORMWrapper(object):
                                     build_name=build_info['build_name'],
                                     bitbake_version=build_info['bitbake_version'])
 
+        logger.debug(1, "buildinfohelper: build is created %s" % build)
         if brbe is not None:
+            logger.debug(1, "buildinfohelper: brbe is %s" % brbe)
             from bldcontrol.models import BuildEnvironment, BuildRequest
             br, be = brbe.split(":")
 
@@ -604,6 +611,7 @@ class BuildInfoHelper(object):
         self.has_build_history = has_build_history
         self.tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0]
         self.brbe    = self.server.runCommand(["getVariable", "TOASTER_BRBE"])[0]
+        logger.debug(1, "buildinfohelper: Build info helper inited %s" % vars(self))
 
 
     def _configure_django(self):
@@ -1127,10 +1135,10 @@ class BuildInfoHelper(object):
         if 'build' in self.internal_state and 'backlog' in self.internal_state:
             if len(self.internal_state['backlog']):
                 tempevent = self.internal_state['backlog'].pop()
-                print "DEBUG: Saving stored event ", tempevent
+                logger.debug(1, "buildinfohelper: Saving stored event %s " % tempevent)
                 self.store_log_event(tempevent)
             else:
-                print "ERROR: Events not saved: \n", self.internal_state['backlog']
+                logger.error("buildinfohelper: Events not saved: %s" % self.internal_state['backlog'])
                 del self.internal_state['backlog']
 
         log_information = {}
lib/bb/ui/toasterui.py +10 -9
index d84b256..a85ad5a 100644
@@ -62,15 +62,6 @@ def _log_settings_from_server(server):
 
 def main(server, eventHandler, params ):
 
-    includelogs, loglines = _log_settings_from_server(server)
-
-    # verify and warn
-    build_history_enabled = True
-    inheritlist, error = server.runCommand(["getVariable", "INHERIT"])
-    if not "buildhistory" in inheritlist.split(" "):
-        logger.warn("buildhistory is not enabled. Please enable INHERIT += \"buildhistory\" to see image details.")
-        build_history_enabled = False
-
     helper = uihelper.BBUIHelper()
 
     console = logging.StreamHandler(sys.stdout)
@@ -80,6 +71,16 @@ def main(server, eventHandler, params ):
     console.setFormatter(format)
     logger.addHandler(console)
 
+    includelogs, loglines = _log_settings_from_server(server)
+
+    # verify and warn
+    build_history_enabled = True
+    inheritlist, error = server.runCommand(["getVariable", "INHERIT"])
+
+    if not "buildhistory" in inheritlist.split(" "):
+        logger.warn("buildhistory is not enabled. Please enable INHERIT += \"buildhistory\" to see image details.")
+        build_history_enabled = False
+
     if not params.observe_only:
         logger.error("ToasterUI can only work in observer mode")
         return
lib/toaster/bldcontrol/localhostbecontroller.py +17 -10
index ebf2b4f..22d31e3 100644
@@ -32,6 +32,10 @@ from toastermain import settings
 
 from bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException, _getgitcheckoutdirectoryname
 
+import logging
+logger = logging.getLogger("toaster")
+
+
 class LocalhostBEController(BuildEnvironmentController):
     """ Implementation of the BuildEnvironmentController for the localhost;
         this controller manages the default build directory,
@@ -56,8 +60,10 @@ class LocalhostBEController(BuildEnvironmentController):
                 err = "command: %s \n%s" % (command, out)
             else:
                 err = "command: %s \n%s" % (command, err)
+            logger.debug("localhostbecontroller: shellcmd error %s" % err)
             raise ShellCmdException(err)
         else:
+            logger.debug("localhostbecontroller: shellcmd success")
             return out
 
     def _createdirpath(self, path):
@@ -90,7 +96,7 @@ class LocalhostBEController(BuildEnvironmentController):
         for i in self._shellcmd(cmd).split("\n"):
             if i.startswith("Bitbake server address"):
                 port = i.split(" ")[-1]
-                print "Found bitbake server port ", port
+                logger.debug("localhostbecontroller: Found bitbake server port %s" % port)
 
         def _toaster_ui_started(filepath):
             if not os.path.exists(filepath):
@@ -103,10 +109,10 @@ class LocalhostBEController(BuildEnvironmentController):
 
         while not _toaster_ui_started(os.path.join(self.be.builddir, "toaster_ui.log")):
             import time
-            print "DEBUG: Waiting server to start"
+            logger.debug("localhostbecontroller: Waiting bitbake server to start")
             time.sleep(0.5)
 
-        print("DEBUG: Started server")
+        logger.debug("localhostbecontroller: Started bitbake server")
         assert self.be.sourcedir and os.path.exists(self.be.builddir)
         self.be.bbaddress = "localhost"
         self.be.bbport = port
@@ -116,11 +122,11 @@ class LocalhostBEController(BuildEnvironmentController):
     def stopBBServer(self):
         assert self.pokydirname and os.path.exists(self.pokydirname)
         assert self.islayerset
-        print self._shellcmd("bash -c \"source %s/oe-init-build-env %s && %s source toaster stop\"" %
+        self._shellcmd("bash -c \"source %s/oe-init-build-env %s && %s source toaster stop\"" %
             (self.pokydirname, self.be.builddir, (lambda: "" if self.be.bbtoken is None else "BBTOKEN=%s" % self.be.bbtoken)()))
         self.be.bbstate = BuildEnvironment.SERVER_STOPPED
         self.be.save()
-        print "Stopped server"
+        logger.debug("localhostbecontroller: Stopped bitbake server")
 
     def setLayers(self, bitbakes, layers):
         """ a word of attention: by convention, the first layer for any build will be poky! """
@@ -149,12 +155,13 @@ class LocalhostBEController(BuildEnvironmentController):
                     raise BuildSetupException("More than one commit per git url, unsupported configuration: \n%s" % pprint.pformat(gitrepos))
 
 
+        logger.debug("localhostbecontroller, our git repos are %s" % gitrepos)
         layerlist = []
 
         # 2. checkout the repositories
         for giturl in gitrepos.keys():
             localdirname = os.path.join(self.be.sourcedir, _getgitcheckoutdirectoryname(giturl))
-            print "DEBUG: giturl ", giturl ,"checking out in current directory", localdirname
+            logger.debug("localhostbecontroller: giturl %s checking out in current directory %s" % (giturl, localdirname))
 
             # make sure our directory is a git repository
             if os.path.exists(localdirname):
@@ -167,17 +174,17 @@ class LocalhostBEController(BuildEnvironmentController):
 
             # branch magic name "HEAD" will inhibit checkout
             if commit != "HEAD":
-                print "DEBUG: checking out commit ", commit, "to", localdirname
+                logger.debug("localhostbecontroller: checking out commit %s to %s " % (commit, localdirname))
                 self._shellcmd("git fetch --all && git checkout \"%s\"" % commit , localdirname)
 
             # take the localdirname as poky dir if we can find the oe-init-build-env
             if self.pokydirname is None and os.path.exists(os.path.join(localdirname, "oe-init-build-env")):
-                print "DEBUG: selected poky dir name", localdirname
+                logger.debug("localhostbecontroller: selected poky dir name %s" % localdirname)
                 self.pokydirname = localdirname
 
                 # make sure we have a working bitbake
                 if not os.path.exists(os.path.join(self.pokydirname, 'bitbake')):
-                    print "DEBUG: checking bitbake into the poky dirname %s " % self.pokydirname
+                    logger.debug("localhostbecontroller: checking bitbake into the poky dirname %s " % self.pokydirname)
                     self._shellcmd("git clone -b \"%s\" \"%s\" \"%s\" " % (bitbakes[0].commit, bitbakes[0].giturl, os.path.join(self.pokydirname, 'bitbake')))
 
             # verify our repositories
@@ -189,7 +196,7 @@ class LocalhostBEController(BuildEnvironmentController):
                 if name != "bitbake":
                     layerlist.append(localdirpath.rstrip("/"))
 
-        print "DEBUG: current layer list ", layerlist
+        logger.debug("localhostbecontroller: current layer list %s " % layerlist)
 
         # 3. configure the build environment, so we have a conf/bblayers.conf
         assert self.pokydirname is not None
lib/toaster/bldcontrol/management/commands/checksettings.py +1 -1
index 96d2d51..55f118c 100644
@@ -1,7 +1,7 @@
 from django.core.management.base import NoArgsCommand, CommandError
 from django.db import transaction
 from bldcontrol.bbcontroller import getBuildEnvironmentController, ShellCmdException
-from bldcontrol.models import BuildRequest, BuildEnvironment
+from bldcontrol.models import BuildRequest, BuildEnvironment, BRError
 from orm.models import ToasterSetting
 import os
 
lib/toaster/bldcontrol/management/commands/runbuilds.py +9 -4
index 5e253e0..56c989c 100644
@@ -4,6 +4,9 @@ from orm.models import Build
 from bldcontrol.bbcontroller import getBuildEnvironmentController, ShellCmdException, BuildSetupException
 from bldcontrol.models import BuildRequest, BuildEnvironment, BRError
 import os
+import logging
+
+logger = logging.getLogger("toaster")
 
 class Command(NoArgsCommand):
     args    = ""
@@ -32,6 +35,7 @@ class Command(NoArgsCommand):
                 # select the build environment and the request to build
                 br = self._selectBuildRequest()
             except IndexError as e:
+                # logger.debug("runbuilds: No build request")
                 return
             try:
                 bec = self._selectBuildEnvironment()
@@ -39,10 +43,10 @@ class Command(NoArgsCommand):
                 # we could not find a BEC; postpone the BR
                 br.state = BuildRequest.REQ_QUEUED
                 br.save()
-                print "No build env"
+                logger.debug("runbuilds: No build env")
                 return
 
-            print "Build %s, Environment %s" % (br, bec.be)
+            logger.debug("runbuilds: starting build %s, environment %s" % (br, bec.be))
             # let the build request know where it is being executed
             br.environment = bec.be
             br.save()
@@ -63,7 +67,7 @@ class Command(NoArgsCommand):
                 task = None
             bbctrl.build(list(map(lambda x:x.target, br.brtarget_set.all())), task)
 
-            print "Build launched, exiting"
+            logger.debug("runbuilds: Build launched, exiting")
             # disconnect from the server
             bbctrl.disconnect()
 
@@ -71,7 +75,7 @@ class Command(NoArgsCommand):
 
 
         except Exception as e:
-            print " EE Error executing shell command\n", e
+            logger.error("runbuilds: Error executing shell command %s" % e)
             traceback.print_exc(e)
             BRError.objects.create(req = br,
                 errtype = str(type(e)),
@@ -83,6 +87,7 @@ class Command(NoArgsCommand):
             bec.be.save()
 
 
+
     def cleanup(self):
         from django.utils import timezone
         from datetime import timedelta
lib/toaster/toastermain/settings.py +25 -2
index 0974b90..acc20cc 100644
@@ -21,11 +21,15 @@
 
 # Django settings for Toaster project.
 
+import os, re
+
 DEBUG = True
 TEMPLATE_DEBUG = DEBUG
 
 # Set to True to see the SQL queries in console
 SQL_DEBUG = False
+if os.environ.get("TOASTER_SQLDEBUG", None) is not None:
+    SQL_DEBUG = True
 
 
 ADMINS = (
@@ -46,7 +50,6 @@ DATABASES = {
 }
 
 # Reinterpret database settings if we have DATABASE_URL environment variable defined
-import os, re
 
 if 'DATABASE_URL' in os.environ:
     dburl = os.environ['DATABASE_URL']
@@ -212,6 +215,9 @@ MIDDLEWARE_CLASSES = (
     # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 )
 
+from os.path import dirname as DN
+SITE_ROOT=DN(DN(os.path.abspath(__file__)))
+
 ROOT_URLCONF = 'toastermain.urls'
 
 # Python dotted path to the WSGI application used by Django's runserver.
@@ -245,6 +251,19 @@ INSTALLED_APPS = (
     'south',
 )
 
+
+# Load django-fresh is TOASTER_DEVEL is set, and the module is available
+FRESH_ENABLED = False
+if os.environ.get('TOASTER_DEVEL', None) is not None:
+    try:
+        import fresh
+        MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ("fresh.middleware.FreshMiddleware",)
+        INSTALLED_APPS = INSTALLED_APPS + ('fresh',)
+        FRESH_ENABLED = True
+    except:
+        pass
+
+
 SOUTH_TESTS_MIGRATE = False
 
 # if we run in managed mode, we need user support
@@ -286,7 +305,7 @@ LOGGING = {
     },
     'formatters': {
         'datetime': {
-            'format': 'DB %(asctime)s %(message)s'
+            'format': '%(levelname)s %(asctime)s %(message)s'
         }
     },
     'handlers': {
@@ -302,6 +321,10 @@ LOGGING = {
         }
     },
     'loggers': {
+        'toaster' : {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+        },
         'django.request': {
             'handlers': ['mail_admins'],
             'level': 'ERROR',
lib/toaster/toastermain/urls.py +7 -3
index 549fda1..a2916e2 100644
@@ -44,11 +44,15 @@ urlpatterns = patterns('',
 )
 
 import toastermain.settings
+
+if toastermain.settings.FRESH_ENABLED:
+    urlpatterns.insert(1, url(r'', include('fresh.urls')))
+
 if toastermain.settings.MANAGED:
-    urlpatterns = urlpatterns + [
+    urlpatterns = [
         # Uncomment the next line to enable the admin:
         url(r'^admin/', include(admin.site.urls)),
-    ]
+    ] + urlpatterns
 # Automatically discover urls.py in various apps, beside our own
 # and map module directories to the patterns
 
@@ -61,4 +65,4 @@ for t in os.walk(os.path.dirname(currentdir)):
 
     if "urls.py" in t[2] and t[0] != currentdir:
         modulename = os.path.basename(t[0])
-        urlpatterns.append( url(r'^' + modulename + '/', include ( modulename + '.urls')))
+        urlpatterns.insert(0, url(r'^' + modulename + '/', include ( modulename + '.urls')))
Signature · 1 line
1.9.1
#7 lore raw

PATCH 6/11 toaster: base Only show change project icon when > one project

In reply to Alex DAMIAN

From: Belen Barros Pena <[email protected]>

In the new build button, it only makes sense to change the selected project when there is more than one project in the Toaster instance. If the number of projects is 1, we hide the change project icon.

---
 lib/toaster/toastergui/static/js/base.js | 4 ++++
 1 file changed, 4 insertions(+)
lib/toaster/toastergui/static/js/base.js +4 -0
index fac59e6..619ad28 100644
@@ -11,6 +11,10 @@ function basePageInit (ctx) {
       return;
   }
 
+  /* Hide the change project icon when there is only one project */
+  if (ctx.numProjects == 1){
+     $('#project .icon-pencil').hide(); 
+  }
 
   newBuildButton.show().removeAttr("disabled");
 
Signature · 1 line
1.9.1
#8 lore raw

PATCH 7/11 toaster: Initialise the 'change' icon tooltips

In reply to Alex DAMIAN

From: Belen Barros Pena <[email protected]>

All 'change' icons should have a tooltip that appears when you hover over them. The tootlip says (fittingly):
"Change". Initialise those icons in the libtoaster.js file so that they work outside the project page.

---
 lib/toaster/toastergui/static/js/libtoaster.js | 3 +++
 1 file changed, 3 insertions(+)
lib/toaster/toastergui/static/js/libtoaster.js +3 -0
index 8e76ecb..37fc80e 100644
@@ -213,6 +213,9 @@ $(document).ready(function() {
     // show task type and outcome in task details pages
     $(".task-info").tooltip({ container: 'body', html: true, delay: {show: 200}, placement: 'right' });
 
+    // initialise the tooltips for the icon-pencil icons
+    $(".icon-pencil").tooltip({ container: 'body', html: true, delay: {show: 400}, title: "Change" });
+
     // linking directly to tabs
     $(function(){
           var hash = window.location.hash;
Signature · 1 line
1.9.1
#9 lore raw

PATCH 8/11 toaster: libtoaster Add a error handler to GET in makeTypehead

In reply to Alex DAMIAN

From: Michael Wood <[email protected]>

If the JSON data comes back from the request with an error set, have a default handler which logs the error to the console.

---
 lib/toaster/toastergui/static/js/libtoaster.js | 5 +++++
 1 file changed, 5 insertions(+)
lib/toaster/toastergui/static/js/libtoaster.js +5 -0
index 37fc80e..b691a3b 100644
@@ -20,6 +20,11 @@ var libtoaster = (function (){
         source: function(query, process){
           xhrParams.value = query;
           $.getJSON(xhrUrl, this.options.xhrParams, function(data){
+            if (data.error != "ok") {
+              console.log("Error getting data from server "+data.error);
+              return;
+            }
+
             return process (data.list);
           });
         },
Signature · 1 line
1.9.1
#10 lore raw

PATCH 9/11 toaster: libtoaster Add editProject and getLayerDepsForProject

In reply to Alex DAMIAN

From: Michael Wood <[email protected]>

Add two utility functions for editing project settings and returning the layer "dependencies" for a specified layer.

---
 lib/toaster/toastergui/static/js/libtoaster.js | 49 ++++++++++++++++++++++++++
 1 file changed, 49 insertions(+)
lib/toaster/toastergui/static/js/libtoaster.js +49 -0
index b691a3b..15815b3 100644
@@ -114,11 +114,60 @@ var libtoaster = (function (){
     });
   };
 
+  /* Properties for data can be:
+   * layerDel (csv)
+   * layerAdd (csv)
+   * projectName
+   * projectVersion
+   * machineName
+   */
+  function _editProject(url, projectId, data, onSuccess, onFail){
+    $.ajax({
+        type: "POST",
+        url: url,
+        data: data,
+        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
+        success: function (data) {
+          if (data.error != "ok") {
+            console.log(data.error);
+            if (onFail != undefined)
+              onFail(data);
+          } else {
+            if (onSuccess != undefined)
+              onSuccess(data);
+          }
+        },
+        error: function (data) {
+          console.log("Call failed");
+          console.log(data);
+        }
+    });
+  };
+
+  function _getLayerDepsForProject(xhrDataTypeaheadUrl, projectId, layerId, onSuccess, onFail){
+    /* Check for dependencies not in the current project */
+    $.getJSON(xhrDataTypeaheadUrl,
+      { type: 'layerdeps', 'value': layerId , project_id: projectId },
+      function(data) {
+        if (data.error != "ok") {
+          console.log(data.error);
+          if (onFail != undefined)
+            onFail(data);
+        } else {
+          onSuccess(data);
+        }
+      }, function() {
+        console.log("E: Failed to make request");
+    });
+  };
+
   return {
     reload_params : reload_params,
     startABuild : _startABuild,
     makeTypeahead : _makeTypeahead,
     getProjectInfo: _getProjectInfo,
+    getLayerDepsForProject : _getLayerDepsForProject,
+    editProject : _editProject,
   }
 })();
 
Signature · 1 line
1.9.1
#11 lore raw

PATCH 10/11 toasterui: refactor project layer finding logic

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

This is a basic refactoring of the code computing the layer equivalence classes for a project, in order to bring common bits of logic in a single place.

---
 lib/toaster/orm/models.py       | 21 +++++++++++++++++-
 lib/toaster/toastergui/views.py | 49 +++++++++++++++--------------------------
 2 files changed, 38 insertions(+), 32 deletions(-)
lib/toaster/orm/models.py +20 -1
index f5c600b..46b704c 100644
@@ -20,7 +20,7 @@
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 from django.db import models
-from django.db.models import F
+from django.db.models import F, Q
 from django.utils import timezone
 
 
@@ -98,6 +98,25 @@ class Project(models.Model):
     def __unicode__(self):
         return "%s (%s, %s)" % (self.name, self.release, self.bitbake_version)
 
+    # returns a queryset of compatible layers for a project
+    def compatible_layerversions(self, release = None, layer_name = None):
+        if release == None:
+            release = self.release
+        # layers on the same branch or layers specifically set for this project
+        queryset = Layer_Version.objects.filter((Q(up_branch__name = release.branch_name) & Q(project = None)) | Q(project = self))
+        if layer_name is not None:
+            # we select only a layer name
+            queryset = queryset.filter(layer__name = layer_name)
+
+        # order by layer version priority
+        queryset = queryset.filter(layer_source__releaselayersourcepriority__release = release).order_by("-layer_source__releaselayersourcepriority__priority")
+
+        return queryset
+
+    # returns a set of layer-equivalent set of layers already in project
+    def projectlayer_equivalent_set(self):
+        return [j for i in [x.layercommit.get_equivalents_wpriority(self) for x in self.projectlayer_set.all()] for j in i]
+
     def schedule_build(self):
         from bldcontrol.models import BuildRequest, BRTarget, BRLayer, BRVariable, BRBitbake
         br = BuildRequest.objects.create(project = self)
lib/toaster/toastergui/views.py +18 -31

104 lines not on this page · raw message

Signature · 1 line
1.9.1
#12 lore raw

PATCH 11/11 toaster: Add import layer feature.

In reply to Alex DAMIAN

From: Michael Wood <[email protected]>

This feature allows users to import layers from git into their current project and associate it with the release of the current project and the dependencies for the newly imported layer with existing layers. It will also resolve the child dependencies of the dependencies added.

[YOCTO #6595]

---
 lib/toaster/toastergui/static/js/importlayer.js    | 190 +++++++++++++++++++++
 lib/toaster/toastergui/static/js/projectapp.js     |   6 +
 lib/toaster/toastergui/templates/importlayer.html  |  80 ++++++---
 .../toastergui/templates/layers_dep_modal.html     |  68 ++++++++
 lib/toaster/toastergui/urls.py                     |   2 +
 lib/toaster/toastergui/views.py                    |  76 +++++++++
 6 files changed, 395 insertions(+), 27 deletions(-)
 create mode 100644 lib/toaster/toastergui/static/js/importlayer.js
 create mode 100644 lib/toaster/toastergui/templates/layers_dep_modal.html
lib/toaster/toastergui/static/js/importlayer.js new file +190 -0
new file mode 100644
index 0000000..e3a0096
@@ -0,0 +1,190 @@
+"use strict"
+
+function importLayerPageInit (ctx) {
+
+  var layerDepBtn = $("#add-layer-dependency-btn");
+  var importAndAddBtn = $("#import-and-add-btn");
+  var layerNameInput = $("#layer-name");
+  var vcsURLInput = $("#layer-git-repo-url");
+  var gitRefInput = $("#layer-git-ref");
+  var summaryInput = $("#layer-summary");
+  var layerDepInput = $("#layer-dependency");
+
+  var layerDeps = {};
+  var currentLayerDepSelection;
+  var validLayerName = /^(\w|-)+$/;
+
+  libtoaster.makeTypeahead(layerDepInput, ctx.xhrDataTypeaheadUrl, { type : "layers", project_id: ctx.projectId }, function(item){
+    currentLayerDepSelection = item;
+
+    layerDepBtn.removeAttr("disabled");
+  });
+
+  layerDepBtn.click(function() {
+    if (currentLayerDepSelection == undefined)
+      return;
+
+    layerDeps[currentLayerDepSelection.id] = currentLayerDepSelection;
+
+    /* Make a list item for the new layer dependency */
+    var newLayerDep = $("<li><a></a><i class=\"icon-trash\"></i></li>");
+
+    newLayerDep.data('layer-id', currentLayerDepSelection.id);
+
+    var link = newLayerDep.children("a");
+    link.attr("href", ctx.layerDetailsUrl+String(currentLayerDepSelection.id));
+    link.text(currentLayerDepSelection.name);
+    link.tooltip({title: currentLayerDepSelection.detail});
+
+    var trashItem = newLayerDep.children("i");
+    trashItem.click(function () {
+      var toRemove = $(this).parent().data('layer-id');
+      delete layerDeps[toRemove];
+      /* also remove from db / system */
+      $(this).parent().remove();
+    });
+
+    $("#layer-deps-list").append(newLayerDep);
+
+    libtoaster.getLayerDepsForProject(ctx.xhrDataTypeaheadUrl, ctx.projectId, currentLayerDepSelection.id, function (data){
+        /* These are the layers needed to be added */
+        if (data.list.length > 0) {
+          currentLayerDepSelection.url = ctx.layerDetailsUrl+currentLayerDepSelection.id;
+          show_layer_deps_modal(ctx.projectId, currentLayerDepSelection, data.list, null);
+        }
+
+        /* Clear the current selection */
+        layerDepInput.val("");
+        currentLayerDepSelection = undefined;
+        layerDepBtn.attr("disabled","disabled");
+      }, null);
+  });
+
+  importAndAddBtn.click(function(){
+    check_layer_name_unique(layerNameInput.val(),function (isUnique){
+      if (!isUnique){
+        console.log("Error layer name not unique");
+        $("#duplicate-layer-name-hint").show();
+        importAndAddBtn.attr("disabled", "disabled");
+        return;
+      }
+
+      var layerDepsCsv = "";
+
+      /* Iter through the properties and convert to a csv */
+      for (var key in layerDeps){
+        layerDepsCsv += ','+key.toString();
+      }
+
+      /* Remove leading comma */
+      layerDepsCsv = layerDepsCsv.substr(1);
+
+      var layerData = {
+        name: layerNameInput.val(),
+        vcs_url: vcsURLInput.val(),
+        git_ref: gitRefInput.val(),
+        summary: summaryInput.val(),
+        project_id: ctx.projectId,
+        layer_deps: layerDepsCsv,
+      };
+
+      $.ajax({
+          type: "POST",
+          url: ctx.xhrImportLayerUrl,
+          data: layerData,
+          headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
+          success: function (data) {
+            if (data.error != "ok") {
+              console.log(data.error);
+            } else {
+              /* Success layer now added to the project */
+              console.log("Layer added");
+              window.location.replace(ctx.projectPageUrl+'#/layerimported='+layerData.name);
+            }
+          },
+          error: function (data) {
+            console.log("Call failed");
+            console.log(data);
+          }
+      });
+    });
+  });
+
+  function check_layer_name_unique(layerName, checkDone){
+    var isUnique = false;
+
+    $.getJSON(ctx.xhrDataTypeaheadUrl, { type: "layers", value: layerName},
+      function(data){
+        console.log(data);
+
+        if (data.error != "ok") {
+          console.log(data.error);
+          checkDone(isUnique);
+          return;
+        }
+        if (data.list.length > 0){
+          /* check through the fuzzy matches for an exact match */
+          for (var layer in data.list){
+            if (data.list[layer].name == layerName){
+              isUnique = false;
+              break;
+            }
+          }
+        } else {
+          isUnique = true;
+        }
+        checkDone(isUnique);
+    });
+  }
+
+  function check_form(){
+    var valid = false;
+    var inputs = $("input:required");
+
+    for (var i=0; i<inputs.length; i++){
+      if (!(valid = inputs[i].value)){
+        importAndAddBtn.attr("disabled", "disabled");
+        break;
+      }
+    }
+
+    if (valid)
+      importAndAddBtn.removeAttr("disabled");
+  }
+
+  vcsURLInput.keyup(function() {
+    check_form();
+  });
+
+  gitRefInput.keyup(function() {
+    check_form();
+  });
+
+  layerNameInput.keyup(function() {
+    if ($(this).val() && !validLayerName.test($(this).val())){
+      $("#invalid-layer-name-hint").show();
+      importAndAddBtn.attr("disabled", "disabled");
+      return;
+    }
+
+    $("#duplicate-layer-name-hint").hide();
+    $("#invalid-layer-name-hint").hide();
+    check_form();
+  });
+
+  /* Have a guess at the layer name */
+  vcsURLInput.focusout(function (){
+    /* If we a layer name specified don't overwrite it or if there isn't a
+     * url typed in yet return
+     */
+    if (layerNameInput.val() || !$(this).val())
+      return;
+
+    if ($(this).val().search("/")){
+      var urlPts = $(this).val().split("/");
+      var suggestion = urlPts[urlPts.length-1].replace(".git","");
+      layerNameInput.val(suggestion);
+    }
+  });
+
+}
lib/toaster/toastergui/static/js/projectapp.js +6 -0
index e9b07c7..99a61c1 100644
@@ -571,6 +571,12 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
                     "\">select targets</a> you want to build.", "alert-success");
         });
 
+        _cmdExecuteWithParam("/layerimported", function (layer) {
+            $scope.displayAlert($scope.zone1alerts,
+                    "You have imported <strong>" + layer +
+                    "</strong> and added it to your project.", "alert-success");
+        });
+
         _cmdExecuteWithParam("/targetbuild=", function (targets) {
             var oldTargetName = $scope.targetName;
             $scope.targetName = targets.split(",").join(" ");
lib/toaster/toastergui/templates/importlayer.html +53 -27

121 lines not on this page · raw message

lib/toaster/toastergui/templates/layers_dep_modal.html new file +68 -0

68 lines not on this page · raw message

lib/toaster/toastergui/urls.py +2 -0
index b60f761..6e1b0ab 100644
@@ -76,6 +76,7 @@ urlpatterns = patterns('toastergui.views',
 
         url(r'^layers/$', 'layers', name='layers'),
         url(r'^layer/(?P<layerid>\d+)/$', 'layerdetails', name='layerdetails'),
+        url(r'^layer/$', 'layerdetails', name='layerdetails'),
         url(r'^targets/$', 'targets', name='targets'),
         url(r'^machines/$', 'machines', name='machines'),
 
@@ -92,6 +93,7 @@ urlpatterns = patterns('toastergui.views',
         url(r'^xhr_projectedit/(?P<pid>\d+)/$', 'xhr_projectedit', name='xhr_projectedit'),
 
         url(r'^xhr_datatypeahead/$', 'xhr_datatypeahead', name='xhr_datatypeahead'),
+        url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'),
 
 
         # default redirection
lib/toaster/toastergui/views.py +76 -0

82 lines not on this page · raw message

Signature · 1 line
1.9.1
#13 lore raw
In reply to Alex DAMIAN

Hello,

Can you please DO NOT MERGE this patchset ?

I have a wrong patch in there - I'll be re-submitting shortly

Cheers,
Alex

Quoted 99 lines · Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

Hello,

This is a patchset with new features and bug fixes. Patches have been individually
reviewed on the toaster mailing list.

Among the features submitted, we have the import layers pages, the download artifacts
patchset, a new command line option for bitbake to write event log files, and corresponding
command to import log files into toaster, as well as new debug options.

We also have fixes around the layout and refactoring to make code cleaner.

Can you please pull at your convenience ?

Thanks,
Alex

The following changes since commit
9e27bb2869e8ec6781d2f68d0585ebbf9ca6f3d8:

bitbake-user-manual-metadata.xml: Updated do_package_write example
(2014-12-09 22:25:11 +0000)

are available in the git repository at:

git://git.yoctoproject.org/poky-contrib adamian/20141210-submission

http://git.yoctoproject.org/cgit.cgi/poky-contrib/log/?h=adamian/20141210-submission

Alexandru DAMIAN (6):
add build artifacts table and other improvements
toastergui: implement UI changes to allow file download add option to write offline event log file
add POST endpoint for uploading eventlog files
toasterui: add extra debug and development infos
toasterui: refactor project layer finding logic

Belen Barros Pena (2):
toaster: base Only show change project icon when > one project
toaster: Initialise the 'change' icon tooltips

Michael Wood (3):
toaster: libtoaster Add a error handler to GET in makeTypehead
toaster: libtoaster Add editProject and getLayerDepsForProject
toaster: Add import layer feature.

 bin/bitbake                                        |   7 +-
 bin/toaster-eventreplay                            | 179 +++++++++++
 lib/bb/cooker.py                                   |  75 ++++-
 lib/bb/cookerdata.py                               |   1 +
 lib/bb/ui/buildinfohelper.py                       |  83 +++--
 lib/bb/ui/toasterui.py                             |  25 +-
 lib/toaster/bldcontrol/localhostbecontroller.py    |  27 +-
 .../management/commands/checksettings.py           |   2 +-
 .../bldcontrol/management/commands/runbuilds.py    |  13 +-
 lib/toaster/bldcontrol/models.py                   |  10 +-
 .../orm/migrations/0019_auto__add_buildartifact.py | 342
+++++++++++++++++++++
 lib/toaster/orm/models.py                          |  50 ++-
 lib/toaster/orm/urls.py                            |  27 ++
 lib/toaster/orm/views.py                           |  60 ++++
 lib/toaster/toastergui/static/js/base.js           |   4 +
 lib/toaster/toastergui/static/js/importlayer.js    | 190 ++++++++++++
 lib/toaster/toastergui/static/js/libtoaster.js     |  57 ++++
 lib/toaster/toastergui/static/js/projectapp.js     |   6 +
 lib/toaster/toastergui/templates/base.html         |  18 +-
 lib/toaster/toastergui/templates/build.html        |  29 +-
 .../toastergui/templates/builddashboard.html       | 131 +++++---
 .../toastergui/templates/configuration.html        |   6 +-
 lib/toaster/toastergui/templates/importlayer.html  |  80 +++--
 .../toastergui/templates/layers_dep_modal.html     |  68 ++++
 .../toastergui/templates/package_detail_base.html  |   3 +
 lib/toaster/toastergui/templates/recipe.html       |  11 +-
 lib/toaster/toastergui/templates/recipes.html      |   7 +-
 lib/toaster/toastergui/templates/target.html       |   8 +-
 lib/toaster/toastergui/templates/task.html         |  18 +-
 lib/toaster/toastergui/templates/tasks.html        |  12 +-
 lib/toaster/toastergui/urls.py                     |   2 +
 lib/toaster/toastergui/views.py                    | 293
++++++++++++------
 lib/toaster/toastermain/settings.py                |  27 +-
 lib/toaster/toastermain/urls.py                    |  10 +-
 34 files changed, 1630 insertions(+), 251 deletions(-)
 create mode 100755 bin/toaster-eventreplay
 create mode 100644
lib/toaster/orm/migrations/0019_auto__add_buildartifact.py
 create mode 100644 lib/toaster/orm/urls.py
 create mode 100644 lib/toaster/orm/views.py
 create mode 100644 lib/toaster/toastergui/static/js/importlayer.js
 create mode 100644 lib/toaster/toastergui/templates/layers_dep_modal.html

--
1.9.1

Signature · 3 lines
Alex Damian
Yocto Project
SSG / OTC