diff options
author | Dave Cobbley <david.j.cobbley@linux.intel.com> | 2018-08-14 20:05:37 +0300 |
---|---|---|
committer | Brad Bishop <bradleyb@fuzziesquirrel.com> | 2018-08-23 04:26:31 +0300 |
commit | eb8dc40360f0cfef56fb6947cc817a547d6d9bc6 (patch) | |
tree | de291a73dc37168da6370e2cf16c347d1eba9df8 /poky/bitbake/lib/toaster/bldcontrol | |
parent | 9c3cf826d853102535ead04cebc2d6023eff3032 (diff) | |
download | openbmc-eb8dc40360f0cfef56fb6947cc817a547d6d9bc6.tar.xz |
[Subtree] Removing import-layers directory
As part of the move to subtrees, need to bring all the import layers
content to the top level.
Change-Id: I4a163d10898cbc6e11c27f776f60e1a470049d8f
Signed-off-by: Dave Cobbley <david.j.cobbley@linux.intel.com>
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
Diffstat (limited to 'poky/bitbake/lib/toaster/bldcontrol')
18 files changed, 1325 insertions, 0 deletions
diff --git a/poky/bitbake/lib/toaster/bldcontrol/__init__.py b/poky/bitbake/lib/toaster/bldcontrol/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/__init__.py diff --git a/poky/bitbake/lib/toaster/bldcontrol/admin.py b/poky/bitbake/lib/toaster/bldcontrol/admin.py new file mode 100644 index 000000000..fcbe5f593 --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin +from django.contrib.admin.filters import RelatedFieldListFilter +from .models import BuildEnvironment + +class BuildEnvironmentAdmin(admin.ModelAdmin): + pass + +admin.site.register(BuildEnvironment, BuildEnvironmentAdmin) diff --git a/poky/bitbake/lib/toaster/bldcontrol/bbcontroller.py b/poky/bitbake/lib/toaster/bldcontrol/bbcontroller.py new file mode 100644 index 000000000..5195600d9 --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/bbcontroller.py @@ -0,0 +1,142 @@ +# +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# 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. + + +import os +import sys +import re +from django.db import transaction +from django.db.models import Q +from bldcontrol.models import BuildEnvironment, BRLayer, BRVariable, BRTarget, BRBitbake + +# load Bitbake components +path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) +sys.path.insert(0, path) + +class BitbakeController(object): + """ This is the basic class that controlls a bitbake server. + It is outside the scope of this class on how the server is started and aquired + """ + + def __init__(self, be): + import bb.server.xmlrpcclient + self.connection = bb.server.xmlrpcclient._create_server(be.bbaddress, + int(be.bbport))[0] + + def _runCommand(self, command): + result, error = self.connection.runCommand(command) + if error: + raise Exception(error) + return result + + def disconnect(self): + return self.connection.removeClient() + + def setVariable(self, name, value): + return self._runCommand(["setVariable", name, value]) + + def getVariable(self, name): + return self._runCommand(["getVariable", name]) + + def triggerEvent(self, event): + return self._runCommand(["triggerEvent", event]) + + def build(self, targets, task = None): + if task is None: + task = "build" + return self._runCommand(["buildTargets", targets, task]) + + def forceShutDown(self): + return self._runCommand(["stateForceShutdown"]) + + + +def getBuildEnvironmentController(**kwargs): + """ Gets you a BuildEnvironmentController that encapsulates a build environment, + based on the query dictionary sent in. + + This is used to retrieve, for example, the currently running BE from inside + the toaster UI, or find a new BE to start a new build in it. + + The return object MUST always be a BuildEnvironmentController. + """ + + from bldcontrol.localhostbecontroller import LocalhostBEController + + be = BuildEnvironment.objects.filter(Q(**kwargs))[0] + if be.betype == BuildEnvironment.TYPE_LOCAL: + return LocalhostBEController(be) + else: + raise Exception("FIXME: Implement BEC for type %s" % str(be.betype)) + + +class BuildEnvironmentController(object): + """ BuildEnvironmentController (BEC) is the abstract class that defines the operations that MUST + or SHOULD be supported by a Build Environment. It is used to establish the framework, and must + not be instantiated directly by the user. + + Use the "getBuildEnvironmentController()" function to get a working BEC for your remote. + + How the BuildEnvironments are discovered is outside the scope of this class. + + You must derive this class to teach Toaster how to operate in your own infrastructure. + We provide some specific BuildEnvironmentController classes that can be used either to + directly set-up Toaster infrastructure, or as a model for your own infrastructure set: + + * Localhost controller will run the Toaster BE on the same account as the web server + (current user if you are using the the Django development web server) + on the local machine, with the "build/" directory under the "poky/" source checkout directory. + Bash is expected to be available. + + """ + def __init__(self, be): + """ Takes a BuildEnvironment object as parameter that points to the settings of the BE. + """ + self.be = be + self.connection = None + + def setLayers(self, bitbake, ls): + """ Checks-out bitbake executor and layers from git repositories. + Sets the layer variables in the config file, after validating local layer paths. + bitbake must be a single BRBitbake instance + The layer paths must be in a list of BRLayer object + + a word of attention: by convention, the first layer for any build will be poky! + """ + raise NotImplementedError("FIXME: Must override setLayers") + + def getArtifact(self, path): + """ This call returns an artifact identified by the 'path'. How 'path' is interpreted as + up to the implementing BEC. The return MUST be a REST URL where a GET will actually return + the content of the artifact, e.g. for use as a "download link" in a web UI. + """ + raise NotImplementedError("Must return the REST URL of the artifact") + + def triggerBuild(self, bitbake, layers, variables, targets): + raise NotImplementedError("Must override BE release") + +class ShellCmdException(Exception): + pass + + +class BuildSetupException(Exception): + pass + diff --git a/poky/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py b/poky/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py new file mode 100644 index 000000000..16c7c8044 --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py @@ -0,0 +1,401 @@ +# +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# 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. + + +import os +import sys +import re +import shutil +import time +from django.db import transaction +from django.db.models import Q +from bldcontrol.models import BuildEnvironment, BRLayer, BRVariable, BRTarget, BRBitbake +from orm.models import CustomImageRecipe, Layer, Layer_Version, ProjectLayer, ToasterSetting +import subprocess + +from toastermain import settings + +from bldcontrol.bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException, BitbakeController + +import logging +logger = logging.getLogger("toaster") + +from pprint import pprint, pformat + +class LocalhostBEController(BuildEnvironmentController): + """ Implementation of the BuildEnvironmentController for the localhost; + this controller manages the default build directory, + the server setup and system start and stop for the localhost-type build environment + + """ + + def __init__(self, be): + super(LocalhostBEController, self).__init__(be) + self.pokydirname = None + self.islayerset = False + + def _shellcmd(self, command, cwd=None, nowait=False,env=None): + if cwd is None: + cwd = self.be.sourcedir + if env is None: + env=os.environ.copy() + + logger.debug("lbc_shellcmd: (%s) %s" % (cwd, command)) + p = subprocess.Popen(command, cwd = cwd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + if nowait: + return + (out,err) = p.communicate() + p.wait() + if p.returncode: + if len(err) == 0: + err = "command: %s \n%s" % (command, out) + else: + err = "command: %s \n%s" % (command, err) + logger.warning("localhostbecontroller: shellcmd error %s" % err) + raise ShellCmdException(err) + else: + logger.debug("localhostbecontroller: shellcmd success") + return out.decode('utf-8') + + def getGitCloneDirectory(self, url, branch): + """Construct unique clone directory name out of url and branch.""" + if branch != "HEAD": + return "_toaster_clones/_%s_%s" % (re.sub('[:/@+%]', '_', url), branch) + + # word of attention; this is a localhost-specific issue; only on the localhost we expect to have "HEAD" releases + # which _ALWAYS_ means the current poky checkout + from os.path import dirname as DN + local_checkout_path = DN(DN(DN(DN(DN(os.path.abspath(__file__)))))) + #logger.debug("localhostbecontroller: using HEAD checkout in %s" % local_checkout_path) + return local_checkout_path + + + def setCloneStatus(self,bitbake,status,total,current): + bitbake.req.build.repos_cloned=current + bitbake.req.build.repos_to_clone=total + bitbake.req.build.save() + + def setLayers(self, bitbake, layers, targets): + """ a word of attention: by convention, the first layer for any build will be poky! """ + + assert self.be.sourcedir is not None + + layerlist = [] + nongitlayerlist = [] + git_env = os.environ.copy() + # (note: add custom environment settings here) + + # set layers in the layersource + + # 1. get a list of repos with branches, and map dirpaths for each layer + gitrepos = {} + + # if we're using a remotely fetched version of bitbake add its git + # details to the list of repos to clone + if bitbake.giturl and bitbake.commit: + gitrepos[(bitbake.giturl, bitbake.commit)] = [] + gitrepos[(bitbake.giturl, bitbake.commit)].append( + ("bitbake", bitbake.dirpath)) + + for layer in layers: + # We don't need to git clone the layer for the CustomImageRecipe + # as it's generated by us layer on if needed + if CustomImageRecipe.LAYER_NAME in layer.name: + continue + + # If we have local layers then we don't need clone them + # For local layers giturl will be empty + if not layer.giturl: + nongitlayerlist.append(layer.layer_version.layer.local_source_dir) + continue + + if not (layer.giturl, layer.commit) in gitrepos: + gitrepos[(layer.giturl, layer.commit)] = [] + gitrepos[(layer.giturl, layer.commit)].append( (layer.name, layer.dirpath) ) + + + logger.debug("localhostbecontroller, our git repos are %s" % pformat(gitrepos)) + + + # 2. Note for future use if the current source directory is a + # checked-out git repos that could match a layer's vcs_url and therefore + # be used to speed up cloning (rather than fetching it again). + + cached_layers = {} + + try: + for remotes in self._shellcmd("git remote -v", self.be.sourcedir,env=git_env).split("\n"): + try: + remote = remotes.split("\t")[1].split(" ")[0] + if remote not in cached_layers: + cached_layers[remote] = self.be.sourcedir + except IndexError: + pass + except ShellCmdException: + # ignore any errors in collecting git remotes this is an optional + # step + pass + + logger.info("Using pre-checked out source for layer %s", cached_layers) + + # 3. checkout the repositories + clone_count=0 + clone_total=len(gitrepos.keys()) + self.setCloneStatus(bitbake,'Started',clone_total,clone_count) + for giturl, commit in gitrepos.keys(): + self.setCloneStatus(bitbake,'progress',clone_total,clone_count) + clone_count += 1 + + localdirname = os.path.join(self.be.sourcedir, self.getGitCloneDirectory(giturl, commit)) + logger.debug("localhostbecontroller: giturl %s:%s checking out in current directory %s" % (giturl, commit, localdirname)) + + # see if our directory is a git repository + if os.path.exists(localdirname): + try: + localremotes = self._shellcmd("git remote -v", + localdirname,env=git_env) + if not giturl in localremotes and commit != 'HEAD': + raise BuildSetupException("Existing git repository at %s, but with different remotes ('%s', expected '%s'). Toaster will not continue out of fear of damaging something." % (localdirname, ", ".join(localremotes.split("\n")), giturl)) + except ShellCmdException: + # our localdirname might not be a git repository + #- that's fine + pass + else: + if giturl in cached_layers: + logger.debug("localhostbecontroller git-copying %s to %s" % (cached_layers[giturl], localdirname)) + self._shellcmd("git clone \"%s\" \"%s\"" % (cached_layers[giturl], localdirname),env=git_env) + self._shellcmd("git remote remove origin", localdirname,env=git_env) + self._shellcmd("git remote add origin \"%s\"" % giturl, localdirname,env=git_env) + else: + logger.debug("localhostbecontroller: cloning %s in %s" % (giturl, localdirname)) + self._shellcmd('git clone "%s" "%s"' % (giturl, localdirname),env=git_env) + + # branch magic name "HEAD" will inhibit checkout + if commit != "HEAD": + logger.debug("localhostbecontroller: checking out commit %s to %s " % (commit, localdirname)) + ref = commit if re.match('^[a-fA-F0-9]+$', commit) else 'origin/%s' % commit + self._shellcmd('git fetch && git reset --hard "%s"' % ref, localdirname,env=git_env) + + # 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")): + 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')): + logger.debug("localhostbecontroller: checking bitbake into the poky dirname %s " % self.pokydirname) + self._shellcmd("git clone -b \"%s\" \"%s\" \"%s\" " % (bitbake.commit, bitbake.giturl, os.path.join(self.pokydirname, 'bitbake')),env=git_env) + + # verify our repositories + for name, dirpath in gitrepos[(giturl, commit)]: + localdirpath = os.path.join(localdirname, dirpath) + logger.debug("localhostbecontroller: localdirpath expected '%s'" % localdirpath) + if not os.path.exists(localdirpath): + raise BuildSetupException("Cannot find layer git path '%s' in checked out repository '%s:%s'. Aborting." % (localdirpath, giturl, commit)) + + if name != "bitbake": + layerlist.append(localdirpath.rstrip("/")) + + self.setCloneStatus(bitbake,'complete',clone_total,clone_count) + logger.debug("localhostbecontroller: current layer list %s " % pformat(layerlist)) + + if self.pokydirname is None and os.path.exists(os.path.join(self.be.sourcedir, "oe-init-build-env")): + logger.debug("localhostbecontroller: selected poky dir name %s" % self.be.sourcedir) + self.pokydirname = self.be.sourcedir + + # 5. create custom layer and add custom recipes to it + for target in targets: + try: + customrecipe = CustomImageRecipe.objects.get( + name=target.target, + project=bitbake.req.project) + + custom_layer_path = self.setup_custom_image_recipe( + customrecipe, layers) + + if os.path.isdir(custom_layer_path): + layerlist.append(custom_layer_path) + + except CustomImageRecipe.DoesNotExist: + continue # not a custom recipe, skip + + layerlist.extend(nongitlayerlist) + logger.debug("\n\nset layers gives this list %s" % pformat(layerlist)) + self.islayerset = True + return layerlist + + def setup_custom_image_recipe(self, customrecipe, layers): + """ Set up toaster-custom-images layer and recipe files """ + layerpath = os.path.join(self.be.builddir, + CustomImageRecipe.LAYER_NAME) + + # create directory structure + for name in ("conf", "recipes"): + path = os.path.join(layerpath, name) + if not os.path.isdir(path): + os.makedirs(path) + + # create layer.conf + config = os.path.join(layerpath, "conf", "layer.conf") + if not os.path.isfile(config): + with open(config, "w") as conf: + conf.write('BBPATH .= ":${LAYERDIR}"\nBBFILES += "${LAYERDIR}/recipes/*.bb"\n') + + # Update the Layer_Version dirpath that has our base_recipe in + # to be able to read the base recipe to then generate the + # custom recipe. + br_layer_base_recipe = layers.get( + layer_version=customrecipe.base_recipe.layer_version) + + # If the layer is one that we've cloned we know where it lives + if br_layer_base_recipe.giturl and br_layer_base_recipe.commit: + layer_path = self.getGitCloneDirectory( + br_layer_base_recipe.giturl, + br_layer_base_recipe.commit) + # Otherwise it's a local layer + elif br_layer_base_recipe.local_source_dir: + layer_path = br_layer_base_recipe.local_source_dir + else: + logger.error("Unable to workout the dir path for the custom" + " image recipe") + + br_layer_base_dirpath = os.path.join( + self.be.sourcedir, + layer_path, + customrecipe.base_recipe.layer_version.dirpath) + + customrecipe.base_recipe.layer_version.dirpath = br_layer_base_dirpath + + customrecipe.base_recipe.layer_version.save() + + # create recipe + recipe_path = os.path.join(layerpath, "recipes", "%s.bb" % + customrecipe.name) + with open(recipe_path, "w") as recipef: + recipef.write(customrecipe.generate_recipe_file_contents()) + + # Update the layer and recipe objects + customrecipe.layer_version.dirpath = layerpath + customrecipe.layer_version.layer.local_source_dir = layerpath + customrecipe.layer_version.layer.save() + customrecipe.layer_version.save() + + customrecipe.file_path = recipe_path + customrecipe.save() + + return layerpath + + + def readServerLogFile(self): + return open(os.path.join(self.be.builddir, "toaster_server.log"), "r").read() + + + def triggerBuild(self, bitbake, layers, variables, targets, brbe): + layers = self.setLayers(bitbake, layers, targets) + + # init build environment from the clone + builddir = '%s-toaster-%d' % (self.be.builddir, bitbake.req.project.id) + oe_init = os.path.join(self.pokydirname, 'oe-init-build-env') + # init build environment + try: + custom_script = ToasterSetting.objects.get(name="CUSTOM_BUILD_INIT_SCRIPT").value + custom_script = custom_script.replace("%BUILDDIR%" ,builddir) + self._shellcmd("bash -c 'source %s'" % (custom_script)) + except ToasterSetting.DoesNotExist: + self._shellcmd("bash -c 'source %s %s'" % (oe_init, builddir), + self.be.sourcedir) + + # update bblayers.conf + bblconfpath = os.path.join(builddir, "conf/toaster-bblayers.conf") + with open(bblconfpath, 'w') as bblayers: + bblayers.write('# line added by toaster build control\n' + 'BBLAYERS = "%s"' % ' '.join(layers)) + + # write configuration file + confpath = os.path.join(builddir, 'conf/toaster.conf') + with open(confpath, 'w') as conf: + for var in variables: + conf.write('%s="%s"\n' % (var.name, var.value)) + conf.write('INHERIT+="toaster buildhistory"') + + # clean the Toaster to build environment + env_clean = 'unset BBPATH;' # clean BBPATH for <= YP-2.4.0 + + # run bitbake server from the clone + bitbake = os.path.join(self.pokydirname, 'bitbake', 'bin', 'bitbake') + toasterlayers = os.path.join(builddir,"conf/toaster-bblayers.conf") + self._shellcmd('%s bash -c \"source %s %s; BITBAKE_UI="knotty" %s --read %s --read %s ' + '--server-only -B 0.0.0.0:0\"' % (env_clean, oe_init, + builddir, bitbake, confpath, toasterlayers), self.be.sourcedir) + + # read port number from bitbake.lock + self.be.bbport = -1 + bblock = os.path.join(builddir, 'bitbake.lock') + # allow 10 seconds for bb lock file to appear but also be populated + for lock_check in range(10): + if not os.path.exists(bblock): + logger.debug("localhostbecontroller: waiting for bblock file to appear") + time.sleep(1) + continue + if 10 < os.stat(bblock).st_size: + break + logger.debug("localhostbecontroller: waiting for bblock content to appear") + time.sleep(1) + else: + raise BuildSetupException("Cannot find bitbake server lock file '%s'. Aborting." % bblock) + + with open(bblock) as fplock: + for line in fplock: + if ":" in line: + self.be.bbport = line.split(":")[-1].strip() + logger.debug("localhostbecontroller: bitbake port %s", self.be.bbport) + break + + if -1 == self.be.bbport: + raise BuildSetupException("localhostbecontroller: can't read bitbake port from %s" % bblock) + + self.be.bbaddress = "localhost" + self.be.bbstate = BuildEnvironment.SERVER_STARTED + self.be.lock = BuildEnvironment.LOCK_RUNNING + self.be.save() + + bbtargets = '' + for target in targets: + task = target.task + if task: + if not task.startswith('do_'): + task = 'do_' + task + task = ':%s' % task + bbtargets += '%s%s ' % (target.target, task) + + # run build with local bitbake. stop the server after the build. + log = os.path.join(builddir, 'toaster_ui.log') + local_bitbake = os.path.join(os.path.dirname(os.getenv('BBBASEDIR')), + 'bitbake') + self._shellcmd(['%s bash -c \"(TOASTER_BRBE="%s" BBSERVER="0.0.0.0:%s" ' + '%s %s -u toasterui --read %s --read %s --token="" >>%s 2>&1;' + 'BITBAKE_UI="knotty" BBSERVER=0.0.0.0:%s %s -m)&\"' \ + % (env_clean, brbe, self.be.bbport, local_bitbake, bbtargets, confpath, toasterlayers, log, + self.be.bbport, bitbake,)], + builddir, nowait=True) + + logger.debug('localhostbecontroller: Build launched, exiting. ' + 'Follow build logs at %s' % log) diff --git a/poky/bitbake/lib/toaster/bldcontrol/management/__init__.py b/poky/bitbake/lib/toaster/bldcontrol/management/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/management/__init__.py diff --git a/poky/bitbake/lib/toaster/bldcontrol/management/commands/__init__.py b/poky/bitbake/lib/toaster/bldcontrol/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/management/commands/__init__.py diff --git a/poky/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py b/poky/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py new file mode 100644 index 000000000..823c6f154 --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py @@ -0,0 +1,167 @@ +from django.core.management.base import BaseCommand, CommandError +from django.db import transaction + +from django.core.management import call_command +from bldcontrol.bbcontroller import getBuildEnvironmentController, ShellCmdException +from bldcontrol.models import BuildRequest, BuildEnvironment, BRError +from orm.models import ToasterSetting, Build, Layer + +import os +import traceback +import warnings + + +def DN(path): + if path is None: + return "" + else: + return os.path.dirname(path) + + +class Command(BaseCommand): + args = "" + help = "Verifies that the configured settings are valid and usable, or prompts the user to fix the settings." + + def __init__(self, *args, **kwargs): + super(Command, self).__init__(*args, **kwargs) + self.guesspath = DN(DN(DN(DN(DN(DN(DN(__file__))))))) + + def _verify_build_environment(self): + # provide a local build env. This will be extended later to include non local + if BuildEnvironment.objects.count() == 0: + BuildEnvironment.objects.create(betype=BuildEnvironment.TYPE_LOCAL) + + # we make sure we have builddir and sourcedir for all defined build envionments + for be in BuildEnvironment.objects.all(): + be.needs_import = False + def _verify_be(): + is_changed = False + + def _update_sourcedir(): + be.sourcedir = os.environ.get('TOASTER_DIR') + return True + + if len(be.sourcedir) == 0: + is_changed = _update_sourcedir() + + if not be.sourcedir.startswith("/"): + print("\n -- Validation: The layers checkout directory must be set to an absolute path.") + is_changed = _update_sourcedir() + + if is_changed: + if be.betype == BuildEnvironment.TYPE_LOCAL: + be.needs_import = True + return True + + def _update_builddir(): + be.builddir = os.environ.get('TOASTER_DIR')+"/build" + return True + + if len(be.builddir) == 0: + is_changed = _update_builddir() + + if not be.builddir.startswith("/"): + print("\n -- Validation: The build directory must to be set to an absolute path.") + is_changed = _update_builddir() + + if is_changed: + print("\nBuild configuration saved") + be.save() + return True + + if be.needs_import: + try: + print("Loading default settings") + call_command("loaddata", "settings") + template_conf = os.environ.get("TEMPLATECONF", "") + + if ToasterSetting.objects.filter(name='CUSTOM_XML_ONLY').count() > 0: + # only use the custom settings + pass + elif "poky" in template_conf: + print("Loading poky configuration") + call_command("loaddata", "poky") + else: + print("Loading OE-Core configuration") + call_command("loaddata", "oe-core") + if template_conf: + oe_core_path = os.path.realpath( + template_conf + + "/../") + else: + print("TEMPLATECONF not found. You may have to" + " manually configure layer paths") + oe_core_path = input("Please enter the path of" + " your openembedded-core " + "layer: ") + # Update the layer instances of openemebedded-core + for layer in Layer.objects.filter( + name="openembedded-core", + local_source_dir="OE-CORE-LAYER-DIR"): + layer.local_path = oe_core_path + layer.save() + + # Import the custom fixture if it's present + with warnings.catch_warnings(): + warnings.filterwarnings( + action="ignore", + message="^.*No fixture named.*$") + print("Importing custom settings if present") + try: + call_command("loaddata", "custom") + except: + print("NOTE: optional fixture 'custom' not found") + + # we run lsupdates after config update + print("\nFetching information from the layer index, " + "please wait.\nYou can re-update any time later " + "by running bitbake/lib/toaster/manage.py " + "lsupdates\n") + call_command("lsupdates") + + # we don't look for any other config files + return is_changed + except Exception as e: + print("Failure while trying to setup toaster: %s" + % e) + traceback.print_exc() + + return is_changed + + while _verify_be(): + pass + return 0 + + def _verify_default_settings(self): + # verify that default settings are there + if ToasterSetting.objects.filter(name='DEFAULT_RELEASE').count() != 1: + ToasterSetting.objects.filter(name='DEFAULT_RELEASE').delete() + ToasterSetting.objects.get_or_create(name='DEFAULT_RELEASE', value='') + return 0 + + def _verify_builds_in_progress(self): + # we are just starting up. we must not have any builds in progress, or build environments taken + for b in BuildRequest.objects.filter(state=BuildRequest.REQ_INPROGRESS): + BRError.objects.create(req=b, errtype="toaster", + errmsg= + "Toaster found this build IN PROGRESS while Toaster started up. This is an inconsistent state, and the build was marked as failed") + + BuildRequest.objects.filter(state=BuildRequest.REQ_INPROGRESS).update(state=BuildRequest.REQ_FAILED) + + BuildEnvironment.objects.update(lock=BuildEnvironment.LOCK_FREE) + + # also mark "In Progress builds as failures" + from django.utils import timezone + Build.objects.filter(outcome=Build.IN_PROGRESS).update(outcome=Build.FAILED, completed_on=timezone.now()) + + return 0 + + + + def handle(self, **options): + retval = 0 + retval += self._verify_build_environment() + retval += self._verify_default_settings() + retval += self._verify_builds_in_progress() + + return retval diff --git a/poky/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py b/poky/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py new file mode 100644 index 000000000..791e53eab --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py @@ -0,0 +1,192 @@ +from django.core.management.base import BaseCommand +from django.db import transaction +from django.db.models import Q + +from bldcontrol.bbcontroller import getBuildEnvironmentController +from bldcontrol.models import BuildRequest, BuildEnvironment +from bldcontrol.models import BRError, BRVariable + +from orm.models import Build, LogMessage, Target + +import logging +import traceback +import signal +import os + +logger = logging.getLogger("toaster") + + +class Command(BaseCommand): + args = "" + help = "Schedules and executes build requests as possible. "\ + "Does not return (interrupt with Ctrl-C)" + + @transaction.atomic + def _selectBuildEnvironment(self): + bec = getBuildEnvironmentController(lock=BuildEnvironment.LOCK_FREE) + bec.be.lock = BuildEnvironment.LOCK_LOCK + bec.be.save() + return bec + + @transaction.atomic + def _selectBuildRequest(self): + br = BuildRequest.objects.filter(state=BuildRequest.REQ_QUEUED).first() + return br + + def schedule(self): + try: + # select the build environment and the request to build + br = self._selectBuildRequest() + if br: + br.state = BuildRequest.REQ_INPROGRESS + br.save() + else: + return + + try: + bec = self._selectBuildEnvironment() + except IndexError as e: + # we could not find a BEC; postpone the BR + br.state = BuildRequest.REQ_QUEUED + br.save() + logger.debug("runbuilds: No build env") + return + + logger.info("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() + + # this triggers an async build + bec.triggerBuild(br.brbitbake, br.brlayer_set.all(), + br.brvariable_set.all(), br.brtarget_set.all(), + "%d:%d" % (br.pk, bec.be.pk)) + + except Exception as e: + logger.error("runbuilds: Error launching build %s" % e) + traceback.print_exc() + if "[Errno 111] Connection refused" in str(e): + # Connection refused, read toaster_server.out + errmsg = bec.readServerLogFile() + else: + errmsg = str(e) + + BRError.objects.create(req=br, errtype=str(type(e)), errmsg=errmsg, + traceback=traceback.format_exc()) + br.state = BuildRequest.REQ_FAILED + br.save() + bec.be.lock = BuildEnvironment.LOCK_FREE + bec.be.save() + # Cancel the pending build and report the exception to the UI + log_object = LogMessage.objects.create( + build = br.build, + level = LogMessage.EXCEPTION, + message = errmsg) + log_object.save() + br.build.outcome = Build.FAILED + br.build.save() + + def archive(self): + for br in BuildRequest.objects.filter(state=BuildRequest.REQ_ARCHIVE): + if br.build is None: + br.state = BuildRequest.REQ_FAILED + else: + br.state = BuildRequest.REQ_COMPLETED + br.save() + + def cleanup(self): + from django.utils import timezone + from datetime import timedelta + # environments locked for more than 30 seconds + # they should be unlocked + BuildEnvironment.objects.filter( + Q(buildrequest__state__in=[BuildRequest.REQ_FAILED, + BuildRequest.REQ_COMPLETED, + BuildRequest.REQ_CANCELLING]) & + Q(lock=BuildEnvironment.LOCK_LOCK) & + Q(updated__lt=timezone.now() - timedelta(seconds=30)) + ).update(lock=BuildEnvironment.LOCK_FREE) + + # update all Builds that were in progress and failed to start + for br in BuildRequest.objects.filter( + state=BuildRequest.REQ_FAILED, + build__outcome=Build.IN_PROGRESS): + # transpose the launch errors in ToasterExceptions + br.build.outcome = Build.FAILED + for brerror in br.brerror_set.all(): + logger.debug("Saving error %s" % brerror) + LogMessage.objects.create(build=br.build, + level=LogMessage.EXCEPTION, + message=brerror.errmsg) + br.build.save() + + # we don't have a true build object here; hence, toasterui + # didn't have a change to release the BE lock + br.environment.lock = BuildEnvironment.LOCK_FREE + br.environment.save() + + # update all BuildRequests without a build created + for br in BuildRequest.objects.filter(build=None): + br.build = Build.objects.create(project=br.project, + completed_on=br.updated, + started_on=br.created) + br.build.outcome = Build.FAILED + try: + br.build.machine = br.brvariable_set.get(name='MACHINE').value + except BRVariable.DoesNotExist: + pass + br.save() + # transpose target information + for brtarget in br.brtarget_set.all(): + Target.objects.create(build=br.build, + target=brtarget.target, + task=brtarget.task) + # transpose the launch errors in ToasterExceptions + for brerror in br.brerror_set.all(): + LogMessage.objects.create(build=br.build, + level=LogMessage.EXCEPTION, + message=brerror.errmsg) + + br.build.save() + + # Make sure the LOCK is removed for builds which have been fully + # cancelled + for br in BuildRequest.objects.filter( + Q(build__outcome=Build.CANCELLED) & + Q(state=BuildRequest.REQ_CANCELLING) & + ~Q(environment=None)): + br.environment.lock = BuildEnvironment.LOCK_FREE + br.environment.save() + + def runbuild(self): + try: + self.cleanup() + except Exception as e: + logger.warn("runbuilds: cleanup exception %s" % str(e)) + + try: + self.archive() + except Exception as e: + logger.warn("runbuilds: archive exception %s" % str(e)) + + try: + self.schedule() + except Exception as e: + logger.warn("runbuilds: schedule exception %s" % str(e)) + + def handle(self, **options): + pidfile_path = os.path.join(os.environ.get("BUILDDIR", "."), + ".runbuilds.pid") + + with open(pidfile_path, 'w') as pidfile: + pidfile.write("%s" % os.getpid()) + + self.runbuild() + + signal.signal(signal.SIGUSR1, lambda sig, frame: None) + + while True: + signal.pause() + self.runbuild() diff --git a/poky/bitbake/lib/toaster/bldcontrol/migrations/0001_initial.py b/poky/bitbake/lib/toaster/bldcontrol/migrations/0001_initial.py new file mode 100644 index 000000000..67db37856 --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/migrations/0001_initial.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orm', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='BRBitbake', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('giturl', models.CharField(max_length=254)), + ('commit', models.CharField(max_length=254)), + ('dirpath', models.CharField(max_length=254)), + ], + ), + migrations.CreateModel( + name='BRError', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('errtype', models.CharField(max_length=100)), + ('errmsg', models.TextField()), + ('traceback', models.TextField()), + ], + ), + migrations.CreateModel( + name='BRLayer', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=100)), + ('giturl', models.CharField(max_length=254)), + ('commit', models.CharField(max_length=254)), + ('dirpath', models.CharField(max_length=254)), + ('layer_version', models.ForeignKey(to='orm.Layer_Version', null=True)), + ], + ), + migrations.CreateModel( + name='BRTarget', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('target', models.CharField(max_length=100)), + ('task', models.CharField(max_length=100, null=True)), + ], + ), + migrations.CreateModel( + name='BRVariable', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=100)), + ('value', models.TextField(blank=True)), + ], + ), + migrations.CreateModel( + name='BuildEnvironment', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('address', models.CharField(max_length=254)), + ('betype', models.IntegerField(choices=[(0, b'local'), (1, b'ssh')])), + ('bbaddress', models.CharField(max_length=254, blank=True)), + ('bbport', models.IntegerField(default=-1)), + ('bbtoken', models.CharField(max_length=126, blank=True)), + ('bbstate', models.IntegerField(default=0, choices=[(0, b'stopped'), (1, b'started')])), + ('sourcedir', models.CharField(max_length=512, blank=True)), + ('builddir', models.CharField(max_length=512, blank=True)), + ('lock', models.IntegerField(default=0, choices=[(0, b'free'), (1, b'lock'), (2, b'running')])), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ], + ), + migrations.CreateModel( + name='BuildRequest', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('state', models.IntegerField(default=0, choices=[(0, b'created'), (1, b'queued'), (2, b'in progress'), (3, b'completed'), (4, b'failed'), (5, b'deleted'), (6, b'archive')])), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('build', models.OneToOneField(null=True, to='orm.Build')), + ('environment', models.ForeignKey(to='bldcontrol.BuildEnvironment', null=True)), + ('project', models.ForeignKey(to='orm.Project')), + ], + ), + migrations.AddField( + model_name='brvariable', + name='req', + field=models.ForeignKey(to='bldcontrol.BuildRequest'), + ), + migrations.AddField( + model_name='brtarget', + name='req', + field=models.ForeignKey(to='bldcontrol.BuildRequest'), + ), + migrations.AddField( + model_name='brlayer', + name='req', + field=models.ForeignKey(to='bldcontrol.BuildRequest'), + ), + migrations.AddField( + model_name='brerror', + name='req', + field=models.ForeignKey(to='bldcontrol.BuildRequest'), + ), + migrations.AddField( + model_name='brbitbake', + name='req', + field=models.OneToOneField(to='bldcontrol.BuildRequest'), + ), + ] diff --git a/poky/bitbake/lib/toaster/bldcontrol/migrations/0002_auto_20160120_1250.py b/poky/bitbake/lib/toaster/bldcontrol/migrations/0002_auto_20160120_1250.py new file mode 100644 index 000000000..0c2475aba --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/migrations/0002_auto_20160120_1250.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bldcontrol', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='buildenvironment', + name='betype', + field=models.IntegerField(choices=[(0, b'local')]), + ), + ] diff --git a/poky/bitbake/lib/toaster/bldcontrol/migrations/0003_add_cancelling_state.py b/poky/bitbake/lib/toaster/bldcontrol/migrations/0003_add_cancelling_state.py new file mode 100644 index 000000000..eec9216ca --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/migrations/0003_add_cancelling_state.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bldcontrol', '0002_auto_20160120_1250'), + ] + + operations = [ + migrations.AlterField( + model_name='buildrequest', + name='state', + field=models.IntegerField(default=0, choices=[(0, b'created'), (1, b'queued'), (2, b'in progress'), (3, b'completed'), (4, b'failed'), (5, b'deleted'), (6, b'cancelling'), (7, b'archive')]), + ), + ] diff --git a/poky/bitbake/lib/toaster/bldcontrol/migrations/0004_auto_20160523_1446.py b/poky/bitbake/lib/toaster/bldcontrol/migrations/0004_auto_20160523_1446.py new file mode 100644 index 000000000..3d9062954 --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/migrations/0004_auto_20160523_1446.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bldcontrol', '0003_add_cancelling_state'), + ] + + operations = [ + migrations.AlterField( + model_name='buildenvironment', + name='bbstate', + field=models.IntegerField(default=0, choices=[(0, 'stopped'), (1, 'started')]), + ), + migrations.AlterField( + model_name='buildenvironment', + name='betype', + field=models.IntegerField(choices=[(0, 'local')]), + ), + migrations.AlterField( + model_name='buildenvironment', + name='lock', + field=models.IntegerField(default=0, choices=[(0, 'free'), (1, 'lock'), (2, 'running')]), + ), + migrations.AlterField( + model_name='buildrequest', + name='state', + field=models.IntegerField(default=0, choices=[(0, 'created'), (1, 'queued'), (2, 'in progress'), (3, 'completed'), (4, 'failed'), (5, 'deleted'), (6, 'cancelling'), (7, 'archive')]), + ), + ] diff --git a/poky/bitbake/lib/toaster/bldcontrol/migrations/0005_reorder_buildrequest_states.py b/poky/bitbake/lib/toaster/bldcontrol/migrations/0005_reorder_buildrequest_states.py new file mode 100644 index 000000000..4bb951776 --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/migrations/0005_reorder_buildrequest_states.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bldcontrol', '0004_auto_20160523_1446'), + ] + + operations = [ + migrations.AlterField( + model_name='buildrequest', + name='state', + field=models.IntegerField(choices=[(0, 'created'), (1, 'queued'), (2, 'in progress'), (3, 'failed'), (4, 'deleted'), (5, 'cancelling'), (6, 'completed'), (7, 'archive')], default=0), + ), + ] diff --git a/poky/bitbake/lib/toaster/bldcontrol/migrations/0006_brlayer_local_source_dir.py b/poky/bitbake/lib/toaster/bldcontrol/migrations/0006_brlayer_local_source_dir.py new file mode 100644 index 000000000..2460002f0 --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/migrations/0006_brlayer_local_source_dir.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bldcontrol', '0005_reorder_buildrequest_states'), + ] + + operations = [ + migrations.AddField( + model_name='brlayer', + name='local_source_dir', + field=models.CharField(max_length=254, null=True), + ), + ] diff --git a/poky/bitbake/lib/toaster/bldcontrol/migrations/0007_brlayers_optional_gitinfo.py b/poky/bitbake/lib/toaster/bldcontrol/migrations/0007_brlayers_optional_gitinfo.py new file mode 100644 index 000000000..4be42a4cf --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/migrations/0007_brlayers_optional_gitinfo.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bldcontrol', '0006_brlayer_local_source_dir'), + ] + + operations = [ + migrations.AlterField( + model_name='brlayer', + name='commit', + field=models.CharField(max_length=254, null=True), + ), + migrations.AlterField( + model_name='brlayer', + name='dirpath', + field=models.CharField(max_length=254, null=True), + ), + migrations.AlterField( + model_name='brlayer', + name='giturl', + field=models.CharField(max_length=254, null=True), + ), + ] diff --git a/poky/bitbake/lib/toaster/bldcontrol/migrations/__init__.py b/poky/bitbake/lib/toaster/bldcontrol/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/migrations/__init__.py diff --git a/poky/bitbake/lib/toaster/bldcontrol/models.py b/poky/bitbake/lib/toaster/bldcontrol/models.py new file mode 100644 index 000000000..409614b9e --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/models.py @@ -0,0 +1,162 @@ +from __future__ import unicode_literals +from django.db import models +from django.core.validators import MaxValueValidator, MinValueValidator +from django.utils.encoding import force_text +from orm.models import Project, ProjectLayer, ProjectVariable, ProjectTarget, Build, Layer_Version + +import logging +logger = logging.getLogger("toaster") +# a BuildEnvironment is the equivalent of the "build/" directory on the localhost +class BuildEnvironment(models.Model): + SERVER_STOPPED = 0 + SERVER_STARTED = 1 + SERVER_STATE = ( + (SERVER_STOPPED, "stopped"), + (SERVER_STARTED, "started"), + ) + + TYPE_LOCAL = 0 + TYPE = ( + (TYPE_LOCAL, "local"), + ) + + LOCK_FREE = 0 + LOCK_LOCK = 1 + LOCK_RUNNING = 2 + LOCK_STATE = ( + (LOCK_FREE, "free"), + (LOCK_LOCK, "lock"), + (LOCK_RUNNING, "running"), + ) + + address = models.CharField(max_length = 254) + betype = models.IntegerField(choices = TYPE) + bbaddress = models.CharField(max_length = 254, blank = True) + bbport = models.IntegerField(default = -1) + bbtoken = models.CharField(max_length = 126, blank = True) + bbstate = models.IntegerField(choices = SERVER_STATE, default = SERVER_STOPPED) + sourcedir = models.CharField(max_length = 512, blank = True) + builddir = models.CharField(max_length = 512, blank = True) + lock = models.IntegerField(choices = LOCK_STATE, default = LOCK_FREE) + created = models.DateTimeField(auto_now_add = True) + updated = models.DateTimeField(auto_now = True) + + def get_artifact(self, path): + if self.betype == BuildEnvironment.TYPE_LOCAL: + return open(path, "r") + raise NotImplementedError("FIXME: artifact download not implemented "\ + "for build environment type %s" % \ + self.get_betype_display()) + + def has_artifact(self, path): + import os + if self.betype == BuildEnvironment.TYPE_LOCAL: + return os.path.exists(path) + raise NotImplementedError("FIXME: has artifact not implemented for "\ + "build environment type %s" % \ + self.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 + +class BuildRequest(models.Model): + REQ_CREATED = 0 + REQ_QUEUED = 1 + REQ_INPROGRESS = 2 + REQ_FAILED = 3 + REQ_DELETED = 4 + REQ_CANCELLING = 5 + REQ_COMPLETED = 6 + REQ_ARCHIVE = 7 + + REQUEST_STATE = ( + (REQ_CREATED, "created"), + (REQ_QUEUED, "queued"), + (REQ_INPROGRESS, "in progress"), + (REQ_FAILED, "failed"), + (REQ_DELETED, "deleted"), + (REQ_CANCELLING, "cancelling"), + (REQ_COMPLETED, "completed"), + (REQ_ARCHIVE, "archive"), + ) + + search_allowed_fields = ("brtarget__target", "build__project__name") + + project = models.ForeignKey(Project) + build = models.OneToOneField(Build, null = True) # TODO: toasterui should set this when Build is created + environment = models.ForeignKey(BuildEnvironment, null = True) + state = models.IntegerField(choices = REQUEST_STATE, default = REQ_CREATED) + created = models.DateTimeField(auto_now_add = True) + updated = models.DateTimeField(auto_now = True) + + def __init__(self, *args, **kwargs): + super(BuildRequest, self).__init__(*args, **kwargs) + # Save the old state in case it's about to be modified + self.old_state = self.state + + def save(self, *args, **kwargs): + # Check that the state we're trying to set is not going backwards + # e.g. from REQ_FAILED to REQ_INPROGRESS + if self.old_state != self.state and self.old_state > self.state: + logger.warning("Invalid state change requested: " + "Cannot go from %s to %s - ignoring request" % + (BuildRequest.REQUEST_STATE[self.old_state][1], + BuildRequest.REQUEST_STATE[self.state][1]) + ) + # Set property back to the old value + self.state = self.old_state + return + + super(BuildRequest, self).save(*args, **kwargs) + + + def get_duration(self): + return (self.updated - self.created).total_seconds() + + def get_sorted_target_list(self): + tgts = self.brtarget_set.order_by( 'target' ); + return( tgts ); + + def get_machine(self): + return self.brvariable_set.get(name="MACHINE").value + + def __str__(self): + return force_text('%s %s' % (self.project, self.get_state_display())) + +# These tables specify the settings for running an actual build. +# They MUST be kept in sync with the tables in orm.models.Project* + + +class BRLayer(models.Model): + req = models.ForeignKey(BuildRequest) + name = models.CharField(max_length=100) + giturl = models.CharField(max_length=254, null=True) + local_source_dir = models.CharField(max_length=254, null=True) + commit = models.CharField(max_length=254, null=True) + dirpath = models.CharField(max_length=254, null=True) + layer_version = models.ForeignKey(Layer_Version, null=True) + +class BRBitbake(models.Model): + req = models.OneToOneField(BuildRequest) # only one bitbake for a request + giturl = models.CharField(max_length =254) + commit = models.CharField(max_length = 254) + dirpath = models.CharField(max_length = 254) + +class BRVariable(models.Model): + req = models.ForeignKey(BuildRequest) + name = models.CharField(max_length=100) + value = models.TextField(blank = True) + +class BRTarget(models.Model): + req = models.ForeignKey(BuildRequest) + target = models.CharField(max_length=100) + task = models.CharField(max_length=100, null=True) + +class BRError(models.Model): + req = models.ForeignKey(BuildRequest) + errtype = models.CharField(max_length=100) + errmsg = models.TextField() + traceback = models.TextField() + + def __str__(self): + return "%s (%s)" % (self.errmsg, self.req) diff --git a/poky/bitbake/lib/toaster/bldcontrol/views.py b/poky/bitbake/lib/toaster/bldcontrol/views.py new file mode 100644 index 000000000..60f00ef0e --- /dev/null +++ b/poky/bitbake/lib/toaster/bldcontrol/views.py @@ -0,0 +1 @@ +# Create your views here. |