xref: /OK3568_Linux_fs/yocto/poky/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#
2*4882a593Smuzhiyun# BitBake Toaster Implementation
3*4882a593Smuzhiyun#
4*4882a593Smuzhiyun# Copyright (C) 2014        Intel Corporation
5*4882a593Smuzhiyun#
6*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
7*4882a593Smuzhiyun#
8*4882a593Smuzhiyun
9*4882a593Smuzhiyunimport os
10*4882a593Smuzhiyunimport re
11*4882a593Smuzhiyunimport shutil
12*4882a593Smuzhiyunimport time
13*4882a593Smuzhiyunfrom bldcontrol.models import BuildEnvironment, BuildRequest, Build
14*4882a593Smuzhiyunfrom orm.models import CustomImageRecipe, Layer, Layer_Version, Project, ToasterSetting
15*4882a593Smuzhiyunfrom orm.models import signal_runbuilds
16*4882a593Smuzhiyunimport subprocess
17*4882a593Smuzhiyun
18*4882a593Smuzhiyunfrom toastermain import settings
19*4882a593Smuzhiyun
20*4882a593Smuzhiyunfrom bldcontrol.bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException
21*4882a593Smuzhiyun
22*4882a593Smuzhiyunimport logging
23*4882a593Smuzhiyunlogger = logging.getLogger("toaster")
24*4882a593Smuzhiyun
25*4882a593Smuzhiyuninstall_dir = os.environ.get('TOASTER_DIR')
26*4882a593Smuzhiyun
27*4882a593Smuzhiyunfrom pprint import pformat
28*4882a593Smuzhiyun
29*4882a593Smuzhiyunclass LocalhostBEController(BuildEnvironmentController):
30*4882a593Smuzhiyun    """ Implementation of the BuildEnvironmentController for the localhost;
31*4882a593Smuzhiyun        this controller manages the default build directory,
32*4882a593Smuzhiyun        the server setup and system start and stop for the localhost-type build environment
33*4882a593Smuzhiyun
34*4882a593Smuzhiyun    """
35*4882a593Smuzhiyun
36*4882a593Smuzhiyun    def __init__(self, be):
37*4882a593Smuzhiyun        super(LocalhostBEController, self).__init__(be)
38*4882a593Smuzhiyun        self.pokydirname = None
39*4882a593Smuzhiyun        self.islayerset = False
40*4882a593Smuzhiyun
41*4882a593Smuzhiyun    def _shellcmd(self, command, cwd=None, nowait=False,env=None):
42*4882a593Smuzhiyun        if cwd is None:
43*4882a593Smuzhiyun            cwd = self.be.sourcedir
44*4882a593Smuzhiyun        if env is None:
45*4882a593Smuzhiyun            env=os.environ.copy()
46*4882a593Smuzhiyun
47*4882a593Smuzhiyun        logger.debug("lbc_shellcmd: (%s) %s" % (cwd, command))
48*4882a593Smuzhiyun        p = subprocess.Popen(command, cwd = cwd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
49*4882a593Smuzhiyun        if nowait:
50*4882a593Smuzhiyun            return
51*4882a593Smuzhiyun        (out,err) = p.communicate()
52*4882a593Smuzhiyun        p.wait()
53*4882a593Smuzhiyun        if p.returncode:
54*4882a593Smuzhiyun            if len(err) == 0:
55*4882a593Smuzhiyun                err = "command: %s \n%s" % (command, out)
56*4882a593Smuzhiyun            else:
57*4882a593Smuzhiyun                err = "command: %s \n%s" % (command, err)
58*4882a593Smuzhiyun            logger.warning("localhostbecontroller: shellcmd error %s" % err)
59*4882a593Smuzhiyun            raise ShellCmdException(err)
60*4882a593Smuzhiyun        else:
61*4882a593Smuzhiyun            logger.debug("localhostbecontroller: shellcmd success")
62*4882a593Smuzhiyun            return out.decode('utf-8')
63*4882a593Smuzhiyun
64*4882a593Smuzhiyun    def getGitCloneDirectory(self, url, branch):
65*4882a593Smuzhiyun        """Construct unique clone directory name out of url and branch."""
66*4882a593Smuzhiyun        if branch != "HEAD":
67*4882a593Smuzhiyun            return "_toaster_clones/_%s_%s" % (re.sub('[:/@+%]', '_', url), branch)
68*4882a593Smuzhiyun
69*4882a593Smuzhiyun        # word of attention; this is a localhost-specific issue; only on the localhost we expect to have "HEAD" releases
70*4882a593Smuzhiyun        # which _ALWAYS_ means the current poky checkout
71*4882a593Smuzhiyun        from os.path import dirname as DN
72*4882a593Smuzhiyun        local_checkout_path = DN(DN(DN(DN(DN(os.path.abspath(__file__))))))
73*4882a593Smuzhiyun        #logger.debug("localhostbecontroller: using HEAD checkout in %s" % local_checkout_path)
74*4882a593Smuzhiyun        return local_checkout_path
75*4882a593Smuzhiyun
76*4882a593Smuzhiyun    def setCloneStatus(self,bitbake,status,total,current,repo_name):
77*4882a593Smuzhiyun        bitbake.req.build.repos_cloned=current
78*4882a593Smuzhiyun        bitbake.req.build.repos_to_clone=total
79*4882a593Smuzhiyun        bitbake.req.build.progress_item=repo_name
80*4882a593Smuzhiyun        bitbake.req.build.save()
81*4882a593Smuzhiyun
82*4882a593Smuzhiyun    def setLayers(self, bitbake, layers, targets):
83*4882a593Smuzhiyun        """ a word of attention: by convention, the first layer for any build will be poky! """
84*4882a593Smuzhiyun
85*4882a593Smuzhiyun        assert self.be.sourcedir is not None
86*4882a593Smuzhiyun
87*4882a593Smuzhiyun        layerlist = []
88*4882a593Smuzhiyun        nongitlayerlist = []
89*4882a593Smuzhiyun        layer_index = 0
90*4882a593Smuzhiyun        git_env = os.environ.copy()
91*4882a593Smuzhiyun        # (note: add custom environment settings here)
92*4882a593Smuzhiyun
93*4882a593Smuzhiyun        # set layers in the layersource
94*4882a593Smuzhiyun
95*4882a593Smuzhiyun        # 1. get a list of repos with branches, and map dirpaths for each layer
96*4882a593Smuzhiyun        gitrepos = {}
97*4882a593Smuzhiyun
98*4882a593Smuzhiyun        # if we're using a remotely fetched version of bitbake add its git
99*4882a593Smuzhiyun        # details to the list of repos to clone
100*4882a593Smuzhiyun        if bitbake.giturl and bitbake.commit:
101*4882a593Smuzhiyun            gitrepos[(bitbake.giturl, bitbake.commit)] = []
102*4882a593Smuzhiyun            gitrepos[(bitbake.giturl, bitbake.commit)].append(
103*4882a593Smuzhiyun                ("bitbake", bitbake.dirpath, 0))
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun        for layer in layers:
106*4882a593Smuzhiyun            # We don't need to git clone the layer for the CustomImageRecipe
107*4882a593Smuzhiyun            # as it's generated by us layer on if needed
108*4882a593Smuzhiyun            if CustomImageRecipe.LAYER_NAME in layer.name:
109*4882a593Smuzhiyun                continue
110*4882a593Smuzhiyun
111*4882a593Smuzhiyun            # If we have local layers then we don't need clone them
112*4882a593Smuzhiyun            # For local layers giturl will be empty
113*4882a593Smuzhiyun            if not layer.giturl:
114*4882a593Smuzhiyun                nongitlayerlist.append( "%03d:%s" % (layer_index,layer.local_source_dir) )
115*4882a593Smuzhiyun                continue
116*4882a593Smuzhiyun
117*4882a593Smuzhiyun            if not (layer.giturl, layer.commit) in gitrepos:
118*4882a593Smuzhiyun                gitrepos[(layer.giturl, layer.commit)] = []
119*4882a593Smuzhiyun            gitrepos[(layer.giturl, layer.commit)].append( (layer.name,layer.dirpath,layer_index) )
120*4882a593Smuzhiyun            layer_index += 1
121*4882a593Smuzhiyun
122*4882a593Smuzhiyun
123*4882a593Smuzhiyun        logger.debug("localhostbecontroller, our git repos are %s" % pformat(gitrepos))
124*4882a593Smuzhiyun
125*4882a593Smuzhiyun
126*4882a593Smuzhiyun        # 2. Note for future use if the current source directory is a
127*4882a593Smuzhiyun        # checked-out git repos that could match a layer's vcs_url and therefore
128*4882a593Smuzhiyun        # be used to speed up cloning (rather than fetching it again).
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun        cached_layers = {}
131*4882a593Smuzhiyun
132*4882a593Smuzhiyun        try:
133*4882a593Smuzhiyun            for remotes in self._shellcmd("git remote -v", self.be.sourcedir,env=git_env).split("\n"):
134*4882a593Smuzhiyun                try:
135*4882a593Smuzhiyun                    remote = remotes.split("\t")[1].split(" ")[0]
136*4882a593Smuzhiyun                    if remote not in cached_layers:
137*4882a593Smuzhiyun                        cached_layers[remote] = self.be.sourcedir
138*4882a593Smuzhiyun                except IndexError:
139*4882a593Smuzhiyun                    pass
140*4882a593Smuzhiyun        except ShellCmdException:
141*4882a593Smuzhiyun            # ignore any errors in collecting git remotes this is an optional
142*4882a593Smuzhiyun            # step
143*4882a593Smuzhiyun            pass
144*4882a593Smuzhiyun
145*4882a593Smuzhiyun        logger.info("Using pre-checked out source for layer %s", cached_layers)
146*4882a593Smuzhiyun
147*4882a593Smuzhiyun        # 3. checkout the repositories
148*4882a593Smuzhiyun        clone_count=0
149*4882a593Smuzhiyun        clone_total=len(gitrepos.keys())
150*4882a593Smuzhiyun        self.setCloneStatus(bitbake,'Started',clone_total,clone_count,'')
151*4882a593Smuzhiyun        for giturl, commit in gitrepos.keys():
152*4882a593Smuzhiyun            self.setCloneStatus(bitbake,'progress',clone_total,clone_count,gitrepos[(giturl, commit)][0][0])
153*4882a593Smuzhiyun            clone_count += 1
154*4882a593Smuzhiyun
155*4882a593Smuzhiyun            localdirname = os.path.join(self.be.sourcedir, self.getGitCloneDirectory(giturl, commit))
156*4882a593Smuzhiyun            logger.debug("localhostbecontroller: giturl %s:%s checking out in current directory %s" % (giturl, commit, localdirname))
157*4882a593Smuzhiyun
158*4882a593Smuzhiyun            # see if our directory is a git repository
159*4882a593Smuzhiyun            if os.path.exists(localdirname):
160*4882a593Smuzhiyun                try:
161*4882a593Smuzhiyun                    localremotes = self._shellcmd("git remote -v",
162*4882a593Smuzhiyun                                                  localdirname,env=git_env)
163*4882a593Smuzhiyun                    # NOTE: this nice-to-have check breaks when using git remaping to get past firewall
164*4882a593Smuzhiyun                    #       Re-enable later with .gitconfig remapping checks
165*4882a593Smuzhiyun                    #if not giturl in localremotes and commit != 'HEAD':
166*4882a593Smuzhiyun                    #    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))
167*4882a593Smuzhiyun                    pass
168*4882a593Smuzhiyun                except ShellCmdException:
169*4882a593Smuzhiyun                    # our localdirname might not be a git repository
170*4882a593Smuzhiyun                    #- that's fine
171*4882a593Smuzhiyun                    pass
172*4882a593Smuzhiyun            else:
173*4882a593Smuzhiyun                if giturl in cached_layers:
174*4882a593Smuzhiyun                    logger.debug("localhostbecontroller git-copying %s to %s" % (cached_layers[giturl], localdirname))
175*4882a593Smuzhiyun                    self._shellcmd("git clone \"%s\" \"%s\"" % (cached_layers[giturl], localdirname),env=git_env)
176*4882a593Smuzhiyun                    self._shellcmd("git remote remove origin", localdirname,env=git_env)
177*4882a593Smuzhiyun                    self._shellcmd("git remote add origin \"%s\"" % giturl, localdirname,env=git_env)
178*4882a593Smuzhiyun                else:
179*4882a593Smuzhiyun                    logger.debug("localhostbecontroller: cloning %s in %s" % (giturl, localdirname))
180*4882a593Smuzhiyun                    self._shellcmd('git clone "%s" "%s"' % (giturl, localdirname),env=git_env)
181*4882a593Smuzhiyun
182*4882a593Smuzhiyun            # branch magic name "HEAD" will inhibit checkout
183*4882a593Smuzhiyun            if commit != "HEAD":
184*4882a593Smuzhiyun                logger.debug("localhostbecontroller: checking out commit %s to %s " % (commit, localdirname))
185*4882a593Smuzhiyun                ref = commit if re.match('^[a-fA-F0-9]+$', commit) else 'origin/%s' % commit
186*4882a593Smuzhiyun                self._shellcmd('git fetch && git reset --hard "%s"' % ref, localdirname,env=git_env)
187*4882a593Smuzhiyun
188*4882a593Smuzhiyun            # take the localdirname as poky dir if we can find the oe-init-build-env
189*4882a593Smuzhiyun            if self.pokydirname is None and os.path.exists(os.path.join(localdirname, "oe-init-build-env")):
190*4882a593Smuzhiyun                logger.debug("localhostbecontroller: selected poky dir name %s" % localdirname)
191*4882a593Smuzhiyun                self.pokydirname = localdirname
192*4882a593Smuzhiyun
193*4882a593Smuzhiyun                # make sure we have a working bitbake
194*4882a593Smuzhiyun                if not os.path.exists(os.path.join(self.pokydirname, 'bitbake')):
195*4882a593Smuzhiyun                    logger.debug("localhostbecontroller: checking bitbake into the poky dirname %s " % self.pokydirname)
196*4882a593Smuzhiyun                    self._shellcmd("git clone -b \"%s\" \"%s\" \"%s\" " % (bitbake.commit, bitbake.giturl, os.path.join(self.pokydirname, 'bitbake')),env=git_env)
197*4882a593Smuzhiyun
198*4882a593Smuzhiyun            # verify our repositories
199*4882a593Smuzhiyun            for name, dirpath, index in gitrepos[(giturl, commit)]:
200*4882a593Smuzhiyun                localdirpath = os.path.join(localdirname, dirpath)
201*4882a593Smuzhiyun                logger.debug("localhostbecontroller: localdirpath expects '%s'" % localdirpath)
202*4882a593Smuzhiyun                if not os.path.exists(localdirpath):
203*4882a593Smuzhiyun                    raise BuildSetupException("Cannot find layer git path '%s' in checked out repository '%s:%s'. Exiting." % (localdirpath, giturl, commit))
204*4882a593Smuzhiyun
205*4882a593Smuzhiyun                if name != "bitbake":
206*4882a593Smuzhiyun                    layerlist.append("%03d:%s" % (index,localdirpath.rstrip("/")))
207*4882a593Smuzhiyun
208*4882a593Smuzhiyun        self.setCloneStatus(bitbake,'complete',clone_total,clone_count,'')
209*4882a593Smuzhiyun        logger.debug("localhostbecontroller: current layer list %s " % pformat(layerlist))
210*4882a593Smuzhiyun
211*4882a593Smuzhiyun        # Resolve self.pokydirname if not resolved yet, consider the scenario
212*4882a593Smuzhiyun        # where all layers are local, that's the else clause
213*4882a593Smuzhiyun        if self.pokydirname is None:
214*4882a593Smuzhiyun            if os.path.exists(os.path.join(self.be.sourcedir, "oe-init-build-env")):
215*4882a593Smuzhiyun                logger.debug("localhostbecontroller: selected poky dir name %s" % self.be.sourcedir)
216*4882a593Smuzhiyun                self.pokydirname = self.be.sourcedir
217*4882a593Smuzhiyun            else:
218*4882a593Smuzhiyun                # Alternatively, scan local layers for relative "oe-init-build-env" location
219*4882a593Smuzhiyun                for layer in layers:
220*4882a593Smuzhiyun                    if os.path.exists(os.path.join(layer.layer_version.layer.local_source_dir,"..","oe-init-build-env")):
221*4882a593Smuzhiyun                        logger.debug("localhostbecontroller, setting pokydirname to %s" % (layer.layer_version.layer.local_source_dir))
222*4882a593Smuzhiyun                        self.pokydirname = os.path.join(layer.layer_version.layer.local_source_dir,"..")
223*4882a593Smuzhiyun                        break
224*4882a593Smuzhiyun                else:
225*4882a593Smuzhiyun                    logger.error("pokydirname is not set, you will run into trouble!")
226*4882a593Smuzhiyun
227*4882a593Smuzhiyun        # 5. create custom layer and add custom recipes to it
228*4882a593Smuzhiyun        for target in targets:
229*4882a593Smuzhiyun            try:
230*4882a593Smuzhiyun                customrecipe = CustomImageRecipe.objects.get(
231*4882a593Smuzhiyun                    name=target.target,
232*4882a593Smuzhiyun                    project=bitbake.req.project)
233*4882a593Smuzhiyun
234*4882a593Smuzhiyun                custom_layer_path = self.setup_custom_image_recipe(
235*4882a593Smuzhiyun                    customrecipe, layers)
236*4882a593Smuzhiyun
237*4882a593Smuzhiyun                if os.path.isdir(custom_layer_path):
238*4882a593Smuzhiyun                    layerlist.append("%03d:%s" % (layer_index,custom_layer_path))
239*4882a593Smuzhiyun
240*4882a593Smuzhiyun            except CustomImageRecipe.DoesNotExist:
241*4882a593Smuzhiyun                continue  # not a custom recipe, skip
242*4882a593Smuzhiyun
243*4882a593Smuzhiyun        layerlist.extend(nongitlayerlist)
244*4882a593Smuzhiyun        logger.debug("\n\nset layers gives this list %s" % pformat(layerlist))
245*4882a593Smuzhiyun        self.islayerset = True
246*4882a593Smuzhiyun
247*4882a593Smuzhiyun        # restore the order of layer list for bblayers.conf
248*4882a593Smuzhiyun        layerlist.sort()
249*4882a593Smuzhiyun        sorted_layerlist = [l[4:] for l in layerlist]
250*4882a593Smuzhiyun        return sorted_layerlist
251*4882a593Smuzhiyun
252*4882a593Smuzhiyun    def setup_custom_image_recipe(self, customrecipe, layers):
253*4882a593Smuzhiyun        """ Set up toaster-custom-images layer and recipe files """
254*4882a593Smuzhiyun        layerpath = os.path.join(self.be.builddir,
255*4882a593Smuzhiyun                                 CustomImageRecipe.LAYER_NAME)
256*4882a593Smuzhiyun
257*4882a593Smuzhiyun        # create directory structure
258*4882a593Smuzhiyun        for name in ("conf", "recipes"):
259*4882a593Smuzhiyun            path = os.path.join(layerpath, name)
260*4882a593Smuzhiyun            if not os.path.isdir(path):
261*4882a593Smuzhiyun                os.makedirs(path)
262*4882a593Smuzhiyun
263*4882a593Smuzhiyun        # create layer.conf
264*4882a593Smuzhiyun        config = os.path.join(layerpath, "conf", "layer.conf")
265*4882a593Smuzhiyun        if not os.path.isfile(config):
266*4882a593Smuzhiyun            with open(config, "w") as conf:
267*4882a593Smuzhiyun                conf.write('BBPATH .= ":${LAYERDIR}"\nBBFILES += "${LAYERDIR}/recipes/*.bb"\n')
268*4882a593Smuzhiyun
269*4882a593Smuzhiyun        # Update the Layer_Version dirpath that has our base_recipe in
270*4882a593Smuzhiyun        # to be able to read the base recipe to then  generate the
271*4882a593Smuzhiyun        # custom recipe.
272*4882a593Smuzhiyun        br_layer_base_recipe = layers.get(
273*4882a593Smuzhiyun            layer_version=customrecipe.base_recipe.layer_version)
274*4882a593Smuzhiyun
275*4882a593Smuzhiyun        # If the layer is one that we've cloned we know where it lives
276*4882a593Smuzhiyun        if br_layer_base_recipe.giturl and br_layer_base_recipe.commit:
277*4882a593Smuzhiyun            layer_path = self.getGitCloneDirectory(
278*4882a593Smuzhiyun                br_layer_base_recipe.giturl,
279*4882a593Smuzhiyun                br_layer_base_recipe.commit)
280*4882a593Smuzhiyun        # Otherwise it's a local layer
281*4882a593Smuzhiyun        elif br_layer_base_recipe.local_source_dir:
282*4882a593Smuzhiyun            layer_path = br_layer_base_recipe.local_source_dir
283*4882a593Smuzhiyun        else:
284*4882a593Smuzhiyun            logger.error("Unable to workout the dir path for the custom"
285*4882a593Smuzhiyun                         " image recipe")
286*4882a593Smuzhiyun
287*4882a593Smuzhiyun        br_layer_base_dirpath = os.path.join(
288*4882a593Smuzhiyun            self.be.sourcedir,
289*4882a593Smuzhiyun            layer_path,
290*4882a593Smuzhiyun            customrecipe.base_recipe.layer_version.dirpath)
291*4882a593Smuzhiyun
292*4882a593Smuzhiyun        customrecipe.base_recipe.layer_version.dirpath = br_layer_base_dirpath
293*4882a593Smuzhiyun
294*4882a593Smuzhiyun        customrecipe.base_recipe.layer_version.save()
295*4882a593Smuzhiyun
296*4882a593Smuzhiyun        # create recipe
297*4882a593Smuzhiyun        recipe_path = os.path.join(layerpath, "recipes", "%s.bb" %
298*4882a593Smuzhiyun                                   customrecipe.name)
299*4882a593Smuzhiyun        with open(recipe_path, "w") as recipef:
300*4882a593Smuzhiyun            recipef.write(customrecipe.generate_recipe_file_contents())
301*4882a593Smuzhiyun
302*4882a593Smuzhiyun        # Update the layer and recipe objects
303*4882a593Smuzhiyun        customrecipe.layer_version.dirpath = layerpath
304*4882a593Smuzhiyun        customrecipe.layer_version.layer.local_source_dir = layerpath
305*4882a593Smuzhiyun        customrecipe.layer_version.layer.save()
306*4882a593Smuzhiyun        customrecipe.layer_version.save()
307*4882a593Smuzhiyun
308*4882a593Smuzhiyun        customrecipe.file_path = recipe_path
309*4882a593Smuzhiyun        customrecipe.save()
310*4882a593Smuzhiyun
311*4882a593Smuzhiyun        return layerpath
312*4882a593Smuzhiyun
313*4882a593Smuzhiyun
314*4882a593Smuzhiyun    def readServerLogFile(self):
315*4882a593Smuzhiyun        return open(os.path.join(self.be.builddir, "toaster_server.log"), "r").read()
316*4882a593Smuzhiyun
317*4882a593Smuzhiyun
318*4882a593Smuzhiyun    def triggerBuild(self, bitbake, layers, variables, targets, brbe):
319*4882a593Smuzhiyun        layers = self.setLayers(bitbake, layers, targets)
320*4882a593Smuzhiyun        is_merged_attr = bitbake.req.project.merged_attr
321*4882a593Smuzhiyun
322*4882a593Smuzhiyun        git_env = os.environ.copy()
323*4882a593Smuzhiyun        # (note: add custom environment settings here)
324*4882a593Smuzhiyun        try:
325*4882a593Smuzhiyun            # insure that the project init/build uses the selected bitbake, and not Toaster's
326*4882a593Smuzhiyun            del git_env['TEMPLATECONF']
327*4882a593Smuzhiyun            del git_env['BBBASEDIR']
328*4882a593Smuzhiyun            del git_env['BUILDDIR']
329*4882a593Smuzhiyun        except KeyError:
330*4882a593Smuzhiyun            pass
331*4882a593Smuzhiyun
332*4882a593Smuzhiyun        # init build environment from the clone
333*4882a593Smuzhiyun        if bitbake.req.project.builddir:
334*4882a593Smuzhiyun            builddir = bitbake.req.project.builddir
335*4882a593Smuzhiyun        else:
336*4882a593Smuzhiyun            builddir = '%s-toaster-%d' % (self.be.builddir, bitbake.req.project.id)
337*4882a593Smuzhiyun        oe_init = os.path.join(self.pokydirname, 'oe-init-build-env')
338*4882a593Smuzhiyun        # init build environment
339*4882a593Smuzhiyun        try:
340*4882a593Smuzhiyun            custom_script = ToasterSetting.objects.get(name="CUSTOM_BUILD_INIT_SCRIPT").value
341*4882a593Smuzhiyun            custom_script = custom_script.replace("%BUILDDIR%" ,builddir)
342*4882a593Smuzhiyun            self._shellcmd("bash -c 'source %s'" % (custom_script),env=git_env)
343*4882a593Smuzhiyun        except ToasterSetting.DoesNotExist:
344*4882a593Smuzhiyun            self._shellcmd("bash -c 'source %s %s'" % (oe_init, builddir),
345*4882a593Smuzhiyun                       self.be.sourcedir,env=git_env)
346*4882a593Smuzhiyun
347*4882a593Smuzhiyun        # update bblayers.conf
348*4882a593Smuzhiyun        if not is_merged_attr:
349*4882a593Smuzhiyun            bblconfpath = os.path.join(builddir, "conf/toaster-bblayers.conf")
350*4882a593Smuzhiyun            with open(bblconfpath, 'w') as bblayers:
351*4882a593Smuzhiyun                bblayers.write('# line added by toaster build control\n'
352*4882a593Smuzhiyun                               'BBLAYERS = "%s"' % ' '.join(layers))
353*4882a593Smuzhiyun
354*4882a593Smuzhiyun            # write configuration file
355*4882a593Smuzhiyun            confpath = os.path.join(builddir, 'conf/toaster.conf')
356*4882a593Smuzhiyun            with open(confpath, 'w') as conf:
357*4882a593Smuzhiyun                for var in variables:
358*4882a593Smuzhiyun                    conf.write('%s="%s"\n' % (var.name, var.value))
359*4882a593Smuzhiyun                conf.write('INHERIT+="toaster buildhistory"')
360*4882a593Smuzhiyun        else:
361*4882a593Smuzhiyun            # Append the Toaster-specific values directly to the bblayers.conf
362*4882a593Smuzhiyun            bblconfpath = os.path.join(builddir, "conf/bblayers.conf")
363*4882a593Smuzhiyun            bblconfpath_save = os.path.join(builddir, "conf/bblayers.conf.save")
364*4882a593Smuzhiyun            shutil.copyfile(bblconfpath, bblconfpath_save)
365*4882a593Smuzhiyun            with open(bblconfpath) as bblayers:
366*4882a593Smuzhiyun                content = bblayers.readlines()
367*4882a593Smuzhiyun            do_write = True
368*4882a593Smuzhiyun            was_toaster = False
369*4882a593Smuzhiyun            with open(bblconfpath,'w') as bblayers:
370*4882a593Smuzhiyun                for line in content:
371*4882a593Smuzhiyun                    #line = line.strip('\n')
372*4882a593Smuzhiyun                    if 'TOASTER_CONFIG_PROLOG' in line:
373*4882a593Smuzhiyun                        do_write = False
374*4882a593Smuzhiyun                        was_toaster = True
375*4882a593Smuzhiyun                    elif 'TOASTER_CONFIG_EPILOG' in line:
376*4882a593Smuzhiyun                        do_write = True
377*4882a593Smuzhiyun                    elif do_write:
378*4882a593Smuzhiyun                        bblayers.write(line)
379*4882a593Smuzhiyun                if not was_toaster:
380*4882a593Smuzhiyun                    bblayers.write('\n')
381*4882a593Smuzhiyun                bblayers.write('#=== TOASTER_CONFIG_PROLOG ===\n')
382*4882a593Smuzhiyun                bblayers.write('BBLAYERS = "\\\n')
383*4882a593Smuzhiyun                for layer in layers:
384*4882a593Smuzhiyun                    bblayers.write('  %s \\\n' % layer)
385*4882a593Smuzhiyun                bblayers.write('  "\n')
386*4882a593Smuzhiyun                bblayers.write('#=== TOASTER_CONFIG_EPILOG ===\n')
387*4882a593Smuzhiyun            # Append the Toaster-specific values directly to the local.conf
388*4882a593Smuzhiyun            bbconfpath = os.path.join(builddir, "conf/local.conf")
389*4882a593Smuzhiyun            bbconfpath_save = os.path.join(builddir, "conf/local.conf.save")
390*4882a593Smuzhiyun            shutil.copyfile(bbconfpath, bbconfpath_save)
391*4882a593Smuzhiyun            with open(bbconfpath) as f:
392*4882a593Smuzhiyun                content = f.readlines()
393*4882a593Smuzhiyun            do_write = True
394*4882a593Smuzhiyun            was_toaster = False
395*4882a593Smuzhiyun            with open(bbconfpath,'w') as conf:
396*4882a593Smuzhiyun                for line in content:
397*4882a593Smuzhiyun                    #line = line.strip('\n')
398*4882a593Smuzhiyun                    if 'TOASTER_CONFIG_PROLOG' in line:
399*4882a593Smuzhiyun                        do_write = False
400*4882a593Smuzhiyun                        was_toaster = True
401*4882a593Smuzhiyun                    elif 'TOASTER_CONFIG_EPILOG' in line:
402*4882a593Smuzhiyun                        do_write = True
403*4882a593Smuzhiyun                    elif do_write:
404*4882a593Smuzhiyun                        conf.write(line)
405*4882a593Smuzhiyun                if not was_toaster:
406*4882a593Smuzhiyun                    conf.write('\n')
407*4882a593Smuzhiyun                conf.write('#=== TOASTER_CONFIG_PROLOG ===\n')
408*4882a593Smuzhiyun                for var in variables:
409*4882a593Smuzhiyun                    if (not var.name.startswith("INTERNAL_")) and (not var.name == "BBLAYERS"):
410*4882a593Smuzhiyun                        conf.write('%s="%s"\n' % (var.name, var.value))
411*4882a593Smuzhiyun                conf.write('#=== TOASTER_CONFIG_EPILOG ===\n')
412*4882a593Smuzhiyun
413*4882a593Smuzhiyun        # If 'target' is just the project preparation target, then we are done
414*4882a593Smuzhiyun        for target in targets:
415*4882a593Smuzhiyun            if "_PROJECT_PREPARE_" == target.target:
416*4882a593Smuzhiyun                logger.debug('localhostbecontroller: Project has been prepared. Done.')
417*4882a593Smuzhiyun                # Update the Build Request and release the build environment
418*4882a593Smuzhiyun                bitbake.req.state = BuildRequest.REQ_COMPLETED
419*4882a593Smuzhiyun                bitbake.req.save()
420*4882a593Smuzhiyun                self.be.lock = BuildEnvironment.LOCK_FREE
421*4882a593Smuzhiyun                self.be.save()
422*4882a593Smuzhiyun                # Close the project build and progress bar
423*4882a593Smuzhiyun                bitbake.req.build.outcome = Build.SUCCEEDED
424*4882a593Smuzhiyun                bitbake.req.build.save()
425*4882a593Smuzhiyun                # Update the project status
426*4882a593Smuzhiyun                bitbake.req.project.set_variable(Project.PROJECT_SPECIFIC_STATUS,Project.PROJECT_SPECIFIC_CLONING_SUCCESS)
427*4882a593Smuzhiyun                signal_runbuilds()
428*4882a593Smuzhiyun                return
429*4882a593Smuzhiyun
430*4882a593Smuzhiyun        # clean the Toaster to build environment
431*4882a593Smuzhiyun        env_clean = 'unset BBPATH;' # clean BBPATH for <= YP-2.4.0
432*4882a593Smuzhiyun
433*4882a593Smuzhiyun        # run bitbake server from the clone if available
434*4882a593Smuzhiyun        # otherwise pick it from the PATH
435*4882a593Smuzhiyun        bitbake = os.path.join(self.pokydirname, 'bitbake', 'bin', 'bitbake')
436*4882a593Smuzhiyun        if not os.path.exists(bitbake):
437*4882a593Smuzhiyun            logger.info("Bitbake not available under %s, will try to use it from PATH" %
438*4882a593Smuzhiyun                        self.pokydirname)
439*4882a593Smuzhiyun            for path in os.environ["PATH"].split(os.pathsep):
440*4882a593Smuzhiyun                if os.path.exists(os.path.join(path, 'bitbake')):
441*4882a593Smuzhiyun                    bitbake = os.path.join(path, 'bitbake')
442*4882a593Smuzhiyun                    break
443*4882a593Smuzhiyun            else:
444*4882a593Smuzhiyun                logger.error("Looks like Bitbake is not available, please fix your environment")
445*4882a593Smuzhiyun
446*4882a593Smuzhiyun        toasterlayers = os.path.join(builddir,"conf/toaster-bblayers.conf")
447*4882a593Smuzhiyun        if not is_merged_attr:
448*4882a593Smuzhiyun            self._shellcmd('%s bash -c \"source %s %s; BITBAKE_UI="knotty" %s --read %s --read %s '
449*4882a593Smuzhiyun                           '--server-only -B 0.0.0.0:0\"' % (env_clean, oe_init,
450*4882a593Smuzhiyun                           builddir, bitbake, confpath, toasterlayers), self.be.sourcedir)
451*4882a593Smuzhiyun        else:
452*4882a593Smuzhiyun            self._shellcmd('%s bash -c \"source %s %s; BITBAKE_UI="knotty" %s '
453*4882a593Smuzhiyun                           '--server-only -B 0.0.0.0:0\"' % (env_clean, oe_init,
454*4882a593Smuzhiyun                           builddir, bitbake), self.be.sourcedir)
455*4882a593Smuzhiyun
456*4882a593Smuzhiyun        # read port number from bitbake.lock
457*4882a593Smuzhiyun        self.be.bbport = -1
458*4882a593Smuzhiyun        bblock = os.path.join(builddir, 'bitbake.lock')
459*4882a593Smuzhiyun        # allow 10 seconds for bb lock file to appear but also be populated
460*4882a593Smuzhiyun        for lock_check in range(10):
461*4882a593Smuzhiyun            if not os.path.exists(bblock):
462*4882a593Smuzhiyun                logger.debug("localhostbecontroller: waiting for bblock file to appear")
463*4882a593Smuzhiyun                time.sleep(1)
464*4882a593Smuzhiyun                continue
465*4882a593Smuzhiyun            if 10 < os.stat(bblock).st_size:
466*4882a593Smuzhiyun                break
467*4882a593Smuzhiyun            logger.debug("localhostbecontroller: waiting for bblock content to appear")
468*4882a593Smuzhiyun            time.sleep(1)
469*4882a593Smuzhiyun        else:
470*4882a593Smuzhiyun            raise BuildSetupException("Cannot find bitbake server lock file '%s'. Exiting." % bblock)
471*4882a593Smuzhiyun
472*4882a593Smuzhiyun        with open(bblock) as fplock:
473*4882a593Smuzhiyun            for line in fplock:
474*4882a593Smuzhiyun                if ":" in line:
475*4882a593Smuzhiyun                    self.be.bbport = line.split(":")[-1].strip()
476*4882a593Smuzhiyun                    logger.debug("localhostbecontroller: bitbake port %s", self.be.bbport)
477*4882a593Smuzhiyun                    break
478*4882a593Smuzhiyun
479*4882a593Smuzhiyun        if -1 == self.be.bbport:
480*4882a593Smuzhiyun            raise BuildSetupException("localhostbecontroller: can't read bitbake port from %s" % bblock)
481*4882a593Smuzhiyun
482*4882a593Smuzhiyun        self.be.bbaddress = "localhost"
483*4882a593Smuzhiyun        self.be.bbstate = BuildEnvironment.SERVER_STARTED
484*4882a593Smuzhiyun        self.be.lock = BuildEnvironment.LOCK_RUNNING
485*4882a593Smuzhiyun        self.be.save()
486*4882a593Smuzhiyun
487*4882a593Smuzhiyun        bbtargets = ''
488*4882a593Smuzhiyun        for target in targets:
489*4882a593Smuzhiyun            task = target.task
490*4882a593Smuzhiyun            if task:
491*4882a593Smuzhiyun                if not task.startswith('do_'):
492*4882a593Smuzhiyun                    task = 'do_' + task
493*4882a593Smuzhiyun                task = ':%s' % task
494*4882a593Smuzhiyun            bbtargets += '%s%s ' % (target.target, task)
495*4882a593Smuzhiyun
496*4882a593Smuzhiyun        # run build with local bitbake. stop the server after the build.
497*4882a593Smuzhiyun        log = os.path.join(builddir, 'toaster_ui.log')
498*4882a593Smuzhiyun        local_bitbake = os.path.join(os.path.dirname(os.getenv('BBBASEDIR')),
499*4882a593Smuzhiyun                                     'bitbake')
500*4882a593Smuzhiyun        if not is_merged_attr:
501*4882a593Smuzhiyun            self._shellcmd(['%s bash -c \"(TOASTER_BRBE="%s" BBSERVER="0.0.0.0:%s" '
502*4882a593Smuzhiyun                        '%s %s -u toasterui  --read %s --read %s --token="" >>%s 2>&1;'
503*4882a593Smuzhiyun                        'BITBAKE_UI="knotty" BBSERVER=0.0.0.0:%s %s -m)&\"' \
504*4882a593Smuzhiyun                        % (env_clean, brbe, self.be.bbport, local_bitbake, bbtargets, confpath, toasterlayers, log,
505*4882a593Smuzhiyun                        self.be.bbport, bitbake,)],
506*4882a593Smuzhiyun                        builddir, nowait=True)
507*4882a593Smuzhiyun        else:
508*4882a593Smuzhiyun            self._shellcmd(['%s bash -c \"(TOASTER_BRBE="%s" BBSERVER="0.0.0.0:%s" '
509*4882a593Smuzhiyun                        '%s %s -u toasterui  --token="" >>%s 2>&1;'
510*4882a593Smuzhiyun                        'BITBAKE_UI="knotty" BBSERVER=0.0.0.0:%s %s -m)&\"' \
511*4882a593Smuzhiyun                        % (env_clean, brbe, self.be.bbport, local_bitbake, bbtargets, log,
512*4882a593Smuzhiyun                        self.be.bbport, bitbake,)],
513*4882a593Smuzhiyun                        builddir, nowait=True)
514*4882a593Smuzhiyun
515*4882a593Smuzhiyun        logger.debug('localhostbecontroller: Build launched, exiting. '
516*4882a593Smuzhiyun                     'Follow build logs at %s' % log)
517