2 # vim: ai ts=4 sts=4 et sw=4
4 # Copyright (c) 2014, 2015, 2016 Samsung Electronics.Co.Ltd.
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the Free
8 # Software Foundation; version 2 of the License
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 from argparse import ArgumentParser
29 g_home = os.path.dirname(os.path.realpath(__file__))
30 class LocalError(Exception):
31 """Local error exception."""
35 class Executor(object):
36 """Subprocess wrapper"""
38 def __init__(self, checker=None):
39 self.stdout = subprocess.PIPE
40 self.stderr = subprocess.STDOUT
41 self.checker = checker
43 def run(self, cmdline_or_args, show=False, checker=False):
44 """Execute external command"""
48 process = subprocess.Popen(cmdline_or_args, \
53 line = process.stdout.readline()
56 out = out + '\n' + line.rstrip()
60 raise LocalError('Running process failed')
64 def list_files(path, ext=None):
67 for root, dirnames, filenames in os.walk(path):
69 for filename in filenames:
70 f_list.append(os.path.join(root, filename))
72 for filename in fnmatch.filter(filenames, '*.'+ext):
73 f_list.append(os.path.join(root, filename))
76 class FakeSecHead(object):
78 def __init__(self, fp):
81 self.sechead = '[ascection]\n'
91 return self.fp.readline()
93 class ErrorParser(object):
94 """Inspect specific error string"""
100 ErrorParser = {'GNU_LINKER':['(.*?):?\(\.\w+\+.*\): (.*)', \
101 '(.*[/\\\])?ld(\.exe)?: (.*)'], \
102 'GNU_GCC':['(.*?):(\d+):(\d+:)? [Ee]rror: ([`\'"](.*)[\'"] undeclared .*)', \
103 '(.*?):(\d+):(\d+:)? [Ee]rror: (conflicting types for .*[`\'"](.*)[\'"].*)', \
104 '(.*?):(\d+):(\d+:)? (parse error before.*[`\'"](.*)[\'"].*)', \
105 '(.*?):(\d+):(\d+:)?\s*(([Ee]rror)|(ERROR)): (.*)'], \
106 # '(.*?):(\d+):(\d+:)? (.*)'], \
107 'GNU_GMAKE':['(.*):(\d*): (\*\*\* .*)', \
108 '.*make.*: \*\*\* .*', \
109 '.*make.*: Target (.*) not remade because of errors.', \
110 '.*[Cc]ommand not found.*', \
112 'TIZEN_NATIVE':['.*ninja: build stopped.*', \
113 'edje_cc: Error..(.*):(\d).*', \
116 for parser in ErrorParser:
117 parser_env = os.getenv('SDK_ERROR_'+parser)
119 self.parsers.append(parser_env)
121 for msg in ErrorParser[parser]:
122 self.parsers.append(msg)
124 def check(self, full_log):
125 """Check error string line by line"""
127 #snipset_text = full_log[:full_log.rfind('PLATFORM_VER\t')].split('\n')
128 for line in full_log.split('\n'):
129 errline = re.search('|'.join(self.parsers), line[:1024])
131 return errline.string #Errors
132 return None #No error
134 class _Rootstrap(object):
135 """Tizen SDK rootstrap info.
136 Used only in Sdk class"""
138 rootstrap_list = None
141 def __init__(self, sdk_path=None, config=None, rootstrap_search=None):
143 self.tizen = sdk_path
144 self.list_rootstrap(rootstrap_search)
145 self.config_file = config
147 def list_rootstrap(self, rootstrap_search=None):
148 """List all the rootstraps"""
150 rs_prefix = 'mobile|wearable|tizeniot'
151 if rootstrap_search is not None:
152 rs_prefix = rootstrap_search
153 print 'Set rs_prefix: %s' % rs_prefix
155 if self.rootstrap_list != None:
156 return self.rootstrap_list
158 cmdline = self.tizen + ' list rootstrap'
159 ret = Executor().run(cmdline, show=False)
160 for x in ret.splitlines():
161 if re.search('(%s)-([0-9.]*)-(device.*|emulator.*).core.*' % rs_prefix, x):
162 if self.rootstrap_list == None:
163 self.rootstrap_list = []
164 self.rootstrap_list.append(x.split(' ')[0])
166 print 'No search result for %s' % '(%s)-([0-9.]*)-(device.*|emulator.*).core.*' % rs_prefix
167 return self.rootstrap_list
169 def check_rootstrap(self, rootstrap, show=True):
170 """Specific rootstrap is in the SDK
171 Otherwise use default"""
173 if rootstrap == None:
174 rootstrap = 'mobile-3.0-emulator.core' #default
175 if rootstrap in self.rootstrap_list:
179 print ' ERROR: Rootstrap [%s] does not exist' % rootstrap
180 print ' Update your rootstrap or use one of:\n %s' \
181 % '\n '.join(self.list_rootstrap())
185 """Tizen SDK related job"""
187 rs = None #_Rootstrap class instance
188 rootstrap_list = None
189 sdk_to_search = ['tizen-studio/tools/ide/bin/tizen', \
190 'tizen-sdk/tools/ide/bin/tizen', \
191 'tizen-sdk-ux/tools/ide/bin/tizen', \
192 'tizen-sdk-cli/tools/ide/bin/tizen']
194 def __init__(self, sdkpath=None, rootstrap_search=None, no_dbus=False):
196 self.error_parser = ErrorParser()
197 self.runtool = Executor(checker=self.error_parser)
199 self.home = os.getenv('HOME')
200 self.config_file = os.path.join(g_home, '.absconfig')
203 self.tizen = self.get_user_root()
204 if self.tizen is None or self.tizen == '':
205 for i in self.sdk_to_search:
206 if os.path.isfile(os.path.join(self.home, i)):
207 self.tizen = os.path.join(self.home, i)
210 self.tizen = os.path.join(sdkpath, 'tools/ide/bin/tizen')
211 self.update_user_root(self.tizen)
213 if not os.path.isfile(self.tizen):
214 print 'Cannot locate cli tool'
215 raise LocalError('Fail to locate cli tool')
217 self.rs = _Rootstrap(sdk_path=self.tizen, config=self.config_file, rootstrap_search=rootstrap_search)
223 def get_user_root(self):
225 if os.path.isfile(self.config_file):
226 config = ConfigParser.RawConfigParser()
227 config.read(self.config_file)
228 return config.get('Global', 'tizen')
231 def update_user_root(self, path):
233 if not os.path.isfile(self.config_file):
234 with open(self.config_file, 'w') as f:
235 f.write('[Global]\n')
236 f.write('tizen = %s\n' % path)
239 config = ConfigParser.RawConfigParser()
240 config.read(self.config_file)
241 config.set('Global', 'tizen', path)
242 with open(self.config_file, 'wb') as cf:
245 def list_rootstrap(self):
246 return self.rs.list_rootstrap()
248 def check_rootstrap(self, rootstrap):
249 return self.rs.check_rootstrap(rootstrap)
251 def _run(self, command, args, show=True, checker=False):
252 """Run a tizen command"""
254 cmd = [self.tizen, command] + args
255 if command == 'package':
256 dbus_command = 'dbus-run-session -- bash; echo build | gnome-keyring-daemon --unlock; '
257 if self.no_dbus == True:
259 cmd = ['{} '.format(dbus_command)] + cmd
260 print '\nRunning command:\n %s' % ' '.join(cmd)
261 return self.runtool.run('{}'.format(' '.join(cmd)), \
262 show=show, checker=checker)
264 def copytree2(self, src, dst, symlinks=False, ignore=None):
265 """Copy with Ignore & Overwrite"""
266 names = os.listdir(src)
267 if ignore is not None:
268 ignored_names = ignore(src, names)
270 ignored_names = set()
279 if name in ignored_names:
281 srcname = os.path.join(src, name)
282 dstname = os.path.join(dst, name)
284 if symlinks and os.path.islink(srcname):
285 linkto = os.readlink(srcname)
286 os.symlink(linkto, dstname)
287 elif os.path.isdir(srcname):
288 self.copytree2(srcname, dstname, symlinks, ignore)
290 # Will raise a SpecialFileError for unsupported file types
291 shutil.copy2(srcname, dstname)
292 # catch the Error from the recursive copytree so that we can
293 # continue with other files
294 except shutil.Error, err:
295 errors.extend(err.args[0])
296 except EnvironmentError, why:
297 errors.append((srcname, dstname, str(why)))
299 shutil.copystat(src, dst)
301 if WindowsError is not None and isinstance(why, WindowsError):
302 # Copying file access times may fail on Windows
305 errors.append((src, dst, str(why)))
307 # raise shutil.Error, errors
309 def _copy_build_output(self, src, dst):
310 if not os.path.isdir(src) :
313 self.copytree2(src, dst, ignore=shutil.ignore_patterns('*.edc', '*.po', 'objs', '*.info', '*.so', 'CMakeLists.txt', '*.h', '*.c'))
314 except OSError as exc:
316 if exc.errno == errno.EEXIST:
317 shutil.copy(src, dst)
318 if exc.errno == errno.ENOENT:
319 shutil.copy(src, dst)
323 def _package_sharedlib(self, project_path, conf, app_name):
324 """If -r option used for packaging, make zip file from copied files"""
325 #project_path=project['path']
326 project_build_output_path=os.path.join(project_path, conf)
327 package_path=os.path.join(project_build_output_path, '.pkg')
329 if os.path.isdir(package_path):
330 shutil.rmtree(package_path)
331 os.makedirs(package_path)
332 os.makedirs(os.path.join(package_path, 'lib'))
334 #Copy project resource
335 self._copy_build_output(os.path.join(project_path, 'lib'), os.path.join(package_path, 'lib'))
336 self._copy_build_output(os.path.join(project_path, 'res'), os.path.join(package_path, 'res'))
338 #Copy built res resource
339 self._copy_build_output(os.path.join(project_build_output_path, 'res'), os.path.join(package_path, 'res'))
341 #Copy so library file
342 for filename in list_files(project_build_output_path, 'so'):
343 shutil.copy(filename, os.path.join(package_path, 'lib'))
345 # Copy so library file
346 zipname=app_name + '.zip'
347 rsrc_zip = os.path.join(project_build_output_path, zipname)
348 myZipFile = zipfile.ZipFile(rsrc_zip, 'w')
349 for filename in list_files(package_path):
351 myZipFile.write(filename, filename.replace(package_path, ''))
357 def build_tizen(self, source, rootstrap=None, arch=None, conf='Debug', jobs=None):
358 """SDK CLI build command"""
360 _rootstrap = self.check_rootstrap(rootstrap)
361 if _rootstrap == None:
362 raise LocalError('Rootstrap %s not exist' % rootstrap)
364 if rootstrap is None and arch is None:
365 rootstrap = _rootstrap
368 if 'emulator64' in rootstrap: self.arch = 'x86_64'
369 elif 'device64' in rootstrap: self.arch = 'aarch64'
370 elif 'emulator' in rootstrap: self.arch = 'x86'
371 elif 'device' in rootstrap: self.arch = 'arm'
372 elif rootstrap is None:
373 if arch not in ['x86', 'arm', 'aarch64', 'x86_64']:
374 raise LocalError('Architecture and rootstrap mismatch')
376 rootstrap = _rootstrap
377 if arch == 'x86_64': rootstrap = rootstrap.replace('emulator', 'emulator64')
378 elif arch == 'aarch64': rootstrap = rootstrap.replace('emulator', 'device64')
379 elif arch == 'arm': rootstrap = rootstrap.replace('emulator', 'device')
381 for x in source.project_list:
383 if x['web_app'] == True:
384 print '\n\n BUILD WEB\n'
385 b_args.extend(['--', x['path']])
386 out = self._run('build-web ', b_args, checker=True)
388 print '\n\n BUILD NATIVE\n'
390 b_args.extend(['-j', jobs])
391 b_args.extend(['-r', rootstrap, '-a', self.arch, '-C', conf])
392 b_args.extend(['--', x['path']])
393 out = self._run('build-native', b_args, checker=True)
394 logpath = os.path.join(source.output_dir, \
395 'build_%s_%s' % (rootstrap, os.path.basename(x['path'])))
396 if not os.path.isdir(source.output_dir):
397 os.makedirs(source.output_dir)
398 with open(logpath, 'w') as lf:
400 ret = self.error_parser.check(out)
402 with open(logpath+'.log', 'w') as lf:
405 raise LocalError(ret)
407 def raise_package_exception(self, out, output_dir, i, bname, appname):
408 logpath = os.path.join(output_dir, \
409 'package_%d_%s' % (i, bname))
410 if not os.path.isdir(output_dir):
411 os.makedirs(output_dir)
412 with open(logpath + '.log', 'w') as lf:
414 if 'keystore password was incorrect' in out \
415 or 'Sequence tag error' in out \
416 or 'Signing... java.io.IOException: ' in out:
417 raise LocalError('signing error for %s.' % appname)
418 raise LocalError('TPK/WGT file not generated for %s.' % appname)
420 def package(self, source, cert=None, pkg_type=None, conf='Debug', manual_strip=False):
421 """SDK CLI package command
422 IF Debug + Manual Strip off then generate package-name-debug.tpk
423 IF Debug + Manual Strip on then generate package-name.tpk with custom strip
424 IF Release then generate package-name.tpk with strip option
426 if cert is None: cert = 'ABS'
427 if pkg_type is None: pkg_type = 'tpk'
428 if conf is None: conf = 'Debug'
431 main_args = ['-t', pkg_type, '-s', cert]
432 main_args_web = ['-t', 'wgt', '-s', cert]
435 # remove tpk or zip file on project path
437 for i, x in enumerate(source.project_list):
438 package_list.extend(list_files(os.path.join(x['path'], conf), ext='tpk'))
439 package_list.extend(list_files(os.path.join(x['path'], conf), ext='zip'))
440 package_list.extend(list_files(x['path'], ext='wgt'))
442 for k in package_list :
443 print ' package list ' + k;
447 if manual_strip == True :
448 main_args.extend(['--strip', 'on'])
450 if self.arch == None:
451 raise LocalError('Architecture is None')
453 for i, x in enumerate(source.project_list):
454 dir = os.path.join(x['path'], conf)
455 if not os.path.isdir(dir):
457 files = [os.path.join(dir,f) for f in os.listdir(dir) if os.path.isfile(os.path.join(dir,f))]
459 # dir must be "project/Debug directory"
460 info_path = os.path.join(dir, "build.info")
461 # Default Strip gcc version is 6.2
463 # Parsing GCC version from build.info inside Debug directory in Project to each project
464 if os.path.exists(info_path) :
465 with open(info_path) as fp :
467 if line.startswith("toolchain=") :
469 gcc_version = re.findall("\d+\.\d+", line)[0]
471 print "Cannot find Debug/build.info. The default gcc will strip tpk"
473 print "gcc version:" + gcc_version
475 if self.arch == 'x86' :
476 if(gcc_version == "4.9"):
477 strip_cmd = os.path.join(os.path.dirname(self.tizen), '../../i386-linux-gnueabi-gcc-' + gcc_version + '/bin/i386-linux-gnueabi-strip')
479 strip_cmd = os.path.join(os.path.dirname(self.tizen), '../../i586-linux-gnueabi-gcc-' + gcc_version + '/bin/i586-linux-gnueabi-strip')
480 elif self.arch == 'arm' :
481 strip_cmd = os.path.join(os.path.dirname(self.tizen), '../../arm-linux-gnueabi-gcc-' + gcc_version + '/bin/arm-linux-gnueabi-strip')
482 elif self.arch == 'x86_64' :
483 strip_cmd = os.path.join(os.path.dirname(self.tizen), '../../x86_64-linux-gnu-gcc-' + gcc_version + '/bin/x86_64-linux-gnu-strip')
484 elif self.arch == 'aarch64' :
485 strip_cmd = os.path.join(os.path.dirname(self.tizen), '../../aarch64-linux-gnu-gcc-' + gcc_version + '/bin/aarch64-linux-gnu-strip')
490 cmdline = strip_cmd + ' ' + k;
491 Executor().run(cmdline, show=False)
493 elif conf == 'Release':
494 main_args.extend(['--strip', 'on'])
496 for i, x in enumerate(source.project_list):
497 if x['web_app'] == False:
498 if x['type'] == 'app':
499 print '\n\n PACKAGE NATIVE\n'
500 out = '%s\n%s' % (out, \
501 self._run('package', main_args + ['--',os.path.join(x['path'],conf)]))
503 final_app = list_files(os.path.join(x['path'], conf), ext='tpk')[0]
505 self.raise_package_exception(out, source.output_dir, i, os.path.basename(x['path']), x['APPNAME'])
506 x['out_package'] = final_app
507 elif x['type'] == 'sharedLib':
508 self._package_sharedlib(x['path'], conf, x['APPNAME'])
509 x['out_package'] = list_files(os.path.join(x['path'], conf), ext='zip')[0]
511 raise LocalError('Not supported project type %s' % x['type'])
512 elif x['web_app'] == True:
513 print '\n\n PACKAGE WEB\n'
514 out = '%s\n%s' % (out, \
515 self._run('package', main_args_web + ['--', os.path.join(x['path'], '.buildResult')]))
517 final_app = list_files(os.path.join(x['path'], '.buildResult'), ext='wgt')[0]
519 self.raise_package_exception(out, source.output_dir, i, os.path.basename(x['path']), x['APPNAME'])
520 x['out_package'] = final_app
522 if source.b_multi == True:
524 print 'THIS IS MULTI PROJECT'
525 for i, x in enumerate(source.project_list):
526 if x['out_package'] != final_app and x.get('type') == 'app':
527 extra_args.extend(['-r', '"%s"' % x['out_package']])
528 elif x.get('type') == 'sharedLib':
529 extra_args.extend(['-r', '"%s"' % x['out_package']])
531 extra_args.extend(['--', '"%s"' % final_app])
532 if final_app.endswith('.tpk'):
533 out = '%s\n\n%s' % (out, self._run('package', main_args + extra_args))
534 elif final_app.endswith('.wgt'):
535 out = '%s\n\n%s' % (out, self._run('package', main_args_web + extra_args))
537 #TODO: signature validation check failed : Invalid file reference. An unsigned file was found.
538 if final_app.endswith('.tpk'):
539 print 'Packaging final step again!'
540 out = '%s\n\n%s' % (out, self._run('package', main_args + ['--', '"%s"' % final_app]))
542 #Append arch to web binary
543 #if final_app.endswith('.wgt'):
544 # final_app_with_arch = final_app.replace('.wgt', '-%s.wgt' % self.arch)
545 # os.rename(final_app, final_app_with_arch)
546 # final_app = final_app_with_arch
548 #Copy tpk to output directory
549 if conf == 'Debug' and manual_strip == False :
550 basename = os.path.splitext(final_app)[0]
551 if final_app.endswith('.tpk'):
552 newname = basename +'-debug.tpk'
553 elif final_app.endswith('.wgt'):
554 newname = basename +'-debug.wgt'
555 os.rename(final_app, newname)
556 shutil.copy(newname, source.output_dir)
558 shutil.copy(final_app, source.output_dir)
561 def clean(self, source):
562 """SDK CLI clean command"""
564 if os.path.isdir(source.multizip_path):
565 shutil.rmtree(source.multizip_path)
567 if os.path.isfile(os.path.join(source.multizip_path, '.zip')):
568 os.remove(os.path.join(source.multizip_path, '.zip'))
570 for x in source.project_list:
571 self._run('clean', ['--', x['path']], show=False)
573 class Source(object):
574 """Project source related job"""
576 workspace = '' #Project root directory
579 multi_conf_file = 'WORKSPACE' #Assume multi-project if this file exist.
580 multizip_path = '' #For multi-project packaging -r option
582 output_dir = '_abs_out_'
584 def __init__(self, src=None):
587 self.workspace = os.getcwd()
589 self.workspace = os.path.abspath(src)
590 self.output_dir = os.path.join(self.workspace, self.output_dir)
592 os.environ['workspace_loc']=str(os.path.realpath(self.workspace))
594 self.multizip_path = os.path.join(self.workspace, 'multizip')
597 def set_properties(self, path):
598 """Fetch all properties from project_def.prop"""
601 cp = ConfigParser.SafeConfigParser()
603 if self.is_web_app(path):
604 mydict['web_app'] = True
606 mydict['web_app'] = False
607 cp.readfp(FakeSecHead(open(os.path.join(path, 'project_def.prop'))))
608 for x in cp.items('ascection'):
610 mydict['path'] = path
613 def set_user_options(self, c_opts=None, cpp_opts=None, link_opts=None):
614 if c_opts is not None:
615 os.environ['USER_C_OPTS'] = c_opts
616 print 'Set USER_C_OPTS=[%s]' % os.getenv('USER_C_OPTS')
617 if cpp_opts is not None:
618 os.environ['USER_CPP_OPTS'] = cpp_opts
619 print 'Set USER_CPP_OPTS=[%s]' % os.getenv('USER_CPP_OPTS')
620 if link_opts is not None:
621 os.environ['USER_LINK_OPTS'] = link_opts
622 print 'Set USER_LINK_OPTS=[%s]' % os.getenv('USER_LINK_OPTS')
624 def is_web_app(self, project_directory):
625 if os.path.isfile(os.path.join(project_directory, 'config.xml')):
629 def pre_process(self):
631 if os.path.isfile(os.path.join(self.workspace, self.multi_conf_file)):
633 with open(os.path.join(self.workspace, self.multi_conf_file)) as f:
637 file_path = os.path.join(self.workspace, line.rstrip())
638 self.project_list.append(self.set_properties(file_path))
641 file_path = os.path.join(self.workspace)
642 self.project_list.append(self.set_properties(file_path))
644 def argument_parsing(argv):
645 """Any arguments passed from user"""
647 parser = argparse.ArgumentParser(description='ABS command line interface')
649 subparsers = parser.add_subparsers(dest='subcommands')
651 #### [subcommand - BUILD] ####
652 build = subparsers.add_parser('build')
653 build.add_argument('-w', '--workspace', action='store', dest='workspace', \
654 help='source directory')
655 build.add_argument('-r', '--rootstrap', action='store', dest='rootstrap', \
656 help='(ex, mobile-3.0-device.core) rootstrap name')
657 build.add_argument('-a', '--arch', action='store', dest='arch', \
658 help='(x86|arm|x86_64|aarch64) Architecture to build')
659 build.add_argument('-t', '--type', action='store', dest='type', \
660 help='(tpk|wgt) Packaging type')
661 build.add_argument('-s', '--cert', action='store', dest='cert', \
662 help='(ex, ABS) Certificate profile name')
663 build.add_argument('-c', '--conf', action='store',default='Release', dest='conf', \
664 help='(ex, Debug|Release) Build Configuration')
665 build.add_argument('-j', '--jobs', action='store', dest='jobs', \
666 help='(number of jobs) The number of parallel builds')
667 build.add_argument('--sdkpath', action='store', dest='sdkpath', \
668 help='Specify Tizen SDK installation root (one time init).' \
669 ' ex) /home/yours/tizen-sdk/')
670 build.add_argument('--profile-to-search', action='store', dest='profiletosearch', \
671 help='Rootstrap profile prefix.' \
672 ' ex) (mobile|wearable|da-hfp)')
673 build.add_argument('--c-opts', action='store', dest='c_opts', \
674 help='Extra compile options USER_C_OPTS')
675 build.add_argument('--cpp-opts', action='store', dest='cpp_opts', \
676 help='Extra compile options USER_CPP_OPTS')
677 build.add_argument('--link-opts', action='store', dest='link_opts', \
678 help='Extra linking options USER_LINK_OPTS')
679 build.add_argument('--no-dbus', action='store', dest='no_dbus', default=False, \
680 help='Do not run dbus session before packaging')
682 return parser.parse_args(argv[1:])
684 def build_main(args):
685 """Command [build] entry point."""
688 my_source = Source(src=args.workspace)
690 my_source.set_user_options(c_opts=args.c_opts, cpp_opts=args.cpp_opts, link_opts=args.link_opts)
691 print '-------------------'
692 print '(%s)' % args.profiletosearch
693 print '-------------------'
694 my_sdk = Sdk(sdkpath=args.sdkpath, rootstrap_search=args.profiletosearch, no_dbus=args.no_dbus)
695 my_sdk.clean(my_source)
696 my_sdk.build_tizen(my_source, rootstrap=args.rootstrap, arch=args.arch, jobs=args.jobs)
697 if args.conf == 'Debug' :
698 my_sdk.package(my_source, pkg_type=args.type, cert=args.cert)
699 my_sdk.package(my_source, pkg_type=args.type, cert=args.cert, manual_strip=True)
701 my_sdk.package(my_source, pkg_type=args.type, cert=args.cert, manual_strip=True)
703 except Exception as err:
704 wrk = os.path.join(os.path.abspath(args.workspace), '_abs_out_')
705 if not os.path.isdir(wrk):
707 with open(os.path.join(wrk, 'build_EXCEPTION.log'), 'w') as ef:
708 ef.write('Exception %s' % str(err))
712 """Script entry point."""
714 args = argument_parsing(argv)
716 if args.subcommands == 'build':
717 return build_main(args)
719 print 'Unsupported command %s' % args.subcommands
720 raise LocalError('Command %s not supported' % args.subcommands)
722 if __name__ == '__main__':
725 sys.exit(main(sys.argv))
727 print 'Exception %s' % str(e)
728 #FIXME: Remove hard-coded output directory.
729 if not os.path.isdir('_abs_out_'):
730 os.makedirs('_abs_out_')
731 with open(os.path.join('_abs_out_', 'build_EXCEPTION.log'), 'w') as ef:
732 ef.write('Exception %s' % repr(e))
733 if 'keystore password was incorrect' in repr(e):