Home | History | Annotate | Download | only in android
      1 # SPDX-License-Identifier: Apache-2.0
      2 #
      3 # Copyright (C) 2015, ARM Limited and contributors.
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
      6 # not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 # http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 #
     17 
     18 import logging
     19 
     20 from devlib.utils.android import adb_command
     21 from devlib import TargetError
     22 from devlib.utils.android_build import Build
     23 from time import sleep
     24 import os
     25 import re
     26 import time
     27 import json
     28 import pexpect as pe
     29 from time import sleep
     30 
     31 GET_FRAMESTATS_CMD = 'shell dumpsys gfxinfo {} > {}'
     32 ADB_INSTALL_CMD = 'install -g -r {}'
     33 BOARD_CONFIG_FILE = 'board.json'
     34 
     35 # See https://developer.android.com/reference/android/content/Intent.html#setFlags(int)
     36 FLAG_ACTIVITY_NEW_TASK = 0x10000000
     37 FLAG_ACTIVITY_CLEAR_TASK = 0x00008000
     38 
     39 class System(object):
     40     """
     41     Collection of Android related services
     42     """
     43 
     44     @staticmethod
     45     def systrace_start(target, trace_file, time=None,
     46                        events=['gfx', 'view', 'sched', 'freq', 'idle'],
     47                        conf=None):
     48         buffsize = "40000"
     49         log = logging.getLogger('System')
     50 
     51         # Android needs good TGID caching support, until atrace has it,
     52         # just increase the cache size to avoid missing TGIDs (and also comms)
     53         target.target.execute("echo 8192 > /sys/kernel/debug/tracing/saved_cmdlines_size")
     54 
     55         # Override systrace defaults from target conf
     56         if conf and ('systrace' in conf):
     57             if 'categories' in conf['systrace']:
     58                 events = conf['systrace']['categories']
     59             if 'extra_categories' in conf['systrace']:
     60                 events += conf['systrace']['extra_categories']
     61             if 'buffsize' in conf['systrace']:
     62                 buffsize = int(conf['systrace']['buffsize'])
     63             if 'extra_events' in conf['systrace']:
     64                 for ev in conf['systrace']['extra_events']:
     65                     log.info("systrace_start: Enabling extra ftrace event {}".format(ev))
     66                     ev_file = target.target.execute("ls /sys/kernel/debug/tracing/events/*/{}/enable".format(ev))
     67                     cmd = "echo 1 > {}".format(ev_file)
     68                     target.target.execute(cmd, as_root=True)
     69             if 'event_triggers' in conf['systrace']:
     70                 for ev in conf['systrace']['event_triggers'].keys():
     71                     tr_file = target.target.execute("ls /sys/kernel/debug/tracing/events/*/{}/trigger".format(ev))
     72                     cmd = "echo {} > {}".format(conf['systrace']['event_triggers'][ev], tr_file)
     73                     target.target.execute(cmd, as_root=True, check_exit_code=False)
     74 
     75         # Check which systrace binary is available under CATAPULT_HOME
     76         for systrace in ['systrace.py', 'run_systrace.py']:
     77                 systrace_path = os.path.join(target.CATAPULT_HOME, 'systrace',
     78                                              'systrace', systrace)
     79                 if os.path.isfile(systrace_path):
     80                         break
     81         else:
     82                 log.warning("Systrace binary not available under CATAPULT_HOME: %s!",
     83                             target.CATAPULT_HOME)
     84                 return None
     85 
     86         #  Format the command according to the specified arguments
     87         device = target.conf.get('device', '')
     88         if device:
     89             device = "-e {}".format(device)
     90         systrace_pattern = "{} {} -o {} {} -b {}"
     91         trace_cmd = systrace_pattern.format(systrace_path, device,
     92                                             trace_file, " ".join(events), buffsize)
     93         if time is not None:
     94             trace_cmd += " -t {}".format(time)
     95 
     96         log.info('SysTrace: %s', trace_cmd)
     97 
     98         # Actually spawn systrace
     99         return pe.spawn(trace_cmd)
    100 
    101     @staticmethod
    102     def systrace_wait(target, systrace_output):
    103         systrace_output.wait()
    104 
    105     @staticmethod
    106     def set_airplane_mode(target, on=True):
    107         """
    108         Set airplane mode
    109         """
    110         ap_mode = 1 if on else 0
    111         ap_state = 'true' if on else 'false'
    112 
    113         try:
    114             target.execute('settings put global airplane_mode_on {}'\
    115                            .format(ap_mode), as_root=True)
    116             target.execute('am broadcast '\
    117                            '-a android.intent.action.AIRPLANE_MODE '\
    118                            '--ez state {}'\
    119                            .format(ap_state), as_root=True)
    120         except TargetError:
    121             log = logging.getLogger('System')
    122             log.warning('Failed to toggle airplane mode, permission denied.')
    123 
    124     @staticmethod
    125     def _set_svc(target, cmd, on=True):
    126         mode = 'enable' if on else 'disable'
    127         try:
    128             target.execute('svc {} {}'.format(cmd, mode), as_root=True)
    129         except TargetError:
    130             log = logging.getLogger('System')
    131             log.warning('Failed to toggle {} mode, permission denied.'\
    132                         .format(cmd))
    133 
    134     @staticmethod
    135     def set_mobile_data(target, on=True):
    136         """
    137         Set mobile data connectivity
    138         """
    139         System._set_svc(target, 'data', on)
    140 
    141     @staticmethod
    142     def set_wifi(target, on=True):
    143         """
    144         Set mobile data connectivity
    145         """
    146         System._set_svc(target, 'wifi', on)
    147 
    148     @staticmethod
    149     def set_nfc(target, on=True):
    150         """
    151         Set mobile data connectivity
    152         """
    153         System._set_svc(target, 'nfc', on)
    154 
    155     @staticmethod
    156     def get_property(target, prop):
    157         """
    158         Get the value of a system property
    159         """
    160         try:
    161             value = target.execute('getprop {}'.format(prop), as_root=True)
    162         except TargetError:
    163             log = logging.getLogger('System')
    164             log.warning('Failed to get prop {}'.format(prop))
    165         return value.strip()
    166 
    167     @staticmethod
    168     def get_boolean_property(target, prop):
    169         """
    170         Get a boolean system property and return whether its value corresponds to True
    171         """
    172         return System.get_property(target, prop) in {'yes', 'true', 'on', '1', 'y'}
    173 
    174     @staticmethod
    175     def set_property(target, prop, value, restart=False):
    176         """
    177         Set a system property, then run adb shell stop && start if necessary
    178 
    179         When restart=True, this function clears logcat to avoid detecting old
    180         "Boot is finished" messages.
    181         """
    182         try:
    183             target.execute('setprop {} {}'.format(prop, value), as_root=True)
    184         except TargetError:
    185             log = logging.getLogger('System')
    186             log.warning('Failed to set {} to {}.'.format(prop, value))
    187         if not restart:
    188             return
    189 
    190         target.execute('logcat -c', check_exit_code=False)
    191         BOOT_FINISHED_RE = re.compile(r'Boot is finished')
    192         logcat = target.background('logcat SurfaceFlinger:* *:S')
    193         target.execute('stop && start', as_root=True)
    194         while True:
    195             message = logcat.stdout.readline(1024)
    196             match = BOOT_FINISHED_RE.search(message)
    197             if match:
    198                 return
    199 
    200     @staticmethod
    201     def start_app(target, apk_name):
    202         """
    203         Start the main activity of the specified application
    204 
    205         :param apk_name: name of the apk
    206         :type apk_name: str
    207         """
    208         target.execute('monkey -p {} -c android.intent.category.LAUNCHER 1'\
    209                       .format(apk_name))
    210 
    211     @staticmethod
    212     def start_activity(target, apk_name, activity_name):
    213         """
    214         Start an application by specifying package and activity name.
    215 
    216         :param apk_name: name of the apk
    217         :type apk_name: str
    218 
    219         :param activity_name: name of the activity to launch
    220         :type activity_name: str
    221         """
    222         target.execute('am start -n {}/{}'.format(apk_name, activity_name))
    223 
    224     @staticmethod
    225     def start_action(target, action, action_args=''):
    226         """
    227         Start an activity by specifying an action.
    228 
    229         :param action: action to be executed
    230         :type action: str
    231 
    232         :param action_args: arguments for the activity
    233         :type action_args: str
    234         """
    235         target.execute('am start -a {} {}'.format(action, action_args))
    236 
    237     @staticmethod
    238     def screen_always_on(target, enable=True):
    239         """
    240         Keep the screen always on
    241 
    242         :param enable: True or false
    243         """
    244         param = 'true'
    245         if not enable:
    246             param = 'false'
    247 
    248         log = logging.getLogger('System')
    249         log.info('Setting screen always on to {}'.format(param))
    250         target.execute('svc power stayon {}'.format(param))
    251 
    252     @staticmethod
    253     def view_uri(target, uri, force_new=True):
    254         """
    255         Start a view activity by specifying a URI
    256 
    257         :param uri: URI of the item to display
    258         :type uri: str
    259 
    260         :param force_new: Force the viewing application to be
    261             relaunched if it is already running
    262         :type force_new: bool
    263         """
    264         arguments = '-d {}'.format(uri)
    265 
    266         if force_new:
    267             # Activity flags ensure the app is restarted
    268             arguments = '{} -f {}'.format(arguments,
    269                 FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)
    270 
    271         System.start_action(target, 'android.intent.action.VIEW', arguments)
    272         # Wait for the viewing application to be completely loaded
    273         sleep(5)
    274 
    275     @staticmethod
    276     def force_stop(target, apk_name, clear=False):
    277         """
    278         Stop the application and clear its data if necessary.
    279 
    280         :param target: instance of devlib Android target
    281         :type target: devlib.target.AndroidTarget
    282 
    283         :param apk_name: name of the apk
    284         :type apk_name: str
    285 
    286         :param clear: clear application data
    287         :type clear: bool
    288         """
    289         target.execute('am force-stop {}'.format(apk_name))
    290         if clear:
    291             target.execute('pm clear {}'.format(apk_name))
    292 
    293     @staticmethod
    294     def force_suspend_start(target):
    295         """
    296         Force the device to go into suspend. If a wakelock is held, the device
    297         will go into idle instead.
    298 
    299         :param target: instance of devlib Android target
    300         :type target: devlib.target.AndroidTarget
    301 
    302         """
    303         target.execute('dumpsys deviceidle force-idle deep')
    304 
    305     @staticmethod
    306     def force_suspend_stop(target):
    307         """
    308         Stop forcing the device to suspend/idle.
    309 
    310         :param target: instance of devlib Android target
    311         :type target: devlib.target.AndroidTarget
    312 
    313         """
    314         target.execute('dumpsys deviceidle unforce')
    315 
    316     @staticmethod
    317     def tap(target, x, y, absolute=False):
    318         """
    319         Tap a given point on the screen.
    320 
    321         :param target: instance of devlib Android target
    322         :type target: devlib.target.AndroidTarget
    323 
    324         :param x: horizontal coordinate
    325         :type x: int
    326 
    327         :param y: vertical coordinate
    328         :type y: int
    329 
    330         :param absolute: use absolute coordinates or percentage of screen
    331             resolution
    332         :type absolute: bool
    333         """
    334         if not absolute:
    335             w, h = target.screen_resolution
    336             x = w * x / 100
    337             y = h * y / 100
    338 
    339         target.execute('input tap {} {}'.format(x, y))
    340 
    341     @staticmethod
    342     def vswipe(target, y_low_pct, y_top_pct, duration='', swipe_up=True):
    343         """
    344         Vertical swipe
    345 
    346         :param target: instance of devlib Android target
    347         :type target: devlib.target.AndroidTarget
    348 
    349         :param y_low_pct: vertical lower coordinate percentage
    350         :type y_low_pct: int
    351 
    352         :param y_top_pct: vertical upper coordinate percentage
    353         :type y_top_pct: int
    354 
    355         :param duration: duration of the swipe in milliseconds
    356         :type duration: int
    357 
    358         :param swipe_up: swipe up or down
    359         :type swipe_up: bool
    360         """
    361         w, h = target.screen_resolution
    362         x = w / 2
    363         if swipe_up:
    364             y1 = h * y_top_pct / 100
    365             y2 = h * y_low_pct / 100
    366         else:
    367             y1 = h * y_low_pct / 100
    368             y2 = h * y_top_pct / 100
    369 
    370         target.execute('input swipe {} {} {} {} {}'\
    371                        .format(x, y1, x, y2, duration))
    372 
    373     @staticmethod
    374     def hswipe(target, x_left_pct, x_right_pct, duration='', swipe_right=True):
    375         """
    376         Horizontal swipe
    377 
    378         :param target: instance of devlib Android target
    379         :type target: devlib.target.AndroidTarget
    380 
    381         :param x_left_pct: horizontal left coordinate percentage
    382         :type x_left_pct: int
    383 
    384         :param x_right_pct: horizontal right coordinate percentage
    385         :type x_right_pct: int
    386 
    387         :param duration: duration of the swipe in milliseconds
    388         :type duration: int
    389 
    390         :param swipe_right: swipe right or left
    391         :type swipe_right: bool
    392         """
    393         w, h = target.screen_resolution
    394         y = h / 2
    395         if swipe_right:
    396             x1 = w * x_left_pct / 100
    397             x2 = w * x_right_pct / 100
    398         else:
    399             x1 = w * x_right_pct / 100
    400             x2 = w * x_left_pct / 100
    401         target.execute('input swipe {} {} {} {} {}'\
    402                        .format(x1, y, x2, y, duration))
    403 
    404     @staticmethod
    405     def menu(target):
    406         """
    407         Press MENU button
    408 
    409         :param target: instance of devlib Android target
    410         :type target: devlib.target.AndroidTarget
    411         """
    412         target.execute('input keyevent KEYCODE_MENU')
    413 
    414     @staticmethod
    415     def home(target):
    416         """
    417         Press HOME button
    418 
    419         :param target: instance of devlib Android target
    420         :type target: devlib.target.AndroidTarget
    421         """
    422         target.execute('input keyevent KEYCODE_HOME')
    423 
    424     @staticmethod
    425     def back(target):
    426         """
    427         Press BACK button
    428 
    429         :param target: instance of devlib Android target
    430         :type target: devlib.target.AndroidTarget
    431         """
    432         target.execute('input keyevent KEYCODE_BACK')
    433 
    434     @staticmethod
    435     def wakeup(target):
    436         """
    437         Wake up the system if its sleeping
    438 
    439         :param target: instance of devlib Android target
    440         :type target: devlib.target.AndroidTarget
    441         """
    442         target.execute('input keyevent KEYCODE_WAKEUP')
    443 
    444     @staticmethod
    445     def sleep(target):
    446         """
    447         Make system sleep if its awake
    448 
    449         :param target: instance of devlib Android target
    450         :type target: devlib.target.AndroidTarget
    451         """
    452         target.execute('input keyevent KEYCODE_SLEEP')
    453 
    454     @staticmethod
    455     def volume(target, times=1, direction='down'):
    456         """
    457         Increase or decrease volume
    458 
    459         :param target: instance of devlib Android target
    460         :type target: devlib.target.AndroidTarget
    461 
    462         :param times: number of times to perform operation
    463         :type times: int
    464 
    465         :param direction: which direction to increase (up/down)
    466         :type direction: str
    467         """
    468         for i in range(times):
    469             if direction == 'up':
    470                 target.execute('input keyevent KEYCODE_VOLUME_UP')
    471             elif direction == 'down':
    472                 target.execute('input keyevent KEYCODE_VOLUME_DOWN')
    473 
    474     @staticmethod
    475     def wakelock(target, name='lisa', take=False):
    476         """
    477         Take or release wakelock
    478 
    479         :param target: instance of devlib Android target
    480         :type target: devlib.target.AndroidTarget
    481 
    482         :param name: name of the wakelock
    483         :type name: str
    484 
    485         :param take: whether to take or release the wakelock
    486         :type take: bool
    487         """
    488         path = '/sys/power/wake_lock' if take else '/sys/power/wake_unlock'
    489         target.execute('echo {} > {}'.format(name, path))
    490 
    491     @staticmethod
    492     def gfxinfo_reset(target, apk_name):
    493         """
    494         Reset gfxinfo frame statistics for a given app.
    495 
    496         :param target: instance of devlib Android target
    497         :type target: devlib.target.AndroidTarget
    498 
    499         :param apk_name: name of the apk
    500         :type apk_name: str
    501         """
    502         target.execute('dumpsys gfxinfo {} reset'.format(apk_name))
    503         sleep(1)
    504 
    505     @staticmethod
    506     def surfaceflinger_reset(target, apk_name):
    507         """
    508         Reset SurfaceFlinger layer statistics for a given app.
    509 
    510         :param target: instance of devlib Android target
    511         :type target: devlib.target.AndroidTarget
    512 
    513         :param apk_name: name of the apk
    514         :type apk_name: str
    515         """
    516         target.execute('dumpsys SurfaceFlinger {} reset'.format(apk_name))
    517 
    518     @staticmethod
    519     def logcat_reset(target):
    520         """
    521         Clears the logcat buffer.
    522 
    523         :param target: instance of devlib Android target
    524         :type target: devlib.target.AndroidTarget
    525         """
    526         target.execute('logcat -c')
    527 
    528     @staticmethod
    529     def gfxinfo_get(target, apk_name, out_file):
    530         """
    531         Collect frame statistics for the given app.
    532 
    533         :param target: instance of devlib Android target
    534         :type target: devlib.target.AndroidTarget
    535 
    536         :param apk_name: name of the apk
    537         :type apk_name: str
    538 
    539         :param out_file: output file name
    540         :type out_file: str
    541         """
    542         adb_command(target.adb_name,
    543                     GET_FRAMESTATS_CMD.format(apk_name, out_file))
    544 
    545     @staticmethod
    546     def surfaceflinger_get(target, apk_name, out_file):
    547         """
    548         Collect SurfaceFlinger layer statistics for the given app.
    549 
    550         :param target: instance of devlib Android target
    551         :type target: devlib.target.AndroidTarget
    552 
    553         :param apk_name: name of the apk
    554         :type apk_name: str
    555 
    556         :param out_file: output file name
    557         :type out_file: str
    558         """
    559         adb_command(target.adb_name,
    560                     'shell dumpsys SurfaceFlinger {} > {}'.format(apk_name, out_file))
    561 
    562     @staticmethod
    563     def logcat_get(target, out_file):
    564         """
    565         Collect the logs from logcat.
    566 
    567         :param target: instance of devlib Android target
    568         :type target: devlib.target.AndroidTarget
    569 
    570         :param out_file: output file name
    571         :type out_file: str
    572         """
    573         adb_command(target.adb_name, 'logcat * -d > {}'.format(out_file))
    574 
    575     @staticmethod
    576     def monkey(target, apk_name, event_count=1):
    577         """
    578         Wrapper for adb monkey tool.
    579 
    580         The Monkey is a program that runs on your emulator or device and
    581         generates pseudo-random streams of user events such as clicks, touches,
    582         or gestures, as well as a number of system-level events. You can use
    583         the Monkey to stress-test applications that you are developing, in a
    584         random yet repeatable manner.
    585 
    586         Full documentation is available at:
    587 
    588         https://developer.android.com/studio/test/monkey.html
    589 
    590         :param target: instance of devlib Android target
    591         :type target: devlib.target.AndroidTarget
    592 
    593         :param apk_name: name of the apk
    594         :type apk_name: str
    595 
    596         :param event_count: number of events to generate
    597         :type event_count: int
    598         """
    599         target.execute('monkey -p {} {}'.format(apk_name, event_count))
    600 
    601     @staticmethod
    602     def list_packages(target, apk_filter=''):
    603         """
    604         List the packages matching the specified filter
    605 
    606         :param target: instance of devlib Android target
    607         :type target: devlib.target.AndroidTarget
    608 
    609         :param apk_filter: a substring which must be part of the package name
    610         :type apk_filter: str
    611         """
    612         packages = []
    613 
    614         pkgs = target.execute('cmd package list packages {}'\
    615                               .format(apk_filter.lower()))
    616         for pkg in pkgs.splitlines():
    617             packages.append(pkg.replace('package:', ''))
    618         packages.sort()
    619 
    620         if len(packages):
    621             return packages
    622         return None
    623 
    624     @staticmethod
    625     def packages_info(target, apk_filter=''):
    626         """
    627         Get a dictionary of installed APKs and related information
    628 
    629         :param target: instance of devlib Android target
    630         :type target: devlib.target.AndroidTarget
    631 
    632         :param apk_filter: a substring which must be part of the package name
    633         :type apk_filter: str
    634         """
    635         packages = {}
    636 
    637         pkgs = target.execute('cmd package list packages {}'\
    638                               .format(apk_filter.lower()))
    639         for pkg in pkgs.splitlines():
    640             pkg = pkg.replace('package:', '')
    641             # Lookup for additional APK information
    642             apk = target.execute('pm path {}'.format(pkg))
    643             apk = apk.replace('package:', '')
    644             packages[pkg] = {
    645                 'apk' : apk.strip()
    646             }
    647 
    648         if len(packages):
    649             return packages
    650         return None
    651 
    652 
    653     @staticmethod
    654     def install_apk(target, apk_path):
    655         """
    656         Get a dictionary of installed APKs and related information
    657 
    658         :param target: instance of devlib Android target
    659         :type target: devlib.target.AndroidTarget
    660 
    661         :param apk_path: path to application
    662         :type apk_path: str
    663         """
    664         adb_command(target.adb_name, ADB_INSTALL_CMD.format(apk_path))
    665 
    666     @staticmethod
    667     def contains_package(target, package):
    668         """
    669         Returns true if the package exists on the device
    670 
    671         :param target: instance of devlib Android target
    672         :type target: devlib.target.AndroidTarget
    673 
    674         :param package: the name of the package
    675         :type package: str
    676         """
    677         packages = System.list_packages(target)
    678         if not packages:
    679             return None
    680 
    681         return package in packages
    682 
    683     @staticmethod
    684     def grant_permission(target, package, permission):
    685         """
    686         Grant permission to a package
    687 
    688         :param target: instance of devlib Android target
    689         :type target: devlib.target.AndroidTarget
    690 
    691         :param package: the name of the package
    692         :type package: str
    693 
    694         :param permission: the name of the permission
    695         :type permission: str
    696         """
    697         target.execute('pm grant {} {}'.format(package, permission))
    698 
    699     @staticmethod
    700     def reset_permissions(target, package):
    701         """
    702         Reset the permission for a package
    703 
    704         :param target: instance of devlib Android target
    705         :type target: devlib.target.AndroidTarget
    706 
    707         :param package: the name of the package
    708         :type package: str
    709         """
    710         target.execute('pm reset-permissions {}'.format(package))
    711 
    712     @staticmethod
    713     def find_config_file(test_env):
    714         # Try device-specific config file first
    715         board_cfg_file = os.path.join(test_env.DEVICE_LISA_HOME, BOARD_CONFIG_FILE)
    716 
    717         if not os.path.exists(board_cfg_file):
    718             # Try local config file $LISA_HOME/libs/utils/platforms/$TARGET_PRODUCT.json
    719             board_cfg_file = 'libs/utils/platforms/{}.json'.format(test_env.TARGET_PRODUCT)
    720             board_cfg_file = os.path.join(test_env.LISA_HOME, board_cfg_file)
    721             if not os.path.exists(board_cfg_file):
    722                 return None
    723         return board_cfg_file
    724 
    725     @staticmethod
    726     def read_config_file(board_cfg_file):
    727         with open(board_cfg_file, "r") as fh:
    728             board_config = json.load(fh)
    729         return board_config
    730 
    731     @staticmethod
    732     def reimage(test_env, kernel_path='', update_cfg=''):
    733         """
    734         Get a reference to the specified Android workload
    735 
    736         :param test_env: target test environment
    737         :type test_env: TestEnv
    738 
    739         :param kernel_path: path to kernel sources, required if reimage option is used
    740         :type kernel_path: str
    741 
    742         :param update_cfg: update configuration name from board_cfg.json
    743         :type update_cfg: str
    744 
    745         """
    746         # Find board config file from device-specific or local directory
    747         board_cfg_file = System.find_config_file(test_env)
    748         if board_cfg_file == None:
    749             raise RuntimeError('Board config file is not found')
    750 
    751         # Read build config file
    752         board_config = System.read_config_file(board_cfg_file)
    753         if board_config == None:
    754             raise RuntimeError('Board config file {} is invalid'.format(board_cfg_file))
    755 
    756         # Read update-config section and execute appropriate scripts
    757         update_config = board_config['update-config'][update_cfg]
    758         if update_config == None:
    759             raise RuntimeError('Update config \'{}\' is not found'.format(update_cfg))
    760 
    761         board_cfg_dir = os.path.dirname(os.path.realpath(board_cfg_file))
    762         build_script = update_config['build-script']
    763         flash_script = update_config['flash-script']
    764         build_script = os.path.join(board_cfg_dir, build_script)
    765         flash_script = os.path.join(board_cfg_dir, flash_script)
    766 
    767         cmd_prefix = "LOCAL_KERNEL_HOME='{}' ".format(kernel_path)
    768         bld = Build(test_env)
    769         bld.exec_cmd(cmd_prefix + build_script)
    770         bld.exec_cmd(cmd_prefix + flash_script)
    771 
    772 
    773 # vim :set tabstop=4 shiftwidth=4 expandtab
    774