From 84a899ad53c6ab1079ab050cded6c275789e3e86 Mon Sep 17 00:00:00 2001 From: hyokeun Date: Thu, 9 Jun 2016 10:40:01 +0900 Subject: [PATCH] Draft version Change-Id: I68e742c2965c6f39e9439a7c39b1fc476c324dd4 --- README | 20 +++ abs | 530 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 550 insertions(+) create mode 100644 README create mode 100644 abs diff --git a/README b/README new file mode 100644 index 0000000..d3aaaab --- /dev/null +++ b/README @@ -0,0 +1,20 @@ + +./abs build -w ~/workspace/phone-contact/ + + +==== build command ==== +usage: abs build [-h] [-w WORKSPACE] [-r ROOTSTRAP] [-a ARCH] [-t TYPE] + [-s SIGN] [--sdkpath SDKPATH] + +optional arguments: + -h, --help show this help message and exit + -w WORKSPACE, --workspace WORKSPACE + source directory + -r ROOTSTRAP, --rootstrap ROOTSTRAP + (ex, mobile-3.0-device.core) rootstrap name + -a ARCH, --arch ARCH (x86|arm) Architecture to build + -t TYPE, --type TYPE (tpk|wgt) Packaging type + -s SIGN, --sign SIGN (ex, ABS) Signing profile name + --sdkpath SDKPATH Specify Tizen SDK installation root (one time init). + ex) /home/yours/tizen-sdk/ + diff --git a/abs b/abs new file mode 100644 index 0000000..48d3be6 --- /dev/null +++ b/abs @@ -0,0 +1,530 @@ +#!/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) + -- 2.7.4