xref: /OK3568_Linux_fs/yocto/poky/scripts/lib/devtool/sdk.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# Development tool - sdk-update command plugin
2*4882a593Smuzhiyun#
3*4882a593Smuzhiyun# Copyright (C) 2015-2016 Intel Corporation
4*4882a593Smuzhiyun#
5*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
6*4882a593Smuzhiyun#
7*4882a593Smuzhiyun
8*4882a593Smuzhiyunimport os
9*4882a593Smuzhiyunimport subprocess
10*4882a593Smuzhiyunimport logging
11*4882a593Smuzhiyunimport glob
12*4882a593Smuzhiyunimport shutil
13*4882a593Smuzhiyunimport errno
14*4882a593Smuzhiyunimport sys
15*4882a593Smuzhiyunimport tempfile
16*4882a593Smuzhiyunimport re
17*4882a593Smuzhiyunfrom devtool import exec_build_env_command, setup_tinfoil, parse_recipe, DevtoolError
18*4882a593Smuzhiyun
19*4882a593Smuzhiyunlogger = logging.getLogger('devtool')
20*4882a593Smuzhiyun
21*4882a593Smuzhiyundef parse_locked_sigs(sigfile_path):
22*4882a593Smuzhiyun    """Return <pn:task>:<hash> dictionary"""
23*4882a593Smuzhiyun    sig_dict = {}
24*4882a593Smuzhiyun    with open(sigfile_path) as f:
25*4882a593Smuzhiyun        lines = f.readlines()
26*4882a593Smuzhiyun        for line in lines:
27*4882a593Smuzhiyun            if ':' in line:
28*4882a593Smuzhiyun                taskkey, _, hashval = line.rpartition(':')
29*4882a593Smuzhiyun                sig_dict[taskkey.strip()] = hashval.split()[0]
30*4882a593Smuzhiyun    return sig_dict
31*4882a593Smuzhiyun
32*4882a593Smuzhiyundef generate_update_dict(sigfile_new, sigfile_old):
33*4882a593Smuzhiyun    """Return a dict containing <pn:task>:<hash> which indicates what need to be updated"""
34*4882a593Smuzhiyun    update_dict = {}
35*4882a593Smuzhiyun    sigdict_new = parse_locked_sigs(sigfile_new)
36*4882a593Smuzhiyun    sigdict_old = parse_locked_sigs(sigfile_old)
37*4882a593Smuzhiyun    for k in sigdict_new:
38*4882a593Smuzhiyun        if k not in sigdict_old:
39*4882a593Smuzhiyun            update_dict[k] = sigdict_new[k]
40*4882a593Smuzhiyun            continue
41*4882a593Smuzhiyun        if sigdict_new[k] != sigdict_old[k]:
42*4882a593Smuzhiyun            update_dict[k] = sigdict_new[k]
43*4882a593Smuzhiyun            continue
44*4882a593Smuzhiyun    return update_dict
45*4882a593Smuzhiyun
46*4882a593Smuzhiyundef get_sstate_objects(update_dict, sstate_dir):
47*4882a593Smuzhiyun    """Return a list containing sstate objects which are to be installed"""
48*4882a593Smuzhiyun    sstate_objects = []
49*4882a593Smuzhiyun    for k in update_dict:
50*4882a593Smuzhiyun        files = set()
51*4882a593Smuzhiyun        hashval = update_dict[k]
52*4882a593Smuzhiyun        p = sstate_dir + '/' + hashval[:2] + '/*' + hashval + '*.tgz'
53*4882a593Smuzhiyun        files |= set(glob.glob(p))
54*4882a593Smuzhiyun        p = sstate_dir + '/*/' + hashval[:2] + '/*' + hashval + '*.tgz'
55*4882a593Smuzhiyun        files |= set(glob.glob(p))
56*4882a593Smuzhiyun        files = list(files)
57*4882a593Smuzhiyun        if len(files) == 1:
58*4882a593Smuzhiyun            sstate_objects.extend(files)
59*4882a593Smuzhiyun        elif len(files) > 1:
60*4882a593Smuzhiyun            logger.error("More than one matching sstate object found for %s" % hashval)
61*4882a593Smuzhiyun
62*4882a593Smuzhiyun    return sstate_objects
63*4882a593Smuzhiyun
64*4882a593Smuzhiyundef mkdir(d):
65*4882a593Smuzhiyun    try:
66*4882a593Smuzhiyun        os.makedirs(d)
67*4882a593Smuzhiyun    except OSError as e:
68*4882a593Smuzhiyun        if e.errno != errno.EEXIST:
69*4882a593Smuzhiyun            raise e
70*4882a593Smuzhiyun
71*4882a593Smuzhiyundef install_sstate_objects(sstate_objects, src_sdk, dest_sdk):
72*4882a593Smuzhiyun    """Install sstate objects into destination SDK"""
73*4882a593Smuzhiyun    sstate_dir = os.path.join(dest_sdk, 'sstate-cache')
74*4882a593Smuzhiyun    if not os.path.exists(sstate_dir):
75*4882a593Smuzhiyun        logger.error("Missing sstate-cache directory in %s, it might not be an extensible SDK." % dest_sdk)
76*4882a593Smuzhiyun        raise
77*4882a593Smuzhiyun    for sb in sstate_objects:
78*4882a593Smuzhiyun        dst = sb.replace(src_sdk, dest_sdk)
79*4882a593Smuzhiyun        destdir = os.path.dirname(dst)
80*4882a593Smuzhiyun        mkdir(destdir)
81*4882a593Smuzhiyun        logger.debug("Copying %s to %s" % (sb, dst))
82*4882a593Smuzhiyun        shutil.copy(sb, dst)
83*4882a593Smuzhiyun
84*4882a593Smuzhiyundef check_manifest(fn, basepath):
85*4882a593Smuzhiyun    import bb.utils
86*4882a593Smuzhiyun    changedfiles = []
87*4882a593Smuzhiyun    with open(fn, 'r') as f:
88*4882a593Smuzhiyun        for line in f:
89*4882a593Smuzhiyun            splitline = line.split()
90*4882a593Smuzhiyun            if len(splitline) > 1:
91*4882a593Smuzhiyun                chksum = splitline[0]
92*4882a593Smuzhiyun                fpath = splitline[1]
93*4882a593Smuzhiyun                curr_chksum = bb.utils.sha256_file(os.path.join(basepath, fpath))
94*4882a593Smuzhiyun                if chksum != curr_chksum:
95*4882a593Smuzhiyun                    logger.debug('File %s changed: old csum = %s, new = %s' % (os.path.join(basepath, fpath), curr_chksum, chksum))
96*4882a593Smuzhiyun                    changedfiles.append(fpath)
97*4882a593Smuzhiyun    return changedfiles
98*4882a593Smuzhiyun
99*4882a593Smuzhiyundef sdk_update(args, config, basepath, workspace):
100*4882a593Smuzhiyun    """Entry point for devtool sdk-update command"""
101*4882a593Smuzhiyun    updateserver = args.updateserver
102*4882a593Smuzhiyun    if not updateserver:
103*4882a593Smuzhiyun        updateserver = config.get('SDK', 'updateserver', '')
104*4882a593Smuzhiyun    logger.debug("updateserver: %s" % updateserver)
105*4882a593Smuzhiyun
106*4882a593Smuzhiyun    # Make sure we are using sdk-update from within SDK
107*4882a593Smuzhiyun    logger.debug("basepath = %s" % basepath)
108*4882a593Smuzhiyun    old_locked_sig_file_path = os.path.join(basepath, 'conf/locked-sigs.inc')
109*4882a593Smuzhiyun    if not os.path.exists(old_locked_sig_file_path):
110*4882a593Smuzhiyun        logger.error("Not using devtool's sdk-update command from within an extensible SDK. Please specify correct basepath via --basepath option")
111*4882a593Smuzhiyun        return -1
112*4882a593Smuzhiyun    else:
113*4882a593Smuzhiyun        logger.debug("Found conf/locked-sigs.inc in %s" % basepath)
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun    if not '://' in updateserver:
116*4882a593Smuzhiyun        logger.error("Update server must be a URL")
117*4882a593Smuzhiyun        return -1
118*4882a593Smuzhiyun
119*4882a593Smuzhiyun    layers_dir = os.path.join(basepath, 'layers')
120*4882a593Smuzhiyun    conf_dir = os.path.join(basepath, 'conf')
121*4882a593Smuzhiyun
122*4882a593Smuzhiyun    # Grab variable values
123*4882a593Smuzhiyun    tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
124*4882a593Smuzhiyun    try:
125*4882a593Smuzhiyun        stamps_dir = tinfoil.config_data.getVar('STAMPS_DIR')
126*4882a593Smuzhiyun        sstate_mirrors = tinfoil.config_data.getVar('SSTATE_MIRRORS')
127*4882a593Smuzhiyun        site_conf_version = tinfoil.config_data.getVar('SITE_CONF_VERSION')
128*4882a593Smuzhiyun    finally:
129*4882a593Smuzhiyun        tinfoil.shutdown()
130*4882a593Smuzhiyun
131*4882a593Smuzhiyun    tmpsdk_dir = tempfile.mkdtemp()
132*4882a593Smuzhiyun    try:
133*4882a593Smuzhiyun        os.makedirs(os.path.join(tmpsdk_dir, 'conf'))
134*4882a593Smuzhiyun        new_locked_sig_file_path = os.path.join(tmpsdk_dir, 'conf', 'locked-sigs.inc')
135*4882a593Smuzhiyun        # Fetch manifest from server
136*4882a593Smuzhiyun        tmpmanifest = os.path.join(tmpsdk_dir, 'conf', 'sdk-conf-manifest')
137*4882a593Smuzhiyun        ret = subprocess.call("wget -q -O %s %s/conf/sdk-conf-manifest" % (tmpmanifest, updateserver), shell=True)
138*4882a593Smuzhiyun        if ret != 0:
139*4882a593Smuzhiyun            logger.error("Cannot dowload files from %s" % updateserver)
140*4882a593Smuzhiyun            return ret
141*4882a593Smuzhiyun        changedfiles = check_manifest(tmpmanifest, basepath)
142*4882a593Smuzhiyun        if not changedfiles:
143*4882a593Smuzhiyun            logger.info("Already up-to-date")
144*4882a593Smuzhiyun            return 0
145*4882a593Smuzhiyun        # Update metadata
146*4882a593Smuzhiyun        logger.debug("Updating metadata via git ...")
147*4882a593Smuzhiyun        #Check for the status before doing a fetch and reset
148*4882a593Smuzhiyun        if os.path.exists(os.path.join(basepath, 'layers/.git')):
149*4882a593Smuzhiyun            out = subprocess.check_output("git status --porcelain", shell=True, cwd=layers_dir)
150*4882a593Smuzhiyun            if not out:
151*4882a593Smuzhiyun                ret = subprocess.call("git fetch --all; git reset --hard @{u}", shell=True, cwd=layers_dir)
152*4882a593Smuzhiyun            else:
153*4882a593Smuzhiyun                logger.error("Failed to update metadata as there have been changes made to it. Aborting.");
154*4882a593Smuzhiyun                logger.error("Changed files:\n%s" % out);
155*4882a593Smuzhiyun                return -1
156*4882a593Smuzhiyun        else:
157*4882a593Smuzhiyun            ret = -1
158*4882a593Smuzhiyun        if ret != 0:
159*4882a593Smuzhiyun            ret = subprocess.call("git clone %s/layers/.git" % updateserver, shell=True, cwd=tmpsdk_dir)
160*4882a593Smuzhiyun            if ret != 0:
161*4882a593Smuzhiyun                logger.error("Updating metadata via git failed")
162*4882a593Smuzhiyun                return ret
163*4882a593Smuzhiyun        logger.debug("Updating conf files ...")
164*4882a593Smuzhiyun        for changedfile in changedfiles:
165*4882a593Smuzhiyun            ret = subprocess.call("wget -q -O %s %s/%s" % (changedfile, updateserver, changedfile), shell=True, cwd=tmpsdk_dir)
166*4882a593Smuzhiyun            if ret != 0:
167*4882a593Smuzhiyun                logger.error("Updating %s failed" % changedfile)
168*4882a593Smuzhiyun                return ret
169*4882a593Smuzhiyun
170*4882a593Smuzhiyun        # Check if UNINATIVE_CHECKSUM changed
171*4882a593Smuzhiyun        uninative = False
172*4882a593Smuzhiyun        if 'conf/local.conf' in changedfiles:
173*4882a593Smuzhiyun            def read_uninative_checksums(fn):
174*4882a593Smuzhiyun                chksumitems = []
175*4882a593Smuzhiyun                with open(fn, 'r') as f:
176*4882a593Smuzhiyun                    for line in f:
177*4882a593Smuzhiyun                        if line.startswith('UNINATIVE_CHECKSUM'):
178*4882a593Smuzhiyun                            splitline = re.split(r'[\[\]"\']', line)
179*4882a593Smuzhiyun                            if len(splitline) > 3:
180*4882a593Smuzhiyun                                chksumitems.append((splitline[1], splitline[3]))
181*4882a593Smuzhiyun                return chksumitems
182*4882a593Smuzhiyun
183*4882a593Smuzhiyun            oldsums = read_uninative_checksums(os.path.join(basepath, 'conf/local.conf'))
184*4882a593Smuzhiyun            newsums = read_uninative_checksums(os.path.join(tmpsdk_dir, 'conf/local.conf'))
185*4882a593Smuzhiyun            if oldsums != newsums:
186*4882a593Smuzhiyun                uninative = True
187*4882a593Smuzhiyun                for buildarch, chksum in newsums:
188*4882a593Smuzhiyun                    uninative_file = os.path.join('downloads', 'uninative', chksum, '%s-nativesdk-libc.tar.bz2' % buildarch)
189*4882a593Smuzhiyun                    mkdir(os.path.join(tmpsdk_dir, os.path.dirname(uninative_file)))
190*4882a593Smuzhiyun                    ret = subprocess.call("wget -q -O %s %s/%s" % (uninative_file, updateserver, uninative_file), shell=True, cwd=tmpsdk_dir)
191*4882a593Smuzhiyun
192*4882a593Smuzhiyun        # Ok, all is well at this point - move everything over
193*4882a593Smuzhiyun        tmplayers_dir = os.path.join(tmpsdk_dir, 'layers')
194*4882a593Smuzhiyun        if os.path.exists(tmplayers_dir):
195*4882a593Smuzhiyun            shutil.rmtree(layers_dir)
196*4882a593Smuzhiyun            shutil.move(tmplayers_dir, layers_dir)
197*4882a593Smuzhiyun        for changedfile in changedfiles:
198*4882a593Smuzhiyun            destfile = os.path.join(basepath, changedfile)
199*4882a593Smuzhiyun            os.remove(destfile)
200*4882a593Smuzhiyun            shutil.move(os.path.join(tmpsdk_dir, changedfile), destfile)
201*4882a593Smuzhiyun        os.remove(os.path.join(conf_dir, 'sdk-conf-manifest'))
202*4882a593Smuzhiyun        shutil.move(tmpmanifest, conf_dir)
203*4882a593Smuzhiyun        if uninative:
204*4882a593Smuzhiyun            shutil.rmtree(os.path.join(basepath, 'downloads', 'uninative'))
205*4882a593Smuzhiyun            shutil.move(os.path.join(tmpsdk_dir, 'downloads', 'uninative'), os.path.join(basepath, 'downloads'))
206*4882a593Smuzhiyun
207*4882a593Smuzhiyun        if not sstate_mirrors:
208*4882a593Smuzhiyun            with open(os.path.join(conf_dir, 'site.conf'), 'a') as f:
209*4882a593Smuzhiyun                f.write('SCONF_VERSION = "%s"\n' % site_conf_version)
210*4882a593Smuzhiyun                f.write('SSTATE_MIRRORS:append = " file://.* %s/sstate-cache/PATH"\n' % updateserver)
211*4882a593Smuzhiyun    finally:
212*4882a593Smuzhiyun        shutil.rmtree(tmpsdk_dir)
213*4882a593Smuzhiyun
214*4882a593Smuzhiyun    if not args.skip_prepare:
215*4882a593Smuzhiyun        # Find all potentially updateable tasks
216*4882a593Smuzhiyun        sdk_update_targets = []
217*4882a593Smuzhiyun        tasks = ['do_populate_sysroot', 'do_packagedata']
218*4882a593Smuzhiyun        for root, _, files in os.walk(stamps_dir):
219*4882a593Smuzhiyun            for fn in files:
220*4882a593Smuzhiyun                if not '.sigdata.' in fn:
221*4882a593Smuzhiyun                    for task in tasks:
222*4882a593Smuzhiyun                        if '.%s.' % task in fn or '.%s_setscene.' % task in fn:
223*4882a593Smuzhiyun                            sdk_update_targets.append('%s:%s' % (os.path.basename(root), task))
224*4882a593Smuzhiyun        # Run bitbake command for the whole SDK
225*4882a593Smuzhiyun        logger.info("Preparing build system... (This may take some time.)")
226*4882a593Smuzhiyun        try:
227*4882a593Smuzhiyun            exec_build_env_command(config.init_path, basepath, 'bitbake --setscene-only %s' % ' '.join(sdk_update_targets), stderr=subprocess.STDOUT)
228*4882a593Smuzhiyun            output, _ = exec_build_env_command(config.init_path, basepath, 'bitbake -n %s' % ' '.join(sdk_update_targets), stderr=subprocess.STDOUT)
229*4882a593Smuzhiyun            runlines = []
230*4882a593Smuzhiyun            for line in output.splitlines():
231*4882a593Smuzhiyun                if 'Running task ' in line:
232*4882a593Smuzhiyun                    runlines.append(line)
233*4882a593Smuzhiyun            if runlines:
234*4882a593Smuzhiyun                logger.error('Unexecuted tasks found in preparation log:\n  %s' % '\n  '.join(runlines))
235*4882a593Smuzhiyun                return -1
236*4882a593Smuzhiyun        except bb.process.ExecutionError as e:
237*4882a593Smuzhiyun            logger.error('Preparation failed:\n%s' % e.stdout)
238*4882a593Smuzhiyun            return -1
239*4882a593Smuzhiyun    return 0
240*4882a593Smuzhiyun
241*4882a593Smuzhiyundef sdk_install(args, config, basepath, workspace):
242*4882a593Smuzhiyun    """Entry point for the devtool sdk-install command"""
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun    import oe.recipeutils
245*4882a593Smuzhiyun    import bb.process
246*4882a593Smuzhiyun
247*4882a593Smuzhiyun    for recipe in args.recipename:
248*4882a593Smuzhiyun        if recipe in workspace:
249*4882a593Smuzhiyun            raise DevtoolError('recipe %s is a recipe in your workspace' % recipe)
250*4882a593Smuzhiyun
251*4882a593Smuzhiyun    tasks = ['do_populate_sysroot', 'do_packagedata']
252*4882a593Smuzhiyun    stampprefixes = {}
253*4882a593Smuzhiyun    def checkstamp(recipe):
254*4882a593Smuzhiyun        stampprefix = stampprefixes[recipe]
255*4882a593Smuzhiyun        stamps = glob.glob(stampprefix + '*')
256*4882a593Smuzhiyun        for stamp in stamps:
257*4882a593Smuzhiyun            if '.sigdata.' not in stamp and stamp.startswith((stampprefix + '.', stampprefix + '_setscene.')):
258*4882a593Smuzhiyun                return True
259*4882a593Smuzhiyun        else:
260*4882a593Smuzhiyun            return False
261*4882a593Smuzhiyun
262*4882a593Smuzhiyun    install_recipes = []
263*4882a593Smuzhiyun    tinfoil = setup_tinfoil(config_only=False, basepath=basepath)
264*4882a593Smuzhiyun    try:
265*4882a593Smuzhiyun        for recipe in args.recipename:
266*4882a593Smuzhiyun            rd = parse_recipe(config, tinfoil, recipe, True)
267*4882a593Smuzhiyun            if not rd:
268*4882a593Smuzhiyun                return 1
269*4882a593Smuzhiyun            stampprefixes[recipe] = '%s.%s' % (rd.getVar('STAMP'), tasks[0])
270*4882a593Smuzhiyun            if checkstamp(recipe):
271*4882a593Smuzhiyun                logger.info('%s is already installed' % recipe)
272*4882a593Smuzhiyun            else:
273*4882a593Smuzhiyun                install_recipes.append(recipe)
274*4882a593Smuzhiyun    finally:
275*4882a593Smuzhiyun        tinfoil.shutdown()
276*4882a593Smuzhiyun
277*4882a593Smuzhiyun    if install_recipes:
278*4882a593Smuzhiyun        logger.info('Installing %s...' % ', '.join(install_recipes))
279*4882a593Smuzhiyun        install_tasks = []
280*4882a593Smuzhiyun        for recipe in install_recipes:
281*4882a593Smuzhiyun            for task in tasks:
282*4882a593Smuzhiyun                if recipe.endswith('-native') and 'package' in task:
283*4882a593Smuzhiyun                    continue
284*4882a593Smuzhiyun                install_tasks.append('%s:%s' % (recipe, task))
285*4882a593Smuzhiyun        options = ''
286*4882a593Smuzhiyun        if not args.allow_build:
287*4882a593Smuzhiyun            options += ' --setscene-only'
288*4882a593Smuzhiyun        try:
289*4882a593Smuzhiyun            exec_build_env_command(config.init_path, basepath, 'bitbake %s %s' % (options, ' '.join(install_tasks)), watch=True)
290*4882a593Smuzhiyun        except bb.process.ExecutionError as e:
291*4882a593Smuzhiyun            raise DevtoolError('Failed to install %s:\n%s' % (recipe, str(e)))
292*4882a593Smuzhiyun        failed = False
293*4882a593Smuzhiyun        for recipe in install_recipes:
294*4882a593Smuzhiyun            if checkstamp(recipe):
295*4882a593Smuzhiyun                logger.info('Successfully installed %s' % recipe)
296*4882a593Smuzhiyun            else:
297*4882a593Smuzhiyun                raise DevtoolError('Failed to install %s - unavailable' % recipe)
298*4882a593Smuzhiyun                failed = True
299*4882a593Smuzhiyun        if failed:
300*4882a593Smuzhiyun            return 2
301*4882a593Smuzhiyun
302*4882a593Smuzhiyun        try:
303*4882a593Smuzhiyun            exec_build_env_command(config.init_path, basepath, 'bitbake build-sysroots', watch=True)
304*4882a593Smuzhiyun        except bb.process.ExecutionError as e:
305*4882a593Smuzhiyun            raise DevtoolError('Failed to bitbake build-sysroots:\n%s' % (str(e)))
306*4882a593Smuzhiyun
307*4882a593Smuzhiyun
308*4882a593Smuzhiyundef register_commands(subparsers, context):
309*4882a593Smuzhiyun    """Register devtool subcommands from the sdk plugin"""
310*4882a593Smuzhiyun    if context.fixed_setup:
311*4882a593Smuzhiyun        parser_sdk = subparsers.add_parser('sdk-update',
312*4882a593Smuzhiyun                                           help='Update SDK components',
313*4882a593Smuzhiyun                                           description='Updates installed SDK components from a remote server',
314*4882a593Smuzhiyun                                           group='sdk')
315*4882a593Smuzhiyun        updateserver = context.config.get('SDK', 'updateserver', '')
316*4882a593Smuzhiyun        if updateserver:
317*4882a593Smuzhiyun            parser_sdk.add_argument('updateserver', help='The update server to fetch latest SDK components from (default %s)' % updateserver, nargs='?')
318*4882a593Smuzhiyun        else:
319*4882a593Smuzhiyun            parser_sdk.add_argument('updateserver', help='The update server to fetch latest SDK components from')
320*4882a593Smuzhiyun        parser_sdk.add_argument('--skip-prepare', action="store_true", help='Skip re-preparing the build system after updating (for debugging only)')
321*4882a593Smuzhiyun        parser_sdk.set_defaults(func=sdk_update)
322*4882a593Smuzhiyun
323*4882a593Smuzhiyun        parser_sdk_install = subparsers.add_parser('sdk-install',
324*4882a593Smuzhiyun                                                   help='Install additional SDK components',
325*4882a593Smuzhiyun                                                   description='Installs additional recipe development files into the SDK. (You can use "devtool search" to find available recipes.)',
326*4882a593Smuzhiyun                                                   group='sdk')
327*4882a593Smuzhiyun        parser_sdk_install.add_argument('recipename', help='Name of the recipe to install the development artifacts for', nargs='+')
328*4882a593Smuzhiyun        parser_sdk_install.add_argument('-s', '--allow-build', help='Allow building requested item(s) from source', action='store_true')
329*4882a593Smuzhiyun        parser_sdk_install.set_defaults(func=sdk_install)
330