1#! /usr/bin/env python3
2#
3# BitBake Toaster Implementation
4#
5# Copyright (C) 2016 Intel Corporation
6#
7# SPDX-License-Identifier: GPL-2.0-only
8#
9
10import os
11import sys
12import time
13import unittest
14
15from orm.models import Project, Release, ProjectTarget, Build, ProjectVariable
16from bldcontrol.models import BuildEnvironment
17
18from bldcontrol.management.commands.runbuilds import Command\
19    as RunBuildsCommand
20
21from django.core.management import call_command
22
23import subprocess
24import logging
25
26logger = logging.getLogger("toaster")
27
28# We use unittest.TestCase instead of django.test.TestCase because we don't
29# want to wrap everything in a database transaction as an external process
30# (bitbake needs access to the database)
31
32def load_build_environment():
33    call_command('loaddata', 'settings.xml', app_label="orm")
34    call_command('loaddata', 'poky.xml', app_label="orm")
35
36    current_builddir = os.environ.get("BUILDDIR")
37    if current_builddir:
38        BuildTest.BUILDDIR = current_builddir
39    else:
40        # Setup a builddir based on default layout
41        # bitbake inside openebedded-core
42        oe_init_build_env_path = os.path.join(
43            os.path.dirname(os.path.abspath(__file__)),
44            os.pardir,
45            os.pardir,
46            os.pardir,
47            os.pardir,
48            os.pardir,
49            'oe-init-build-env'
50        )
51        if not os.path.exists(oe_init_build_env_path):
52            raise Exception("We had no BUILDDIR set and couldn't "
53                            "find oe-init-build-env to set this up "
54                            "ourselves please run oe-init-build-env "
55                            "before running these tests")
56
57        oe_init_build_env_path = os.path.realpath(oe_init_build_env_path)
58        cmd = "bash -c 'source oe-init-build-env %s'" % BuildTest.BUILDDIR
59        p = subprocess.Popen(
60            cmd,
61            cwd=os.path.dirname(oe_init_build_env_path),
62            shell=True,
63            stdout=subprocess.PIPE,
64            stderr=subprocess.PIPE)
65
66        output, err = p.communicate()
67        p.wait()
68
69        logger.info("oe-init-build-env %s %s" % (output, err))
70
71        os.environ['BUILDDIR'] = BuildTest.BUILDDIR
72
73    # Setup the path to bitbake we know where to find this
74    bitbake_path = os.path.join(
75        os.path.dirname(os.path.abspath(__file__)),
76        os.pardir,
77        os.pardir,
78        os.pardir,
79        os.pardir,
80        'bin',
81        'bitbake')
82    if not os.path.exists(bitbake_path):
83        raise Exception("Could not find bitbake at the expected path %s"
84                        % bitbake_path)
85
86    os.environ['BBBASEDIR'] = bitbake_path
87
88class BuildTest(unittest.TestCase):
89
90    PROJECT_NAME = "Testbuild"
91    BUILDDIR = os.environ.get("BUILDDIR")
92
93    def build(self, target):
94        # So that the buildinfo helper uses the test database'
95        self.assertEqual(
96            os.environ.get('DJANGO_SETTINGS_MODULE', ''),
97            'toastermain.settings_test',
98            "Please initialise django with the tests settings:  "
99            "DJANGO_SETTINGS_MODULE='toastermain.settings_test'")
100
101        built = self.target_already_built(target)
102        if built:
103            return built
104
105        load_build_environment()
106
107        BuildEnvironment.objects.get_or_create(
108            betype=BuildEnvironment.TYPE_LOCAL,
109            sourcedir=BuildTest.BUILDDIR,
110            builddir=BuildTest.BUILDDIR
111        )
112
113        release = Release.objects.get(name='local')
114
115        # Create a project for this build to run in
116        project = Project.objects.create_project(name=BuildTest.PROJECT_NAME,
117                                                 release=release)
118
119        passthrough_variable_names = ["SSTATE_DIR", "DL_DIR", "SSTATE_MIRRORS", "BB_HASHSERVE", "BB_HASHSERVE_UPSTREAM"]
120        for variable_name in passthrough_variable_names:
121            current_variable = os.environ.get(variable_name)
122            if current_variable:
123                ProjectVariable.objects.get_or_create(
124                    name=variable_name,
125                    value=current_variable,
126                    project=project)
127
128        if os.environ.get("TOASTER_TEST_USE_SSTATE_MIRROR"):
129            ProjectVariable.objects.get_or_create(
130                name="SSTATE_MIRRORS",
131                value="file://.* http://cdn.jsdelivr.net/yocto/sstate/all/PATH;downloadfilename=PATH",
132                project=project)
133
134        ProjectTarget.objects.create(project=project,
135                                     target=target,
136                                     task="")
137        build_request = project.schedule_build()
138
139        # run runbuilds command to dispatch the build
140        # e.g. manage.py runubilds
141        RunBuildsCommand().runbuild()
142
143        build_pk = build_request.build.pk
144        while Build.objects.get(pk=build_pk).outcome == Build.IN_PROGRESS:
145            sys.stdout.write("\rBuilding %s %d%%" %
146                             (target,
147                              build_request.build.completeper()))
148            sys.stdout.flush()
149            time.sleep(1)
150
151        self.assertEqual(Build.objects.get(pk=build_pk).outcome,
152                         Build.SUCCEEDED,
153                         "Build did not SUCCEEDED")
154
155        logger.info("\nBuild finished %s" % build_request.build.outcome)
156        return build_request.build
157
158    def target_already_built(self, target):
159        """ If the target is already built no need to build it again"""
160        for build in Build.objects.filter(
161                project__name=BuildTest.PROJECT_NAME):
162            targets = build.target_set.values_list('target', flat=True)
163            if target in targets:
164                return build
165
166        return None
167