Draft version 05/73605/2
authorhyokeun <hyokeun.jeon@samsung.com>
Thu, 9 Jun 2016 01:40:01 +0000 (10:40 +0900)
committerhyokeun <hyokeun.jeon@samsung.com>
Thu, 9 Jun 2016 01:44:51 +0000 (10:44 +0900)
Change-Id: I68e742c2965c6f39e9439a7c39b1fc476c324dd4

README [new file with mode: 0644]
abs [new file with mode: 0644]

diff --git a/README b/README
new file mode 100644 (file)
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 (file)
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)
+