Complete Yocto mirror with license table for TQMa6UL (2038-compliance)
- 264 license table entries with exact download URLs (224/264 resolved) - Complete sources/ directory with all BitBake recipes - Build configuration: tqma6ul-multi-mba6ulx, spaetzle (musl) - Full traceability for Softwarefreigabeantrag - GCC 13.4.0, Linux 6.6.102, U-Boot 2023.04, musl 1.2.4 - License distribution: GPL-2.0 (24), MIT (23), GPL-2.0+ (18), BSD-3 (16)
This commit is contained in:
11
sources/poky/bitbake/lib/toaster/bldcontrol/admin.py
Normal file
11
sources/poky/bitbake/lib/toaster/bldcontrol/admin.py
Normal file
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
|
||||
from django.contrib import admin
|
||||
from .models import BuildEnvironment
|
||||
|
||||
class BuildEnvironmentAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
admin.site.register(BuildEnvironment, BuildEnvironmentAdmin)
|
||||
126
sources/poky/bitbake/lib/toaster/bldcontrol/bbcontroller.py
Normal file
126
sources/poky/bitbake/lib/toaster/bldcontrol/bbcontroller.py
Normal file
@@ -0,0 +1,126 @@
|
||||
#
|
||||
# BitBake Toaster Implementation
|
||||
#
|
||||
# Copyright (C) 2014 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
from django.db.models import Q
|
||||
from bldcontrol.models import BuildEnvironment, BRLayer, 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
|
||||
|
||||
@@ -0,0 +1,516 @@
|
||||
#
|
||||
# BitBake Toaster Implementation
|
||||
#
|
||||
# Copyright (C) 2014 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
from bldcontrol.models import BuildEnvironment, BuildRequest, Build
|
||||
from orm.models import CustomImageRecipe, Layer, Layer_Version, Project, ToasterSetting
|
||||
from orm.models import signal_runbuilds
|
||||
import subprocess
|
||||
|
||||
from toastermain import settings
|
||||
|
||||
from bldcontrol.bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger("toaster")
|
||||
|
||||
install_dir = os.environ.get('TOASTER_DIR')
|
||||
|
||||
from pprint import 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,repo_name):
|
||||
bitbake.req.build.repos_cloned=current
|
||||
bitbake.req.build.repos_to_clone=total
|
||||
bitbake.req.build.progress_item=repo_name
|
||||
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 = []
|
||||
layer_index = 0
|
||||
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, 0))
|
||||
|
||||
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( "%03d:%s" % (layer_index,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,layer_index) )
|
||||
layer_index += 1
|
||||
|
||||
|
||||
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,gitrepos[(giturl, commit)][0][0])
|
||||
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)
|
||||
# NOTE: this nice-to-have check breaks when using git remaping to get past firewall
|
||||
# Re-enable later with .gitconfig remapping checks
|
||||
#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))
|
||||
pass
|
||||
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, index in gitrepos[(giturl, commit)]:
|
||||
localdirpath = os.path.join(localdirname, dirpath)
|
||||
logger.debug("localhostbecontroller: localdirpath expects '%s'" % localdirpath)
|
||||
if not os.path.exists(localdirpath):
|
||||
raise BuildSetupException("Cannot find layer git path '%s' in checked out repository '%s:%s'. Exiting." % (localdirpath, giturl, commit))
|
||||
|
||||
if name != "bitbake":
|
||||
layerlist.append("%03d:%s" % (index,localdirpath.rstrip("/")))
|
||||
|
||||
self.setCloneStatus(bitbake,'complete',clone_total,clone_count,'')
|
||||
logger.debug("localhostbecontroller: current layer list %s " % pformat(layerlist))
|
||||
|
||||
# Resolve self.pokydirname if not resolved yet, consider the scenario
|
||||
# where all layers are local, that's the else clause
|
||||
if self.pokydirname is None:
|
||||
if 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
|
||||
else:
|
||||
# Alternatively, scan local layers for relative "oe-init-build-env" location
|
||||
for layer in layers:
|
||||
if os.path.exists(os.path.join(layer.layer_version.layer.local_source_dir,"..","oe-init-build-env")):
|
||||
logger.debug("localhostbecontroller, setting pokydirname to %s" % (layer.layer_version.layer.local_source_dir))
|
||||
self.pokydirname = os.path.join(layer.layer_version.layer.local_source_dir,"..")
|
||||
break
|
||||
else:
|
||||
logger.error("pokydirname is not set, you will run into trouble!")
|
||||
|
||||
# 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("%03d:%s" % (layer_index,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
|
||||
|
||||
# restore the order of layer list for bblayers.conf
|
||||
layerlist.sort()
|
||||
sorted_layerlist = [l[4:] for l in layerlist]
|
||||
return sorted_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)
|
||||
is_merged_attr = bitbake.req.project.merged_attr
|
||||
|
||||
git_env = os.environ.copy()
|
||||
# (note: add custom environment settings here)
|
||||
try:
|
||||
# insure that the project init/build uses the selected bitbake, and not Toaster's
|
||||
del git_env['TEMPLATECONF']
|
||||
del git_env['BBBASEDIR']
|
||||
del git_env['BUILDDIR']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# init build environment from the clone
|
||||
if bitbake.req.project.builddir:
|
||||
builddir = bitbake.req.project.builddir
|
||||
else:
|
||||
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),env=git_env)
|
||||
except ToasterSetting.DoesNotExist:
|
||||
self._shellcmd("bash -c 'source %s %s'" % (oe_init, builddir),
|
||||
self.be.sourcedir,env=git_env)
|
||||
|
||||
# update bblayers.conf
|
||||
if not is_merged_attr:
|
||||
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"')
|
||||
else:
|
||||
# Append the Toaster-specific values directly to the bblayers.conf
|
||||
bblconfpath = os.path.join(builddir, "conf/bblayers.conf")
|
||||
bblconfpath_save = os.path.join(builddir, "conf/bblayers.conf.save")
|
||||
shutil.copyfile(bblconfpath, bblconfpath_save)
|
||||
with open(bblconfpath) as bblayers:
|
||||
content = bblayers.readlines()
|
||||
do_write = True
|
||||
was_toaster = False
|
||||
with open(bblconfpath,'w') as bblayers:
|
||||
for line in content:
|
||||
#line = line.strip('\n')
|
||||
if 'TOASTER_CONFIG_PROLOG' in line:
|
||||
do_write = False
|
||||
was_toaster = True
|
||||
elif 'TOASTER_CONFIG_EPILOG' in line:
|
||||
do_write = True
|
||||
elif do_write:
|
||||
bblayers.write(line)
|
||||
if not was_toaster:
|
||||
bblayers.write('\n')
|
||||
bblayers.write('#=== TOASTER_CONFIG_PROLOG ===\n')
|
||||
bblayers.write('BBLAYERS = "\\\n')
|
||||
for layer in layers:
|
||||
bblayers.write(' %s \\\n' % layer)
|
||||
bblayers.write(' "\n')
|
||||
bblayers.write('#=== TOASTER_CONFIG_EPILOG ===\n')
|
||||
# Append the Toaster-specific values directly to the local.conf
|
||||
bbconfpath = os.path.join(builddir, "conf/local.conf")
|
||||
bbconfpath_save = os.path.join(builddir, "conf/local.conf.save")
|
||||
shutil.copyfile(bbconfpath, bbconfpath_save)
|
||||
with open(bbconfpath) as f:
|
||||
content = f.readlines()
|
||||
do_write = True
|
||||
was_toaster = False
|
||||
with open(bbconfpath,'w') as conf:
|
||||
for line in content:
|
||||
#line = line.strip('\n')
|
||||
if 'TOASTER_CONFIG_PROLOG' in line:
|
||||
do_write = False
|
||||
was_toaster = True
|
||||
elif 'TOASTER_CONFIG_EPILOG' in line:
|
||||
do_write = True
|
||||
elif do_write:
|
||||
conf.write(line)
|
||||
if not was_toaster:
|
||||
conf.write('\n')
|
||||
conf.write('#=== TOASTER_CONFIG_PROLOG ===\n')
|
||||
for var in variables:
|
||||
if (not var.name.startswith("INTERNAL_")) and (not var.name == "BBLAYERS"):
|
||||
conf.write('%s="%s"\n' % (var.name, var.value))
|
||||
conf.write('#=== TOASTER_CONFIG_EPILOG ===\n')
|
||||
|
||||
# If 'target' is just the project preparation target, then we are done
|
||||
for target in targets:
|
||||
if "_PROJECT_PREPARE_" == target.target:
|
||||
logger.debug('localhostbecontroller: Project has been prepared. Done.')
|
||||
# Update the Build Request and release the build environment
|
||||
bitbake.req.state = BuildRequest.REQ_COMPLETED
|
||||
bitbake.req.save()
|
||||
self.be.lock = BuildEnvironment.LOCK_FREE
|
||||
self.be.save()
|
||||
# Close the project build and progress bar
|
||||
bitbake.req.build.outcome = Build.SUCCEEDED
|
||||
bitbake.req.build.save()
|
||||
# Update the project status
|
||||
bitbake.req.project.set_variable(Project.PROJECT_SPECIFIC_STATUS,Project.PROJECT_SPECIFIC_CLONING_SUCCESS)
|
||||
signal_runbuilds()
|
||||
return
|
||||
|
||||
# clean the Toaster to build environment
|
||||
env_clean = 'unset BBPATH;' # clean BBPATH for <= YP-2.4.0
|
||||
|
||||
# run bitbake server from the clone if available
|
||||
# otherwise pick it from the PATH
|
||||
bitbake = os.path.join(self.pokydirname, 'bitbake', 'bin', 'bitbake')
|
||||
if not os.path.exists(bitbake):
|
||||
logger.info("Bitbake not available under %s, will try to use it from PATH" %
|
||||
self.pokydirname)
|
||||
for path in os.environ["PATH"].split(os.pathsep):
|
||||
if os.path.exists(os.path.join(path, 'bitbake')):
|
||||
bitbake = os.path.join(path, 'bitbake')
|
||||
break
|
||||
else:
|
||||
logger.error("Looks like Bitbake is not available, please fix your environment")
|
||||
|
||||
toasterlayers = os.path.join(builddir,"conf/toaster-bblayers.conf")
|
||||
if not is_merged_attr:
|
||||
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)
|
||||
else:
|
||||
self._shellcmd('%s bash -c \"source %s %s; BITBAKE_UI="knotty" %s '
|
||||
'--server-only -B 0.0.0.0:0\"' % (env_clean, oe_init,
|
||||
builddir, bitbake), 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'. Exiting." % 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')
|
||||
if not is_merged_attr:
|
||||
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)
|
||||
else:
|
||||
self._shellcmd(['%s bash -c \"(TOASTER_BRBE="%s" BBSERVER="0.0.0.0:%s" '
|
||||
'%s %s -u toasterui --token="" >>%s 2>&1;'
|
||||
'BITBAKE_UI="knotty" BBSERVER=0.0.0.0:%s %s -m)&\"' \
|
||||
% (env_clean, brbe, self.be.bbport, local_bitbake, bbtargets, log,
|
||||
self.be.bbport, bitbake,)],
|
||||
builddir, nowait=True)
|
||||
|
||||
logger.debug('localhostbecontroller: Build launched, exiting. '
|
||||
'Follow build logs at %s' % log)
|
||||
@@ -0,0 +1,170 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from django.core.management import call_command
|
||||
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", "")
|
||||
custom_xml_only = os.environ.get("CUSTOM_XML_ONLY")
|
||||
|
||||
if ToasterSetting.objects.filter(name='CUSTOM_XML_ONLY').count() > 0 or custom_xml_only is not None:
|
||||
# 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
|
||||
@@ -0,0 +1,275 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
|
||||
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 (%s)" % e)
|
||||
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.warning("runbuilds: cleanup exception %s" % str(e))
|
||||
|
||||
try:
|
||||
self.archive()
|
||||
except Exception as e:
|
||||
logger.warning("runbuilds: archive exception %s" % str(e))
|
||||
|
||||
try:
|
||||
self.schedule()
|
||||
except Exception as e:
|
||||
logger.warning("runbuilds: schedule exception %s" % str(e))
|
||||
|
||||
# Test to see if a build pre-maturely died due to a bitbake crash
|
||||
def check_dead_builds(self):
|
||||
do_cleanup = False
|
||||
try:
|
||||
for br in BuildRequest.objects.filter(state=BuildRequest.REQ_INPROGRESS):
|
||||
# Get the build directory
|
||||
if br.project.builddir:
|
||||
builddir = br.project.builddir
|
||||
else:
|
||||
builddir = '%s-toaster-%d' % (br.environment.builddir,br.project.id)
|
||||
# Check log to see if there is a recent traceback
|
||||
toaster_ui_log = os.path.join(builddir, 'toaster_ui.log')
|
||||
test_file = os.path.join(builddir, '._toaster_check.txt')
|
||||
os.system("tail -n 50 %s > %s" % (os.path.join(builddir, 'toaster_ui.log'),test_file))
|
||||
traceback_text = ''
|
||||
is_traceback = False
|
||||
with open(test_file,'r') as test_file_fd:
|
||||
test_file_tail = test_file_fd.readlines()
|
||||
for line in test_file_tail:
|
||||
if line.startswith('Traceback (most recent call last):'):
|
||||
traceback_text = line
|
||||
is_traceback = True
|
||||
elif line.startswith('NOTE: ToasterUI waiting for events'):
|
||||
# Ignore any traceback before new build start
|
||||
traceback_text = ''
|
||||
is_traceback = False
|
||||
elif line.startswith('Note: Toaster traceback auto-stop'):
|
||||
# Ignore any traceback before this previous traceback catch
|
||||
traceback_text = ''
|
||||
is_traceback = False
|
||||
elif is_traceback:
|
||||
traceback_text += line
|
||||
# Test the results
|
||||
is_stop = False
|
||||
if is_traceback:
|
||||
# Found a traceback
|
||||
errtype = 'Bitbake crash'
|
||||
errmsg = 'Bitbake crash\n' + traceback_text
|
||||
state = BuildRequest.REQ_FAILED
|
||||
# Clean up bitbake files
|
||||
bitbake_lock = os.path.join(builddir, 'bitbake.lock')
|
||||
if os.path.isfile(bitbake_lock):
|
||||
os.remove(bitbake_lock)
|
||||
bitbake_sock = os.path.join(builddir, 'bitbake.sock')
|
||||
if os.path.isfile(bitbake_sock):
|
||||
os.remove(bitbake_sock)
|
||||
if os.path.isfile(test_file):
|
||||
os.remove(test_file)
|
||||
# Add note to ignore this traceback on next check
|
||||
os.system('echo "Note: Toaster traceback auto-stop" >> %s' % toaster_ui_log)
|
||||
is_stop = True
|
||||
# Add more tests here
|
||||
#elif ...
|
||||
# Stop the build request?
|
||||
if is_stop:
|
||||
brerror = BRError(
|
||||
req = br,
|
||||
errtype = errtype,
|
||||
errmsg = errmsg,
|
||||
traceback = traceback_text,
|
||||
)
|
||||
brerror.save()
|
||||
br.state = state
|
||||
br.save()
|
||||
do_cleanup = True
|
||||
# Do cleanup
|
||||
if do_cleanup:
|
||||
self.cleanup()
|
||||
except Exception as e:
|
||||
logger.error("runbuilds: Error in check_dead_builds %s" % 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())
|
||||
|
||||
# Clean up any stale/failed builds from previous Toaster run
|
||||
self.runbuild()
|
||||
|
||||
signal.signal(signal.SIGUSR1, lambda sig, frame: None)
|
||||
|
||||
while True:
|
||||
sigset = signal.sigtimedwait([signal.SIGUSR1], 5)
|
||||
if sigset:
|
||||
for sig in sigset:
|
||||
# Consume each captured pending event
|
||||
self.runbuild()
|
||||
else:
|
||||
# Check for build exceptions
|
||||
self.check_dead_builds()
|
||||
|
||||
@@ -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, on_delete=models.CASCADE)),
|
||||
],
|
||||
),
|
||||
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', on_delete=models.CASCADE)),
|
||||
('environment', models.ForeignKey(to='bldcontrol.BuildEnvironment', null=True, on_delete=models.CASCADE)),
|
||||
('project', models.ForeignKey(to='orm.Project', on_delete=models.CASCADE)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='brvariable',
|
||||
name='req',
|
||||
field=models.ForeignKey(to='bldcontrol.BuildRequest', on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='brtarget',
|
||||
name='req',
|
||||
field=models.ForeignKey(to='bldcontrol.BuildRequest', on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='brlayer',
|
||||
name='req',
|
||||
field=models.ForeignKey(to='bldcontrol.BuildRequest', on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='brerror',
|
||||
name='req',
|
||||
field=models.ForeignKey(to='bldcontrol.BuildRequest', on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='brbitbake',
|
||||
name='req',
|
||||
field=models.OneToOneField(to='bldcontrol.BuildRequest', on_delete=models.CASCADE),
|
||||
),
|
||||
]
|
||||
@@ -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')]),
|
||||
),
|
||||
]
|
||||
@@ -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')]),
|
||||
),
|
||||
]
|
||||
@@ -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')]),
|
||||
),
|
||||
]
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,48 @@
|
||||
# Generated by Django 3.2.12 on 2022-03-06 03:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bldcontrol', '0007_brlayers_optional_gitinfo'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='brbitbake',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='brerror',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='brlayer',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='brtarget',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='brvariable',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='buildenvironment',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='buildrequest',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
]
|
||||
165
sources/poky/bitbake/lib/toaster/bldcontrol/models.py
Normal file
165
sources/poky/bitbake/lib/toaster/bldcontrol/models.py
Normal file
@@ -0,0 +1,165 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from django.db import models
|
||||
from django.utils.encoding import force_str
|
||||
from orm.models import Project, 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, on_delete=models.CASCADE)
|
||||
build = models.OneToOneField(Build, on_delete=models.CASCADE, null = True) # TODO: toasterui should set this when Build is created
|
||||
environment = models.ForeignKey(BuildEnvironment, on_delete=models.CASCADE, 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_str('%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, on_delete=models.CASCADE)
|
||||
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, on_delete=models.CASCADE, null=True)
|
||||
|
||||
class BRBitbake(models.Model):
|
||||
req = models.OneToOneField(BuildRequest, on_delete=models.CASCADE) # 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, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=100)
|
||||
value = models.TextField(blank = True)
|
||||
|
||||
class BRTarget(models.Model):
|
||||
req = models.ForeignKey(BuildRequest, on_delete=models.CASCADE)
|
||||
target = models.CharField(max_length=100)
|
||||
task = models.CharField(max_length=100, null=True)
|
||||
|
||||
class BRError(models.Model):
|
||||
req = models.ForeignKey(BuildRequest, on_delete=models.CASCADE)
|
||||
errtype = models.CharField(max_length=100)
|
||||
errmsg = models.TextField()
|
||||
traceback = models.TextField()
|
||||
|
||||
def __str__(self):
|
||||
return "%s (%s)" % (self.errmsg, self.req)
|
||||
5
sources/poky/bitbake/lib/toaster/bldcontrol/views.py
Normal file
5
sources/poky/bitbake/lib/toaster/bldcontrol/views.py
Normal file
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
|
||||
# Create your views here.
|
||||
Reference in New Issue
Block a user