xref: /OK3568_Linux_fs/yocto/scripts/devtool (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#!/usr/bin/env python3
2*4882a593Smuzhiyun
3*4882a593Smuzhiyun# OpenEmbedded Development tool
4*4882a593Smuzhiyun#
5*4882a593Smuzhiyun# Copyright (C) 2014-2015 Intel Corporation
6*4882a593Smuzhiyun#
7*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
8*4882a593Smuzhiyun#
9*4882a593Smuzhiyun
10*4882a593Smuzhiyunimport sys
11*4882a593Smuzhiyunimport os
12*4882a593Smuzhiyunimport argparse
13*4882a593Smuzhiyunimport glob
14*4882a593Smuzhiyunimport re
15*4882a593Smuzhiyunimport configparser
16*4882a593Smuzhiyunimport subprocess
17*4882a593Smuzhiyunimport logging
18*4882a593Smuzhiyun
19*4882a593Smuzhiyunbasepath = ''
20*4882a593Smuzhiyunworkspace = {}
21*4882a593Smuzhiyunconfig = None
22*4882a593Smuzhiyuncontext = None
23*4882a593Smuzhiyun
24*4882a593Smuzhiyun
25*4882a593Smuzhiyunscripts_path = os.path.dirname(os.path.realpath(__file__))
26*4882a593Smuzhiyunlib_path = scripts_path + '/lib'
27*4882a593Smuzhiyunsys.path = sys.path + [lib_path]
28*4882a593Smuzhiyunfrom devtool import DevtoolError, setup_tinfoil
29*4882a593Smuzhiyunimport scriptutils
30*4882a593Smuzhiyunimport argparse_oe
31*4882a593Smuzhiyunlogger = scriptutils.logger_create('devtool')
32*4882a593Smuzhiyun
33*4882a593Smuzhiyunplugins = []
34*4882a593Smuzhiyun
35*4882a593Smuzhiyun
36*4882a593Smuzhiyunclass ConfigHandler(object):
37*4882a593Smuzhiyun    config_file = ''
38*4882a593Smuzhiyun    config_obj = None
39*4882a593Smuzhiyun    init_path = ''
40*4882a593Smuzhiyun    workspace_path = ''
41*4882a593Smuzhiyun
42*4882a593Smuzhiyun    def __init__(self, filename):
43*4882a593Smuzhiyun        self.config_file = filename
44*4882a593Smuzhiyun        self.config_obj = configparser.ConfigParser()
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun    def get(self, section, option, default=None):
47*4882a593Smuzhiyun        try:
48*4882a593Smuzhiyun            ret = self.config_obj.get(section, option)
49*4882a593Smuzhiyun        except (configparser.NoOptionError, configparser.NoSectionError):
50*4882a593Smuzhiyun            if default != None:
51*4882a593Smuzhiyun                ret = default
52*4882a593Smuzhiyun            else:
53*4882a593Smuzhiyun                raise
54*4882a593Smuzhiyun        return ret
55*4882a593Smuzhiyun
56*4882a593Smuzhiyun    def read(self):
57*4882a593Smuzhiyun        if os.path.exists(self.config_file):
58*4882a593Smuzhiyun            self.config_obj.read(self.config_file)
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun            if self.config_obj.has_option('General', 'init_path'):
61*4882a593Smuzhiyun                pth = self.get('General', 'init_path')
62*4882a593Smuzhiyun                self.init_path = os.path.join(basepath, pth)
63*4882a593Smuzhiyun                if not os.path.exists(self.init_path):
64*4882a593Smuzhiyun                    logger.error('init_path %s specified in config file cannot be found' % pth)
65*4882a593Smuzhiyun                    return False
66*4882a593Smuzhiyun        else:
67*4882a593Smuzhiyun            self.config_obj.add_section('General')
68*4882a593Smuzhiyun
69*4882a593Smuzhiyun        self.workspace_path = self.get('General', 'workspace_path', os.path.join(basepath, 'workspace'))
70*4882a593Smuzhiyun        return True
71*4882a593Smuzhiyun
72*4882a593Smuzhiyun
73*4882a593Smuzhiyun    def write(self):
74*4882a593Smuzhiyun        logger.debug('writing to config file %s' % self.config_file)
75*4882a593Smuzhiyun        self.config_obj.set('General', 'workspace_path', self.workspace_path)
76*4882a593Smuzhiyun        with open(self.config_file, 'w') as f:
77*4882a593Smuzhiyun            self.config_obj.write(f)
78*4882a593Smuzhiyun
79*4882a593Smuzhiyun    def set(self, section, option, value):
80*4882a593Smuzhiyun        if not self.config_obj.has_section(section):
81*4882a593Smuzhiyun            self.config_obj.add_section(section)
82*4882a593Smuzhiyun        self.config_obj.set(section, option, value)
83*4882a593Smuzhiyun
84*4882a593Smuzhiyunclass Context:
85*4882a593Smuzhiyun    def __init__(self, **kwargs):
86*4882a593Smuzhiyun        self.__dict__.update(kwargs)
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun
89*4882a593Smuzhiyundef read_workspace():
90*4882a593Smuzhiyun    global workspace
91*4882a593Smuzhiyun    workspace = {}
92*4882a593Smuzhiyun    if not os.path.exists(os.path.join(config.workspace_path, 'conf', 'layer.conf')):
93*4882a593Smuzhiyun        if context.fixed_setup:
94*4882a593Smuzhiyun            logger.error("workspace layer not set up")
95*4882a593Smuzhiyun            sys.exit(1)
96*4882a593Smuzhiyun        else:
97*4882a593Smuzhiyun            logger.info('Creating workspace layer in %s' % config.workspace_path)
98*4882a593Smuzhiyun            _create_workspace(config.workspace_path, config, basepath)
99*4882a593Smuzhiyun    if not context.fixed_setup:
100*4882a593Smuzhiyun        _enable_workspace_layer(config.workspace_path, config, basepath)
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun    logger.debug('Reading workspace in %s' % config.workspace_path)
103*4882a593Smuzhiyun    externalsrc_re = re.compile(r'^EXTERNALSRC(:pn-([^ =]+))? *= *"([^"]*)"$')
104*4882a593Smuzhiyun    for fn in glob.glob(os.path.join(config.workspace_path, 'appends', '*.bbappend')):
105*4882a593Smuzhiyun        with open(fn, 'r') as f:
106*4882a593Smuzhiyun            pnvalues = {}
107*4882a593Smuzhiyun            pn = None
108*4882a593Smuzhiyun            for line in f:
109*4882a593Smuzhiyun                res = externalsrc_re.match(line.rstrip())
110*4882a593Smuzhiyun                if res:
111*4882a593Smuzhiyun                    recipepn = os.path.splitext(os.path.basename(fn))[0].split('_')[0]
112*4882a593Smuzhiyun                    pn = res.group(2) or recipepn
113*4882a593Smuzhiyun                    # Find the recipe file within the workspace, if any
114*4882a593Smuzhiyun                    bbfile = os.path.basename(fn).replace('.bbappend', '.bb').replace('%', '*')
115*4882a593Smuzhiyun                    recipefile = glob.glob(os.path.join(config.workspace_path,
116*4882a593Smuzhiyun                                                        'recipes',
117*4882a593Smuzhiyun                                                        recipepn,
118*4882a593Smuzhiyun                                                        bbfile))
119*4882a593Smuzhiyun                    if recipefile:
120*4882a593Smuzhiyun                        recipefile = recipefile[0]
121*4882a593Smuzhiyun                    pnvalues['srctree'] = res.group(3)
122*4882a593Smuzhiyun                    pnvalues['bbappend'] = fn
123*4882a593Smuzhiyun                    pnvalues['recipefile'] = recipefile
124*4882a593Smuzhiyun                elif line.startswith('# srctreebase: '):
125*4882a593Smuzhiyun                    pnvalues['srctreebase'] = line.split(':', 1)[1].strip()
126*4882a593Smuzhiyun            if pnvalues:
127*4882a593Smuzhiyun                if not pn:
128*4882a593Smuzhiyun                    raise DevtoolError("Found *.bbappend in %s, but could not determine EXTERNALSRC:pn-*. "
129*4882a593Smuzhiyun                            "Maybe still using old syntax?" % config.workspace_path)
130*4882a593Smuzhiyun                if not pnvalues.get('srctreebase', None):
131*4882a593Smuzhiyun                    pnvalues['srctreebase'] = pnvalues['srctree']
132*4882a593Smuzhiyun                logger.debug('Found recipe %s' % pnvalues)
133*4882a593Smuzhiyun                workspace[pn] = pnvalues
134*4882a593Smuzhiyun
135*4882a593Smuzhiyundef create_workspace(args, config, basepath, workspace):
136*4882a593Smuzhiyun    if args.layerpath:
137*4882a593Smuzhiyun        workspacedir = os.path.abspath(args.layerpath)
138*4882a593Smuzhiyun    else:
139*4882a593Smuzhiyun        workspacedir = os.path.abspath(os.path.join(basepath, 'workspace'))
140*4882a593Smuzhiyun    _create_workspace(workspacedir, config, basepath)
141*4882a593Smuzhiyun    if not args.create_only:
142*4882a593Smuzhiyun        _enable_workspace_layer(workspacedir, config, basepath)
143*4882a593Smuzhiyun
144*4882a593Smuzhiyundef _create_workspace(workspacedir, config, basepath):
145*4882a593Smuzhiyun    import bb
146*4882a593Smuzhiyun
147*4882a593Smuzhiyun    confdir = os.path.join(workspacedir, 'conf')
148*4882a593Smuzhiyun    if os.path.exists(os.path.join(confdir, 'layer.conf')):
149*4882a593Smuzhiyun        logger.info('Specified workspace already set up, leaving as-is')
150*4882a593Smuzhiyun    else:
151*4882a593Smuzhiyun        # Add a config file
152*4882a593Smuzhiyun        bb.utils.mkdirhier(confdir)
153*4882a593Smuzhiyun        with open(os.path.join(confdir, 'layer.conf'), 'w') as f:
154*4882a593Smuzhiyun            f.write('# ### workspace layer auto-generated by devtool ###\n')
155*4882a593Smuzhiyun            f.write('BBPATH =. "$' + '{LAYERDIR}:"\n')
156*4882a593Smuzhiyun            f.write('BBFILES += "$' + '{LAYERDIR}/recipes/*/*.bb \\\n')
157*4882a593Smuzhiyun            f.write('            $' + '{LAYERDIR}/appends/*.bbappend"\n')
158*4882a593Smuzhiyun            f.write('BBFILE_COLLECTIONS += "workspacelayer"\n')
159*4882a593Smuzhiyun            f.write('BBFILE_PATTERN_workspacelayer = "^$' + '{LAYERDIR}/"\n')
160*4882a593Smuzhiyun            f.write('BBFILE_PATTERN_IGNORE_EMPTY_workspacelayer = "1"\n')
161*4882a593Smuzhiyun            f.write('BBFILE_PRIORITY_workspacelayer = "99"\n')
162*4882a593Smuzhiyun            f.write('LAYERSERIES_COMPAT_workspacelayer = "${LAYERSERIES_COMPAT_core}"\n')
163*4882a593Smuzhiyun        # Add a README file
164*4882a593Smuzhiyun        with open(os.path.join(workspacedir, 'README'), 'w') as f:
165*4882a593Smuzhiyun            f.write('This layer was created by the OpenEmbedded devtool utility in order to\n')
166*4882a593Smuzhiyun            f.write('contain recipes and bbappends that are currently being worked on. The idea\n')
167*4882a593Smuzhiyun            f.write('is that the contents is temporary - once you have finished working on a\n')
168*4882a593Smuzhiyun            f.write('recipe you use the appropriate method to move the files you have been\n')
169*4882a593Smuzhiyun            f.write('working on to a proper layer. In most instances you should use the\n')
170*4882a593Smuzhiyun            f.write('devtool utility to manage files within it rather than modifying files\n')
171*4882a593Smuzhiyun            f.write('directly (although recipes added with "devtool add" will often need\n')
172*4882a593Smuzhiyun            f.write('direct modification.)\n')
173*4882a593Smuzhiyun            f.write('\nIf you no longer need to use devtool or the workspace layer\'s contents\n')
174*4882a593Smuzhiyun            f.write('you can remove the path to this workspace layer from your conf/bblayers.conf\n')
175*4882a593Smuzhiyun            f.write('file (and then delete the layer, if you wish).\n')
176*4882a593Smuzhiyun            f.write('\nNote that by default, if devtool fetches and unpacks source code, it\n')
177*4882a593Smuzhiyun            f.write('will place it in a subdirectory of a "sources" subdirectory of the\n')
178*4882a593Smuzhiyun            f.write('layer. If you prefer it to be elsewhere you can specify the source\n')
179*4882a593Smuzhiyun            f.write('tree path on the command line.\n')
180*4882a593Smuzhiyun
181*4882a593Smuzhiyundef _enable_workspace_layer(workspacedir, config, basepath):
182*4882a593Smuzhiyun    """Ensure the workspace layer is in bblayers.conf"""
183*4882a593Smuzhiyun    import bb
184*4882a593Smuzhiyun    bblayers_conf = os.path.join(basepath, 'conf', 'bblayers.conf')
185*4882a593Smuzhiyun    if not os.path.exists(bblayers_conf):
186*4882a593Smuzhiyun        logger.error('Unable to find bblayers.conf')
187*4882a593Smuzhiyun        return
188*4882a593Smuzhiyun    if os.path.abspath(workspacedir) != os.path.abspath(config.workspace_path):
189*4882a593Smuzhiyun        removedir = config.workspace_path
190*4882a593Smuzhiyun    else:
191*4882a593Smuzhiyun        removedir = None
192*4882a593Smuzhiyun    _, added = bb.utils.edit_bblayers_conf(bblayers_conf, workspacedir, removedir)
193*4882a593Smuzhiyun    if added:
194*4882a593Smuzhiyun        logger.info('Enabling workspace layer in bblayers.conf')
195*4882a593Smuzhiyun    if config.workspace_path != workspacedir:
196*4882a593Smuzhiyun        # Update our config to point to the new location
197*4882a593Smuzhiyun        config.workspace_path = workspacedir
198*4882a593Smuzhiyun        config.write()
199*4882a593Smuzhiyun
200*4882a593Smuzhiyun
201*4882a593Smuzhiyundef main():
202*4882a593Smuzhiyun    global basepath
203*4882a593Smuzhiyun    global config
204*4882a593Smuzhiyun    global context
205*4882a593Smuzhiyun
206*4882a593Smuzhiyun    if sys.getfilesystemencoding() != "utf-8":
207*4882a593Smuzhiyun        sys.exit("Please use a locale setting which supports utf-8.\nPython can't change the filesystem locale after loading so we need a utf-8 when python starts or things won't work.")
208*4882a593Smuzhiyun
209*4882a593Smuzhiyun    context = Context(fixed_setup=False)
210*4882a593Smuzhiyun
211*4882a593Smuzhiyun    # Default basepath
212*4882a593Smuzhiyun    basepath = os.path.dirname(os.path.abspath(__file__))
213*4882a593Smuzhiyun
214*4882a593Smuzhiyun    parser = argparse_oe.ArgumentParser(description="OpenEmbedded development tool",
215*4882a593Smuzhiyun                                        add_help=False,
216*4882a593Smuzhiyun                                        epilog="Use %(prog)s <subcommand> --help to get help on a specific command")
217*4882a593Smuzhiyun    parser.add_argument('--basepath', help='Base directory of SDK / build directory')
218*4882a593Smuzhiyun    parser.add_argument('--bbpath', help='Explicitly specify the BBPATH, rather than getting it from the metadata')
219*4882a593Smuzhiyun    parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true')
220*4882a593Smuzhiyun    parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true')
221*4882a593Smuzhiyun    parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR')
222*4882a593Smuzhiyun
223*4882a593Smuzhiyun    global_args, unparsed_args = parser.parse_known_args()
224*4882a593Smuzhiyun
225*4882a593Smuzhiyun    # Help is added here rather than via add_help=True, as we don't want it to
226*4882a593Smuzhiyun    # be handled by parse_known_args()
227*4882a593Smuzhiyun    parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
228*4882a593Smuzhiyun                        help='show this help message and exit')
229*4882a593Smuzhiyun
230*4882a593Smuzhiyun    if global_args.debug:
231*4882a593Smuzhiyun        logger.setLevel(logging.DEBUG)
232*4882a593Smuzhiyun    elif global_args.quiet:
233*4882a593Smuzhiyun        logger.setLevel(logging.ERROR)
234*4882a593Smuzhiyun
235*4882a593Smuzhiyun    if global_args.basepath:
236*4882a593Smuzhiyun        # Override
237*4882a593Smuzhiyun        basepath = global_args.basepath
238*4882a593Smuzhiyun        if os.path.exists(os.path.join(basepath, '.devtoolbase')):
239*4882a593Smuzhiyun            context.fixed_setup = True
240*4882a593Smuzhiyun    else:
241*4882a593Smuzhiyun        pth = basepath
242*4882a593Smuzhiyun        while pth != '' and pth != os.sep:
243*4882a593Smuzhiyun            if os.path.exists(os.path.join(pth, '.devtoolbase')):
244*4882a593Smuzhiyun                context.fixed_setup = True
245*4882a593Smuzhiyun                basepath = pth
246*4882a593Smuzhiyun                break
247*4882a593Smuzhiyun            pth = os.path.dirname(pth)
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun        if not context.fixed_setup:
250*4882a593Smuzhiyun            basepath = os.environ.get('BUILDDIR')
251*4882a593Smuzhiyun            if not basepath:
252*4882a593Smuzhiyun                logger.error("This script can only be run after initialising the build environment (e.g. by using oe-init-build-env)")
253*4882a593Smuzhiyun                sys.exit(1)
254*4882a593Smuzhiyun
255*4882a593Smuzhiyun    logger.debug('Using basepath %s' % basepath)
256*4882a593Smuzhiyun
257*4882a593Smuzhiyun    config = ConfigHandler(os.path.join(basepath, 'conf', 'devtool.conf'))
258*4882a593Smuzhiyun    if not config.read():
259*4882a593Smuzhiyun        return -1
260*4882a593Smuzhiyun    context.config = config
261*4882a593Smuzhiyun
262*4882a593Smuzhiyun    bitbake_subdir = config.get('General', 'bitbake_subdir', '')
263*4882a593Smuzhiyun    if bitbake_subdir:
264*4882a593Smuzhiyun        # Normally set for use within the SDK
265*4882a593Smuzhiyun        logger.debug('Using bitbake subdir %s' % bitbake_subdir)
266*4882a593Smuzhiyun        sys.path.insert(0, os.path.join(basepath, bitbake_subdir, 'lib'))
267*4882a593Smuzhiyun        core_meta_subdir = config.get('General', 'core_meta_subdir')
268*4882a593Smuzhiyun        sys.path.insert(0, os.path.join(basepath, core_meta_subdir, 'lib'))
269*4882a593Smuzhiyun    else:
270*4882a593Smuzhiyun        # Standard location
271*4882a593Smuzhiyun        import scriptpath
272*4882a593Smuzhiyun        bitbakepath = scriptpath.add_bitbake_lib_path()
273*4882a593Smuzhiyun        if not bitbakepath:
274*4882a593Smuzhiyun            logger.error("Unable to find bitbake by searching parent directory of this script or PATH")
275*4882a593Smuzhiyun            sys.exit(1)
276*4882a593Smuzhiyun        logger.debug('Using standard bitbake path %s' % bitbakepath)
277*4882a593Smuzhiyun        scriptpath.add_oe_lib_path()
278*4882a593Smuzhiyun
279*4882a593Smuzhiyun    scriptutils.logger_setup_color(logger, global_args.color)
280*4882a593Smuzhiyun
281*4882a593Smuzhiyun    if global_args.bbpath is None:
282*4882a593Smuzhiyun        try:
283*4882a593Smuzhiyun            tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
284*4882a593Smuzhiyun            try:
285*4882a593Smuzhiyun                global_args.bbpath = tinfoil.config_data.getVar('BBPATH')
286*4882a593Smuzhiyun            finally:
287*4882a593Smuzhiyun                tinfoil.shutdown()
288*4882a593Smuzhiyun        except bb.BBHandledException:
289*4882a593Smuzhiyun            return 2
290*4882a593Smuzhiyun
291*4882a593Smuzhiyun    # Search BBPATH first to allow layers to override plugins in scripts_path
292*4882a593Smuzhiyun    for path in global_args.bbpath.split(':') + [scripts_path]:
293*4882a593Smuzhiyun        pluginpath = os.path.join(path, 'lib', 'devtool')
294*4882a593Smuzhiyun        scriptutils.load_plugins(logger, plugins, pluginpath)
295*4882a593Smuzhiyun
296*4882a593Smuzhiyun    subparsers = parser.add_subparsers(dest="subparser_name", title='subcommands', metavar='<subcommand>')
297*4882a593Smuzhiyun    subparsers.required = True
298*4882a593Smuzhiyun
299*4882a593Smuzhiyun    subparsers.add_subparser_group('sdk', 'SDK maintenance', -2)
300*4882a593Smuzhiyun    subparsers.add_subparser_group('advanced', 'Advanced', -1)
301*4882a593Smuzhiyun    subparsers.add_subparser_group('starting', 'Beginning work on a recipe', 100)
302*4882a593Smuzhiyun    subparsers.add_subparser_group('info', 'Getting information')
303*4882a593Smuzhiyun    subparsers.add_subparser_group('working', 'Working on a recipe in the workspace')
304*4882a593Smuzhiyun    subparsers.add_subparser_group('testbuild', 'Testing changes on target')
305*4882a593Smuzhiyun
306*4882a593Smuzhiyun    if not context.fixed_setup:
307*4882a593Smuzhiyun        parser_create_workspace = subparsers.add_parser('create-workspace',
308*4882a593Smuzhiyun                                                        help='Set up workspace in an alternative location',
309*4882a593Smuzhiyun                                                        description='Sets up a new workspace. NOTE: other devtool subcommands will create a workspace automatically as needed, so you only need to use %(prog)s if you want to specify where the workspace should be located.',
310*4882a593Smuzhiyun                                                        group='advanced')
311*4882a593Smuzhiyun        parser_create_workspace.add_argument('layerpath', nargs='?', help='Path in which the workspace layer should be created')
312*4882a593Smuzhiyun        parser_create_workspace.add_argument('--create-only', action="store_true", help='Only create the workspace layer, do not alter configuration')
313*4882a593Smuzhiyun        parser_create_workspace.set_defaults(func=create_workspace, no_workspace=True)
314*4882a593Smuzhiyun
315*4882a593Smuzhiyun    for plugin in plugins:
316*4882a593Smuzhiyun        if hasattr(plugin, 'register_commands'):
317*4882a593Smuzhiyun            plugin.register_commands(subparsers, context)
318*4882a593Smuzhiyun
319*4882a593Smuzhiyun    args = parser.parse_args(unparsed_args, namespace=global_args)
320*4882a593Smuzhiyun
321*4882a593Smuzhiyun    try:
322*4882a593Smuzhiyun        if not getattr(args, 'no_workspace', False):
323*4882a593Smuzhiyun            read_workspace()
324*4882a593Smuzhiyun
325*4882a593Smuzhiyun        ret = args.func(args, config, basepath, workspace)
326*4882a593Smuzhiyun    except DevtoolError as err:
327*4882a593Smuzhiyun        if str(err):
328*4882a593Smuzhiyun            logger.error(str(err))
329*4882a593Smuzhiyun        ret = err.exitcode
330*4882a593Smuzhiyun    except argparse_oe.ArgumentUsageError as ae:
331*4882a593Smuzhiyun        parser.error_subcommand(ae.message, ae.subcommand)
332*4882a593Smuzhiyun
333*4882a593Smuzhiyun    return ret
334*4882a593Smuzhiyun
335*4882a593Smuzhiyun
336*4882a593Smuzhiyunif __name__ == "__main__":
337*4882a593Smuzhiyun    try:
338*4882a593Smuzhiyun        ret = main()
339*4882a593Smuzhiyun    except Exception:
340*4882a593Smuzhiyun        ret = 1
341*4882a593Smuzhiyun        import traceback
342*4882a593Smuzhiyun        traceback.print_exc()
343*4882a593Smuzhiyun    sys.exit(ret)
344