Use default debug build. - DA support
[scm/meta/abs.git] / abs
1 #!/usr/bin/env python
2 # vim: ai ts=4 sts=4 et sw=4
3 #
4 # Copyright (c) 2014, 2015, 2016 Samsung Electronics.Co.Ltd.
5 #
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
9 #
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
13 # for more details.
14 #
15
16 import sys
17 import os
18 import subprocess
19 import re
20 import argparse
21 from argparse import ArgumentParser
22 import ConfigParser
23 import glob
24 import fnmatch
25 import shutil
26 import zipfile
27 import errno
28
29 g_home = os.path.dirname(os.path.realpath(__file__))
30
31 class LocalError(Exception):
32     """Local error exception."""
33
34     pass
35
36 class Executor(object):
37     """Subprocess wrapper"""
38
39     def __init__(self, checker=None):
40         self.stdout = subprocess.PIPE
41         self.stderr = subprocess.STDOUT
42         self.checker = checker
43
44     def run(self, cmdline_or_args, show=False, checker=False):
45         """Execute external command"""
46
47         out = ''
48         try:
49             process = subprocess.Popen(cmdline_or_args, \
50                                        stdout=self.stdout, \
51                                        stderr=self.stderr, \
52                                        shell=True)
53             while True:
54                 line = process.stdout.readline()
55                 if show:
56                     print line.rstrip()
57                 out = out + '\n' + line.rstrip()
58                 if not line:
59                     break
60         except:
61             raise LocalError('Running process failed')
62
63         return out
64
65 def list_files(path, ext=None):
66
67     f_list = []
68     for root, dirnames, filenames in os.walk(path):
69         if ext is None:
70             for filename in filenames:
71                 f_list.append(os.path.join(root, filename))
72         else:
73             for filename in fnmatch.filter(filenames, '*.'+ext):
74                 f_list.append(os.path.join(root, filename))
75     return f_list
76
77 class FakeSecHead(object):
78
79     def __init__(self, fp):
80
81         self.fp = fp
82         self.sechead = '[ascection]\n'
83
84     def readline(self):
85
86         if self.sechead:
87             try:
88                 return self.sechead
89             finally:
90                 self.sechead = None
91         else:
92             return self.fp.readline()
93
94 class ErrorParser(object):
95     """Inspect specific error string"""
96
97     parsers = []
98
99     def __init__(self):
100
101         ErrorParser = {'GNU_LINKER':['(.*?):?\(\.\w+\+.*\): (.*)', \
102                                      '(.*[/\\\])?ld(\.exe)?: (.*)'], \
103                        'GNU_GCC':['(.*?):(\d+):(\d+:)? [Ee]rror: ([`\'"](.*)[\'"] undeclared .*)', \
104                                   '(.*?):(\d+):(\d+:)? [Ee]rror: (conflicting types for .*[`\'"](.*)[\'"].*)', \
105                                   '(.*?):(\d+):(\d+:)? (parse error before.*[`\'"](.*)[\'"].*)', \
106                                   '(.*?):(\d+):(\d+:)?\s*(([Ee]rror)|(ERROR)): (.*)'], \
107 #                                  '(.*?):(\d+):(\d+:)? (.*)'], \
108                        'GNU_GMAKE':['(.*):(\d*): (\*\*\* .*)', \
109                                     '.*make.*: \*\*\* .*', \
110                                     '.*make.*: Target (.*) not remade because of errors.', \
111                                     '.*[Cc]ommand not found.*', \
112                                     'Error:\s*(.*)'], \
113                        'TIZEN_NATIVE':['.*ninja: build stopped.*', \
114                                        'edje_cc: Error..(.*):(\d).*', \
115                                        'edje_cc: Error.*']}
116
117         for parser in ErrorParser:
118             parser_env = os.getenv('SDK_ERROR_'+parser)
119             if parser_env:
120                 self.parsers.append(parser_env)
121             else:
122                 for msg in ErrorParser[parser]:
123                     self.parsers.append(msg)
124
125     def check(self, full_log):
126         """Check error string line by line"""
127
128         #snipset_text = full_log[:full_log.rfind('PLATFORM_VER\t')].split('\n')
129         for line in full_log.split('\n'):
130             errline = re.search('|'.join(self.parsers), line[:1024])
131             if errline:
132                 return errline.string #Errors
133         return None #No error
134
135 class _Rootstrap(object):
136     """Tizen SDK rootstrap info.
137        Used only in Sdk class"""
138
139     rootstrap_list = None
140     sdk_path = None
141
142     def __init__(self, sdk_path=None, config=None, rootstrap_search=None):
143
144         self.tizen = sdk_path
145         self.list_rootstrap(rootstrap_search)
146         self.config_file = config
147
148     def list_rootstrap(self, rootstrap_search=None):
149         """List all the rootstraps"""
150
151         rs_prefix = 'mobile|wearable'
152         if rootstrap_search is not None:
153             rs_prefix = rootstrap_search
154         print 'Set rs_prefix: %s' % rs_prefix
155
156         if self.rootstrap_list != None:
157             return self.rootstrap_list
158
159         cmdline = self.tizen + ' list rootstrap'
160         ret = Executor().run(cmdline, show=False)
161         for x in ret.splitlines():
162             if re.search('(%s)-(2.4|3.0|4.0|5.0)-(device|emulator|device64|emulator64).core.*' % rs_prefix, x):
163                 if self.rootstrap_list == None:
164                     self.rootstrap_list = []
165                 self.rootstrap_list.append(x.split(' ')[0])
166             else:
167                 print 'No search result for %s' % '(%s)-(2.4|3.0|4.0|5.0)-(device|emulator|device64|emulator64).core.*' % rs_prefix
168         return self.rootstrap_list
169
170     def check_rootstrap(self, rootstrap, show=True):
171         """Specific rootstrap is in the SDK
172            Otherwise use default"""
173
174         if rootstrap == None:
175             rootstrap = 'mobile-3.0-emulator.core' #default
176         if rootstrap in self.rootstrap_list:
177             return rootstrap
178         else:
179             if show == True:
180                 print '  ERROR: Rootstrap [%s] does not exist' % rootstrap
181                 print '  Update your rootstrap or use one of:\n    %s' \
182                        % '\n    '.join(self.list_rootstrap())
183             return None
184
185 class Sdk(object):
186     """Tizen SDK related job"""
187
188     rs = None #_Rootstrap class instance
189     rootstrap_list = None
190     sdk_to_search = ['tizen-studio/tools/ide/bin/tizen', \
191                      'tizen-sdk/tools/ide/bin/tizen', \
192                      'tizen-sdk-ux/tools/ide/bin/tizen', \
193                      'tizen-sdk-cli/tools/ide/bin/tizen']
194
195     def __init__(self, sdkpath=None, rootstrap_search=None):
196
197         self.error_parser = ErrorParser()
198         self.runtool = Executor(checker=self.error_parser)
199
200         self.home = os.getenv('HOME')
201         self.config_file = os.path.join(g_home, '.abs')
202
203         if sdkpath is None:
204             self.tizen = self.get_user_root()
205             if self.tizen is None or self.tizen == '':
206                 for i in self.sdk_to_search:
207                     if os.path.isfile(os.path.join(self.home, i)):
208                         self.tizen = os.path.join(self.home, i)
209                         break
210         else:
211             self.tizen = os.path.join(sdkpath, 'tools/ide/bin/tizen')
212             self.update_user_root(self.tizen)
213
214         if not os.path.isfile(self.tizen):
215             print 'Cannot locate cli tool'
216             raise LocalError('Fail to locate cli tool')
217
218         self.rs = _Rootstrap(sdk_path=self.tizen, config=self.config_file, rootstrap_search=rootstrap_search)
219
220     def get_user_root(self):
221
222         if os.path.isfile(self.config_file):
223             config = ConfigParser.RawConfigParser()
224             config.read(self.config_file)
225             return config.get('Global', 'tizen')
226         return None
227
228     def update_user_root(self, path):
229
230         if not os.path.isfile(self.config_file):
231             with open(self.config_file, 'w') as f:
232                 f.write('[Global]\n')
233                 f.write('tizen = %s\n' % path)
234             return
235
236         config = ConfigParser.RawConfigParser()
237         config.read(self.config_file)
238         config.set('Global', 'tizen', path)
239         with open(self.config_file, 'wb') as cf:
240             config.write(cf)
241
242     def list_rootstrap(self):
243         return self.rs.list_rootstrap()
244
245     def check_rootstrap(self, rootstrap):
246         return self.rs.check_rootstrap(rootstrap)
247
248     def _run(self, command, args, show=True, checker=False):
249         """Run a tizen command"""
250
251         cmd = [self.tizen, command] + args
252         print '\nRunning command:\n    %s' % ' '.join(cmd)
253         return self.runtool.run(' '.join(cmd), show=show, checker=checker)
254
255     def copytree2(self, src, dst, symlinks=False, ignore=None):
256         """Copy with Ignore & Overwrite"""
257         names = os.listdir(src)
258         if ignore is not None:
259             ignored_names = ignore(src, names)
260         else:
261             ignored_names = set()
262
263         try:
264             os.makedirs(dst)
265         except:
266             pass
267
268         errors = []
269         for name in names:
270             if name in ignored_names:
271                 continue
272             srcname = os.path.join(src, name)
273             dstname = os.path.join(dst, name)
274             try:
275                 if symlinks and os.path.islink(srcname):
276                     linkto = os.readlink(srcname)
277                     os.symlink(linkto, dstname)
278                 elif os.path.isdir(srcname):
279                     self.copytree2(srcname, dstname, symlinks, ignore)
280                 else:
281                     # Will raise a SpecialFileError for unsupported file types
282                     shutil.copy2(srcname, dstname)
283             # catch the Error from the recursive copytree so that we can
284             # continue with other files
285             except shutil.Error, err:
286                 errors.extend(err.args[0])
287             except EnvironmentError, why:
288                 errors.append((srcname, dstname, str(why)))
289         try:
290             shutil.copystat(src, dst)
291         except OSError, why:
292             if WindowsError is not None and isinstance(why, WindowsError):
293                 # Copying file access times may fail on Windows
294                 pass
295             else:
296                 errors.append((src, dst, str(why)))
297         #if errors:
298          #   raise shutil.Error, errors
299
300     def _copy_build_output(self, src, dst):
301         if not os.path.isdir(src) :
302             return
303         try:
304             self.copytree2(src, dst, ignore=shutil.ignore_patterns('*.edc', '*.po', 'objs', '*.info', '*.so', 'CMakeLists.txt', '*.h', '*.c'))
305         except OSError as exc:
306             # File already exist
307             if exc.errno == errno.EEXIST:
308                 shutil.copy(src, dst)
309             if exc.errno == errno.ENOENT:
310                 shutil.copy(src, dst)
311             else:
312                 raise
313
314     def _package_sharedlib(self, project_path, conf, app_name):
315         """If -r option used for packaging, make zip file from copied files"""
316         #project_path=project['path']
317         project_build_output_path=os.path.join(project_path, conf)
318         package_path=os.path.join(project_build_output_path, '.pkg')
319
320         if os.path.isdir(package_path):
321             shutil.rmtree(package_path)
322         os.makedirs(package_path)
323         os.makedirs(os.path.join(package_path, 'lib'))
324
325         #Copy project resource
326         self._copy_build_output(os.path.join(project_path, 'lib'), os.path.join(package_path, 'lib'))
327         self._copy_build_output(os.path.join(project_path, 'res'), os.path.join(package_path, 'res'))
328
329         #Copy built res resource
330         self._copy_build_output(os.path.join(project_build_output_path, 'res'), os.path.join(package_path, 'res'))
331
332         #Copy so library file
333         for filename in list_files(project_build_output_path, 'so'):
334             shutil.copy(filename, os.path.join(package_path, 'lib'))
335
336         # Copy so library file
337         zipname=app_name + '.zip'
338         rsrc_zip = os.path.join(project_build_output_path, zipname)
339         myZipFile = zipfile.ZipFile(rsrc_zip, 'w')
340         for filename in list_files(package_path):
341             try:
342                 myZipFile.write(filename, filename.replace(package_path, ''))
343             except Exception, e:
344                 print str(e)
345         myZipFile.close()
346         return rsrc_zip
347
348     def build_native(self, source, rootstrap=None, arch=None, conf='Debug', jobs=None):
349         """SDK CLI build command"""
350
351         _rootstrap = self.check_rootstrap(rootstrap)
352         if _rootstrap == None:
353             raise LocalError('Rootstrap %s not exist' % rootstrap)
354
355         if rootstrap is None and arch is None:
356             rootstrap = _rootstrap
357             self.arch = 'x86'
358         elif arch is None:
359             if 'emulator64' in rootstrap: self.arch = 'x86_64'
360             elif 'device64' in rootstrap: self.arch = 'aarch64'
361             elif 'emulator' in rootstrap: self.arch = 'x86'
362             elif 'device' in rootstrap: self.arch = 'arm'
363         elif rootstrap is None:
364             if arch not in ['x86', 'arm', 'aarch64', 'x86_64']:
365                 raise LocalError('Architecture and rootstrap mismatch')
366
367             rootstrap = _rootstrap
368             if arch == 'x86_64': rootstrap = rootstrap.replace('emulator', 'emulator64')
369             elif arch == 'aarch64': rootstrap = rootstrap.replace('emulator', 'device64')
370             elif arch == 'arm': rootstrap = rootstrap.replace('emulator', 'device')
371
372         for x in source.project_list:
373             b_args = ['-r', rootstrap, '-a', self.arch, '-C', conf, '-c', 'gcc']
374             if jobs is not None: b_args.extend(['-j', jobs])
375             b_args.extend(['--', x['path']])
376             out = self._run('build-native', b_args, checker=True)
377             logpath = os.path.join(source.output_dir, \
378                                   'build_%s_%s' % (rootstrap, os.path.basename(x['path'])))
379             if not os.path.isdir(source.output_dir):
380                 os.makedirs(source.output_dir)
381             with open(logpath, 'w') as lf:
382                 lf.write(out)
383             ret = self.error_parser.check(out)
384             if True:
385                 with open(logpath+'.log', 'w') as lf:
386                     lf.write(out)
387             if ret:
388                 raise LocalError(ret)
389
390     def package(self, source, cert=None, pkg_type=None, conf='Debug', manual_strip=False):
391         """SDK CLI package command
392             IF Debug + Manual Strip off then generate package-name-debug.tpk
393             IF Debug + Manual Strip on then generate package-name.tpk with custom strip
394             IF Release then generate package-name.tpk with strip option
395         """
396
397         if cert is None: cert = 'ABS'
398         if pkg_type is None: pkg_type = 'tpk'
399         if conf is None: conf = 'Debug'
400
401         final_app = ''
402         main_args = ['-t', pkg_type, '-s', cert]
403         out = '' #logfile
404
405         # remove tpk or zip file on project path
406         package_list = []
407         for i, x in enumerate(source.project_list):
408             package_list.extend(list_files(os.path.join(x['path'], conf), ext='tpk'))
409             package_list.extend(list_files(os.path.join(x['path'], conf), ext='zip'))
410
411         for k in package_list :
412             print ' package list ' + k;
413             os.remove(k)
414
415         # Manual strip
416         if manual_strip == True :
417             strip_cmd='';
418             if self.arch == None:
419                 raise LocalError('Architecture is Noen')
420             elif self.arch == 'x86' :
421                 strip_cmd = os.path.join(os.path.dirname(self.tizen), '../../i386-linux-gnueabi-gcc-4.9/bin/i386-linux-gnueabi-strip')
422             elif self.arch == 'arm' :
423                 strip_cmd = os.path.join(os.path.dirname(self.tizen), '../../arm-linux-gnueabi-gcc-4.9/bin/arm-linux-gnueabi-strip')
424             elif self.arch == 'x86_64' :
425                 strip_cmd = os.path.join(os.path.dirname(self.tizen), '../../x86_64-linux-gnu-gcc-4.9/bin/x86_64-linux-gnu-strip')
426             elif self.arch == 'aarch64' :
427                 strip_cmd = os.path.join(os.path.dirname(self.tizen), '../../aarch64-linux-gnu-gcc-4.9/bin/aarch64-linux-gnu-strip')
428
429             print strip_cmd
430
431             for i, x in enumerate(source.project_list):
432                 dir = os.path.join(x['path'], conf)
433                 files = [os.path.join(dir,f) for f in os.listdir(dir) if os.path.isfile(os.path.join(dir,f))]
434                 for k in files:
435                     cmdline = strip_cmd + ' ' + k;
436                     #print 'my command line ' + cmdline;
437                     Executor().run(cmdline, show=False)
438         elif conf == 'Release':
439             main_args.extend(['--strip', 'on'])
440
441         for i, x in enumerate(source.project_list):
442             if x['type'] == 'app':
443                 out = '%s\n%s' % (out, \
444                       self._run('package', main_args + ['--',os.path.join(x['path'],conf)]))
445                 try:
446                     final_app = list_files(os.path.join(x['path'], conf), ext='tpk')[0]
447                 except:
448                     raise LocalError('TPK file not generated for %s.' % x['APPNAME'])
449                 x['out_package'] = final_app
450             elif x['type'] == 'sharedLib':
451                 self._package_sharedlib(x['path'], conf, x['APPNAME'])
452                 x['out_package'] = list_files(os.path.join(x['path'], conf), ext='zip')[0]
453             else:
454                 raise LocalError('Not supported project type %s' % x['type'])
455
456         if source.b_multi == True:
457             extra_args=[]
458             print 'THIS IS MULTI PROJECT'
459             for i, x in enumerate(source.project_list):
460                 if x['out_package'] != final_app and x['type'] == 'app':
461                     extra_args.extend(['-r', x['out_package']])
462                 elif x['type'] == 'sharedLib':
463                     extra_args.extend(['-r', x['out_package']])
464
465             extra_args.extend(['--', final_app])
466             out = self._run('package', main_args + extra_args)
467
468         #TODO: signature validation check failed : Invalid file reference. An unsigned file was found.
469         print 'Packaging final step again!'
470         out = self._run('package', main_args + ['--', final_app])
471
472         #Copy tpk to output directory
473         if conf == 'Debug' and manual_strip == False :
474             basename = os.path.splitext(final_app)[0]
475             newname = basename +'-debug.tpk'
476             os.rename(final_app, newname)
477             shutil.copy(newname, source.output_dir)
478         else :
479             shutil.copy(final_app, source.output_dir)
480
481     def clean(self, source):
482         """SDK CLI clean command"""
483
484         if os.path.isdir(source.multizip_path):
485             shutil.rmtree(source.multizip_path)
486
487         if os.path.isfile(os.path.join(source.multizip_path, '.zip')):
488             os.remove(os.path.join(source.multizip_path, '.zip'))
489
490         for x in source.project_list:
491             self._run('clean', ['--', x['path']], show=False)
492
493 class Source(object):
494     """Project source related job"""
495
496     workspace = '' #Project root directory
497     project_list = []
498     b_multi = False
499     multi_conf_file = 'WORKSPACE' #Assume multi-project if this file exist.
500     multizip_path = '' #For multi-project packaging -r option
501     property_dict = {}
502     output_dir = '_abs_out_'
503
504     def __init__(self, src=None):
505
506         if src == None:
507             self.workspace = os.getcwd()
508         else:
509             self.workspace = os.path.abspath(src)
510         self.output_dir = os.path.join(self.workspace, self.output_dir)
511
512         os.environ['workspace_loc']=str(os.path.realpath(self.workspace))
513
514         self.multizip_path = os.path.join(self.workspace, 'multizip')
515         self.pre_process()
516
517     def set_properties(self, path):
518         """Fetch all properties from project_def.prop"""
519
520         mydict = {}
521         cp = ConfigParser.SafeConfigParser()
522         cp.optionxform = str
523         cp.readfp(FakeSecHead(open(os.path.join(path, 'project_def.prop'))))
524         for x in cp.items('ascection'):
525             mydict[x[0]] = x[1]
526         mydict['path'] = path
527         return mydict
528
529     def set_user_options(self, c_opts=None, cpp_opts=None, link_opts=None):
530         if c_opts is not None:
531             os.environ['USER_C_OPTS'] = c_opts
532             print 'Set USER_C_OPTS=[%s]' % os.getenv('USER_C_OPTS')
533         if cpp_opts is not None:
534             os.environ['USER_CPP_OPTS'] = cpp_opts
535             print 'Set USER_CPP_OPTS=[%s]' % os.getenv('USER_CPP_OPTS')
536         if link_opts is not None:
537             os.environ['USER_LINK_OPTS'] = link_opts
538             print 'Set USER_LINK_OPTS=[%s]' % os.getenv('USER_LINK_OPTS')
539
540     def pre_process(self):
541
542         if os.path.isfile(os.path.join(self.workspace, self.multi_conf_file)):
543             self.b_multi = True
544             with open(os.path.join(self.workspace, self.multi_conf_file)) as f:
545                 for line in f:
546                     file_path = os.path.join(self.workspace, line.rstrip())
547                     self.project_list.append(self.set_properties(file_path))
548         else:
549             self.b_multi = False
550             file_path = os.path.join(self.workspace)
551             self.project_list.append(self.set_properties(file_path))
552
553 def argument_parsing(argv):
554     """Any arguments passed from user"""
555
556     parser = argparse.ArgumentParser(description='ABS command line interface')
557
558     subparsers = parser.add_subparsers(dest='subcommands')
559
560     #### [subcommand - BUILD] ####
561     build = subparsers.add_parser('build')
562     build.add_argument('-w', '--workspace', action='store', dest='workspace', \
563                         help='source directory')
564     build.add_argument('-r', '--rootstrap', action='store', dest='rootstrap', \
565                         help='(ex, mobile-3.0-device.core) rootstrap name')
566     build.add_argument('-a', '--arch', action='store', dest='arch', \
567                         help='(x86|arm|x86_64|aarch64) Architecture to build')
568     build.add_argument('-t', '--type', action='store', dest='type', \
569                         help='(tpk|wgt) Packaging type')
570     build.add_argument('-s', '--cert', action='store', dest='cert', \
571                         help='(ex, ABS) Certificate profile name')
572     build.add_argument('-c', '--conf', action='store',default='Release', dest='conf', \
573                         help='(ex, Debug|Release) Build Configuration')
574     build.add_argument('-j', '--jobs', action='store', dest='jobs', \
575                         help='(number of jobs) The number of parallel builds')
576     build.add_argument('--sdkpath', action='store', dest='sdkpath', \
577                         help='Specify Tizen SDK installation root (one time init).' \
578                              ' ex) /home/yours/tizen-sdk/')
579     build.add_argument('--profile-to-search', action='store', dest='profiletosearch', \
580                         help='Rootstrap profile prefix.' \
581                              ' ex) (mobile|wearable|da-hfp)')
582     build.add_argument('--c-opts', action='store', dest='c_opts', \
583                         help='Extra compile options USER_C_OPTS')
584     build.add_argument('--cpp-opts', action='store', dest='cpp_opts', \
585                         help='Extra compile options USER_CPP_OPTS')
586     build.add_argument('--link-opts', action='store', dest='link_opts', \
587                         help='Extra linking options USER_LINK_OPTS')
588
589     return parser.parse_args(argv[1:])
590
591 def build_main(args):
592     """Command [build] entry point."""
593
594     try:
595         my_source = Source(src=args.workspace)
596
597         my_source.set_user_options(c_opts=args.c_opts, cpp_opts=args.cpp_opts, link_opts=args.link_opts)
598         print '-------------------'
599         print '(%s)' % args.profiletosearch
600         print '-------------------'
601         my_sdk = Sdk(sdkpath=args.sdkpath, rootstrap_search=args.profiletosearch)
602         my_sdk.clean(my_source)
603         my_sdk.build_native(my_source, rootstrap=args.rootstrap, arch=args.arch, jobs=args.jobs)
604         if args.conf == 'Debug' :
605             my_sdk.package(my_source, pkg_type=args.type, cert=args.cert)
606             my_sdk.package(my_source, pkg_type=args.type, cert=args.cert, manual_strip=True)
607         else :
608             my_sdk.package(my_source, pkg_type=args.type, cert=args.cert, manual_strip=True)
609
610     except Exception as err:
611         wrk = os.path.join(os.path.abspath(args.workspace), '_abs_out_')
612         if not os.path.isdir(wrk):
613             os.makedirs(wrk)
614         with open(os.path.join(wrk, 'build_EXCEPTION.log'), 'w') as ef:
615             ef.write('Exception %s' % str(err))
616         raise err
617
618 def main(argv):
619     """Script entry point."""
620
621     print 'ABS SCRIPT FROM GIT'
622
623     args = argument_parsing(argv)
624
625     if args.subcommands == 'build':
626         return build_main(args)
627     else:
628         print 'Unsupported command %s' % args.subcommands
629         raise LocalError('Command %s not supported' % args.subcommands)
630
631 if __name__ == '__main__':
632
633     try:
634         sys.exit(main(sys.argv))
635     except Exception, e:
636         print 'Exception %s' % str(e)
637         #FIXME: Remove hard-coded output directory.
638         if not os.path.isdir('_abs_out_'):
639             os.makedirs('_abs_out_')
640         with open(os.path.join('_abs_out_', 'build_EXCEPTION.log'), 'w') as ef:
641             ef.write('Exception %s' % repr(e))
642         sys.exit(1)