--- /dev/null
+#!/usr/bin/env python
+# vim: ai ts=4 sts=4 et sw=4
+#
+# Copyright (c) 2014, 2015, 2016 Samsung Electronics.Co.Ltd.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; version 2 of the License
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+import sys
+import os
+import subprocess
+import re
+import argparse
+from argparse import ArgumentParser
+import ConfigParser
+import glob
+import fnmatch
+import shutil
+import zipfile
+import errno
+
+g_home = os.path.dirname(os.path.realpath(__file__))
+
+class LocalError(Exception):
+ """Local error exception."""
+
+ pass
+
+class Executor(object):
+ """Subprocess wrapper"""
+
+ def __init__(self, checker=None):
+ self.stdout = subprocess.PIPE
+ self.stderr = subprocess.STDOUT
+ self.checker = checker
+
+ def run(self, cmdline_or_args, show=False, checker=False):
+ """Execute external command"""
+
+ out = ''
+ try:
+ process = subprocess.Popen(cmdline_or_args, \
+ stdout=self.stdout, \
+ stderr=self.stderr, \
+ shell=True)
+ while True:
+ line = process.stdout.readline()
+ if show:
+ print line.rstrip()
+ out = out + '\n' + line.rstrip()
+ if not line:
+ break
+ except:
+ raise LocalError('Running process failed')
+
+ return out
+
+def list_files(path, ext=None):
+
+ f_list = []
+ for root, dirnames, filenames in os.walk(path):
+ if ext is None:
+ for filename in filenames:
+ f_list.append(os.path.join(root, filename))
+ else:
+ for filename in fnmatch.filter(filenames, '*.'+ext):
+ f_list.append(os.path.join(root, filename))
+ return f_list
+
+class FakeSecHead(object):
+
+ def __init__(self, fp):
+
+ self.fp = fp
+ self.sechead = '[ascection]\n'
+
+ def readline(self):
+
+ if self.sechead:
+ try:
+ return self.sechead
+ finally:
+ self.sechead = None
+ else:
+ return self.fp.readline()
+
+class ErrorParser(object):
+ """Inspect specific error string"""
+
+ parsers = []
+
+ def __init__(self):
+
+ ErrorParser = {'GNU_LINKER':['(.*?):?\(\.\w+\+.*\): (.*)', \
+ '(.*[/\\\])?ld(\.exe)?: (.*)'], \
+ 'GNU_GCC':['(.*?):(\d+):(\d+:)? [Ee]rror: ([`\'"](.*)[\'"] undeclared .*)', \
+ '(.*?):(\d+):(\d+:)? [Ee]rror: (conflicting types for .*[`\'"](.*)[\'"].*)', \
+ '(.*?):(\d+):(\d+:)? (parse error before.*[`\'"](.*)[\'"].*)', \
+ '(.*?):(\d+):(\d+:)?\s*(([Ee]rror)|(ERROR)): (.*)'], \
+# '(.*?):(\d+):(\d+:)? (.*)'], \
+ 'GNU_GMAKE':['(.*):(\d*): (\*\*\* .*)', \
+ '.*make.*: \*\*\* .*', \
+ '.*make.*: Target (.*) not remade because of errors.', \
+ '.*[Cc]ommand not found.*', \
+ 'Error:\s*(.*)'], \
+ 'TIZEN_NATIVE':['.*ninja: build stopped.*', \
+ 'edje_cc: Error..(.*):(\d).*', \
+ 'edje_cc: Error.*']}
+
+ for parser in ErrorParser:
+ parser_env = os.getenv('SDK_ERROR_'+parser)
+ if parser_env:
+ self.parsers.append(parser_env)
+ else:
+ for msg in ErrorParser[parser]:
+ self.parsers.append(msg)
+
+ def check(self, full_log):
+ """Check error string line by line"""
+
+ #snipset_text = full_log[:full_log.rfind('PLATFORM_VER\t')].split('\n')
+ for line in full_log.split('\n'):
+ errline = re.search('|'.join(self.parsers), line[:1024])
+ if errline:
+ return errline.string #Errors
+ return None #No error
+
+class _Rootstrap(object):
+ """Tizen SDK rootstrap info.
+ Used only in Sdk class"""
+
+ rootstrap_list = None
+ sdk_path = None
+
+ def __init__(self, sdk_path=None, config=None):
+
+ self.tizen = sdk_path
+ self.list_rootstrap()
+ self.config_file = config
+
+ def list_rootstrap(self):
+ """List all the rootstraps"""
+
+ if self.rootstrap_list != None:
+ return self.rootstrap_list
+
+ cmdline = self.tizen + ' list rootstrap'
+ ret = Executor().run(cmdline, show=False)
+ for x in ret.splitlines():
+ if re.search('(mobile|wearable)-(2.4|3.0)-(device|emulator).core.*', x):
+ if self.rootstrap_list == None:
+ self.rootstrap_list = []
+ self.rootstrap_list.append(x.split(' ')[0])
+ return self.rootstrap_list
+
+ def check_rootstrap(self, rootstrap, show=True):
+ """Specific rootstrap is in the SDK
+ Otherwise use default"""
+
+ if rootstrap == None:
+ rootstrap = 'mobile-3.0-emulator.core' #default
+ if rootstrap in self.rootstrap_list:
+ return rootstrap
+ else:
+ if show == True:
+ print ' ERROR: Rootstrap [%s] does not exist' % rootstrap
+ print ' Update your rootstrap or use one of:\n %s' \
+ % '\n '.join(self.list_rootstrap())
+ return None
+
+class Sdk(object):
+ """Tizen SDK related job"""
+
+ rs = None #_Rootstrap class instance
+ rootstrap_list = None
+ sdk_to_search = ['tizen-sdk/tools/ide/bin/tizen', \
+ 'tizen-sdk-ux/tools/ide/bin/tizen', \
+ 'tizen-sdk-cli/tools/ide/bin/tizen']
+
+ def __init__(self, sdkpath=None):
+
+ self.error_parser = ErrorParser()
+ self.runtool = Executor(checker=self.error_parser)
+
+ self.home = os.getenv('HOME')
+ self.config_file = os.path.join(g_home, '.abs')
+
+ if sdkpath is None:
+ self.tizen = self.get_user_root()
+ if self.tizen is None or self.tizen == '':
+ for i in self.sdk_to_search:
+ if os.path.isfile(os.path.join(self.home, i)):
+ self.tizen = os.path.join(self.home, i)
+ break
+ else:
+ self.tizen = os.path.join(sdkpath, 'tools/ide/bin/tizen')
+ self.update_user_root(self.tizen)
+
+ if not os.path.isfile(self.tizen):
+ print 'Cannot locate cli tool'
+ raise LocalError('Fail to locate cli tool')
+
+ self.rs = _Rootstrap(sdk_path=self.tizen, config=self.config_file)
+
+ def get_user_root(self):
+
+ if os.path.isfile(self.config_file):
+ config = ConfigParser.RawConfigParser()
+ config.read(self.config_file)
+ return config.get('Global', 'tizen')
+ return None
+
+ def update_user_root(self, path):
+
+ if not os.path.isfile(self.config_file):
+ with open(self.config_file, 'w') as f:
+ f.write('[Global]\n')
+ f.write('tizen = %s\n' % path)
+ return
+
+ config = ConfigParser.RawConfigParser()
+ config.read(self.config_file)
+ config.set('Global', 'tizen', path)
+ with open(self.config_file, 'wb') as cf:
+ config.write(cf)
+
+ def list_rootstrap(self):
+ return self.rs.list_rootstrap()
+
+ def check_rootstrap(self, rootstrap):
+ return self.rs.check_rootstrap(rootstrap)
+
+ def _run(self, command, args, show=True, checker=False):
+ """Run a tizen command"""
+
+ cmd = [self.tizen, command] + args
+ print '\nRunning command:\n %s' % ' '.join(cmd)
+ return self.runtool.run(' '.join(cmd), show=show, checker=checker)
+
+ def copytree2(self, src, dst, symlinks=False, ignore=None):
+ """Copy with Ignore & Overwrite"""
+ names = os.listdir(src)
+ if ignore is not None:
+ ignored_names = ignore(src, names)
+ else:
+ ignored_names = set()
+
+ try:
+ os.makedirs(dst)
+ except:
+ pass
+
+ errors = []
+ for name in names:
+ if name in ignored_names:
+ continue
+ srcname = os.path.join(src, name)
+ dstname = os.path.join(dst, name)
+ try:
+ if symlinks and os.path.islink(srcname):
+ linkto = os.readlink(srcname)
+ os.symlink(linkto, dstname)
+ elif os.path.isdir(srcname):
+ self.copytree2(srcname, dstname, symlinks, ignore)
+ else:
+ # Will raise a SpecialFileError for unsupported file types
+ shutil.copy2(srcname, dstname)
+ # catch the Error from the recursive copytree so that we can
+ # continue with other files
+ except shutil.Error, err:
+ errors.extend(err.args[0])
+ except EnvironmentError, why:
+ errors.append((srcname, dstname, str(why)))
+ try:
+ shutil.copystat(src, dst)
+ except OSError, why:
+ if WindowsError is not None and isinstance(why, WindowsError):
+ # Copying file access times may fail on Windows
+ pass
+ else:
+ errors.append((src, dst, str(why)))
+ #if errors:
+ # raise shutil.Error, errors
+
+ def _copy_build_output(self, src, dst):
+ if not os.path.isdir(src) :
+ return
+ try:
+ self.copytree2(src, dst, ignore=shutil.ignore_patterns('*.edc', '*.po', 'objs', '*.info', '*.so', 'CMakeLists.txt', '*.h', '*.c'))
+ except OSError as exc:
+ # File already exist
+ if exc.errno == errno.EEXIST:
+ shutil.copy(src, dst)
+ if exc.errno == errno.ENOENT:
+ shutil.copy(src, dst)
+ else:
+ raise
+
+ def _package_sharedlib(self, project_path, conf, app_name):
+ """If -r option used for packaging, make zip file from copied files"""
+ #project_path=project['path']
+ project_build_output_path=os.path.join(project_path, conf)
+ package_path=os.path.join(project_build_output_path, '.pkg')
+
+ if os.path.isdir(package_path):
+ shutil.rmtree(package_path)
+ os.makedirs(package_path)
+ os.makedirs(os.path.join(package_path, 'lib'))
+
+ #Copy project resource
+ self._copy_build_output(os.path.join(project_path, 'lib'), os.path.join(package_path, 'lib'))
+ self._copy_build_output(os.path.join(project_path, 'res'), os.path.join(package_path, 'res'))
+
+ #Copy built res resource
+ self._copy_build_output(os.path.join(project_build_output_path, 'res'), os.path.join(package_path, 'res'))
+
+ #Copy so library file
+ for filename in list_files(project_build_output_path, 'so'):
+ shutil.copy(filename, os.path.join(package_path, 'lib'))
+
+ # Copy so library file
+ zipname=app_name + '.zip'
+ rsrc_zip = os.path.join(project_build_output_path, zipname)
+ myZipFile = zipfile.ZipFile(rsrc_zip, 'w')
+ for filename in list_files(package_path):
+ try:
+ myZipFile.write(filename, filename.replace(package_path, ''))
+ except Exception, e:
+ print str(e)
+ myZipFile.close()
+ return rsrc_zip
+
+ def build_native(self, source, rootstrap=None, arch=None):
+ """SDK CLI build command"""
+
+ _rootstrap = self.check_rootstrap(rootstrap)
+ if _rootstrap == None:
+ raise LocalError('Rootstrap %s not exist' % rootstrap)
+
+ if rootstrap is None and arch is None:
+ rootstrap = _rootstrap
+ arch = 'x86'
+ elif arch is None:
+ if 'emulator' in rootstrap: arch = 'x86'
+ elif 'device' in rootstrap: arch = 'arm'
+ elif rootstrap is None:
+ if arch not in ['x86', 'arm']:
+ raise LocalError('Architecture and rootstrap mismatch')
+ rootstrap = _rootstrap
+ if arch == 'arm': rootstrap = rootstrap.replace('emulator', 'device')
+
+ for x in source.project_list:
+ out = self._run('build-native', ['-r', rootstrap, '-a', arch, '--' , x['path']], checker=True)
+ logpath = os.path.join(source.output_dir, \
+ 'build_%s_%s' % (rootstrap, os.path.basename(x['path'])))
+ if not os.path.isdir(source.output_dir):
+ os.makedirs(source.output_dir)
+ with open(logpath, 'w') as lf:
+ lf.write(out)
+ ret = self.error_parser.check(out)
+ if ret:
+ with open(logpath+'.log', 'w') as lf:
+ lf.write(out)
+ raise LocalError(ret)
+
+ def package(self, source, cert=None, type=None, conf=None):
+ """SDK CLI package command"""
+
+ if cert is None: cert = 'ABS'
+ if type is None: type = 'tpk'
+ if conf is None: conf = 'Debug'
+
+ final_app = ''
+ extra_args = ['-t', type, '-s', cert]
+ out = '' #logfile
+
+ for i, x in enumerate(source.project_list):
+ if x['type'] == 'app':
+ out = '%s\n%s' % (out, \
+ self._run('package',['-t',type,'-s',cert,'--',os.path.join(x['path'],conf)]))
+ try:
+ final_app = list_files(os.path.join(x['path'], conf), ext='tpk')[0]
+ except:
+ raise LocalError('TPK file not generated for %s.' % x['APPNAME'])
+ x['out_package'] = final_app
+ elif x['type'] == 'sharedLib':
+ self._package_sharedlib(x['path'], conf, x['APPNAME'])
+ x['out_package'] = list_files(os.path.join(x['path'], conf), ext='zip')[0]
+ else:
+ raise LocalError('Not supported project type %s' % x['type'])
+
+ if source.b_multi == True:
+ print 'THIS IS MULTI PROJECT'
+ for i, x in enumerate(source.project_list):
+ if x['out_package'] != final_app and x['type'] == 'app':
+ extra_args.extend(['-r', x['out_package']])
+ elif x['type'] == 'sharedLib':
+ extra_args.extend(['-r', x['out_package']])
+
+ extra_args.extend(['--', final_app])
+ out = self._run('package', extra_args)
+
+ #TODO: signature validation check failed : Invalid file reference. An unsigned file was found.
+ print 'Packaging final step again!'
+ out = self._run('package', ['-t', type, '-s', cert, '--', final_app])
+
+ #Copy tpk to output directory
+ shutil.copy(final_app, source.output_dir)
+
+ def clean(self, source):
+ """SDK CLI clean command"""
+
+ if os.path.isdir(source.multizip_path):
+ shutil.rmtree(source.multizip_path)
+
+ if os.path.isfile(os.path.join(source.multizip_path, '.zip')):
+ os.remove(os.path.join(source.multizip_path, '.zip'))
+
+ for x in source.project_list:
+ self._run('clean', ['--', x['path']], show=False)
+
+class Source(object):
+ """Project source related job"""
+
+ workspace = '' #Project root directory
+ project_list = []
+ b_multi = False
+ multi_conf_file = 'WORKSPACE' #Assume multi-project if this file exist.
+ multizip_path = '' #For multi-project packaging -r option
+ property_dict = {}
+ output_dir = '_abs_out_'
+
+ def __init__(self, src=None):
+
+ if src == None:
+ self.workspace = os.getcwd()
+ else:
+ self.workspace = os.path.abspath(src)
+ self.output_dir = os.path.join(self.workspace, self.output_dir)
+
+ os.environ['workspace_loc']=str(os.path.realpath(self.workspace))
+
+ self.multizip_path = os.path.join(self.workspace, 'multizip')
+ self.pre_process()
+
+ def set_properties(self, path):
+ """Fetch all properties from project_def.prop"""
+
+ mydict = {}
+ cp = ConfigParser.SafeConfigParser()
+ cp.optionxform = str
+ cp.readfp(FakeSecHead(open(os.path.join(path, 'project_def.prop'))))
+ for x in cp.items('ascection'):
+ mydict[x[0]] = x[1]
+ mydict['path'] = path
+ return mydict
+
+ def pre_process(self):
+
+ if os.path.isfile(os.path.join(self.workspace, self.multi_conf_file)):
+ self.b_multi = True
+ with open(os.path.join(self.workspace, self.multi_conf_file)) as f:
+ for line in f:
+ file_path = os.path.join(self.workspace, line.rstrip())
+ self.project_list.append(self.set_properties(file_path))
+ else:
+ self.b_multi = False
+ file_path = os.path.join(self.workspace)
+ self.project_list.append(self.set_properties(file_path))
+
+def argument_parsing(argv):
+ """Any arguments passed from user"""
+
+ parser = argparse.ArgumentParser(description='ABS command line interface')
+
+ subparsers = parser.add_subparsers(dest='subcommands')
+
+ #### [subcommand - BUILD] ####
+ build = subparsers.add_parser('build')
+ build.add_argument('-w', '--workspace', action='store', dest='workspace', \
+ help='source directory')
+ build.add_argument('-r', '--rootstrap', action='store', dest='rootstrap', \
+ help='(ex, mobile-3.0-device.core) rootstrap name')
+ build.add_argument('-a', '--arch', action='store', dest='arch', \
+ help='(x86|arm) Architecture to build')
+ build.add_argument('-t', '--type', action='store', dest='type', \
+ help='(tpk|wgt) Packaging type')
+ build.add_argument('-s', '--cert', action='store', dest='cert', \
+ help='(ex, ABS) Certificate profile name')
+ build.add_argument('--sdkpath', action='store', dest='sdkpath', \
+ help='Specify Tizen SDK installation root (one time init).' \
+ ' ex) /home/yours/tizen-sdk/')
+
+ return parser.parse_args(argv[1:])
+
+def build_main(args):
+ """Command [build] entry point."""
+
+ my_source = Source(src=args.workspace)
+ my_sdk = Sdk(sdkpath=args.sdkpath)
+ my_sdk.clean(my_source)
+ my_sdk.build_native(my_source, rootstrap=args.rootstrap, arch=args.arch)
+ my_sdk.package(my_source, type=args.type, cert=args.cert)
+
+def main(argv):
+ """Script entry point."""
+
+ args = argument_parsing(argv)
+
+ if args.subcommands == 'build':
+ return build_main(args)
+ else:
+ print 'Unsupported command %s' % args.subcommands
+ raise LocalError('Command %s not supported' % args.subcommands)
+
+if __name__ == '__main__':
+
+ try:
+ sys.exit(main(sys.argv))
+ except Exception, e:
+ print 'Exception %s' % str(e)
+ sys.exit(1)
+