# -*- coding: utf-8 -*- import logging import sys import os logfmt = logging.Formatter((' backend %5d ' % os.getpid()) +'%(message)s') logger = logging.getLogger('backend') logger.setLevel(logging.INFO) logger.info('backend: %s', os.getpid()) logger.info('sys.executable = %s', sys.executable) import unohelper g_ImplementationHelper = unohelper.ImplementationHelper() def implementation(component_name, *services): def decorator(cls): g_ImplementationHelper.addImplementation(cls, component_name, services) return cls return decorator from com.sun.star.task import XJob @implementation('backend.TestRunnerJob') class TestRunnerJob(unohelper.Base, XJob): def __init__(self, context): self.context = context def execute(self, arguments): import sys args = dict((nv.Name, nv.Value) for nv in arguments) cwd = os.getcwd() working_dir = args['working_dir'] os.chdir(working_dir) try: logstream = args['logstream'] logstream = FileFromStream(logstream) loghandler = logging.StreamHandler(logstream) loghandler.setFormatter(logfmt) logger.addHandler(loghandler) try: logger.info('current dir: %s', cwd) logger.info('working dir: %s', working_dir) logger.info('sys.path:') for x in sys.path: logger.info('- %s', x) return self.run(args) finally: logger.removeHandler(loghandler) finally: os.chdir(cwd) def run(self, args): import cPickle outstream = args.get('outputstream') outstream = FileFromStream(outstream) extra_path = args.get('extra_path') if extra_path: logger.info('extra_path: %s', ' '.join(extra_path)) sys.path.extend(extra_path) logconf_path = args.get('logconf_path') if logconf_path: import logging.config logging.config.fileConfig(logconf_path) from hwp5.plat import _uno _uno.enable() pickled_testsuite = args.get('pickled_testsuite') if not pickled_testsuite: logger.error('pickled_testsuite is required') return cPickle.dumps(dict(successful=False, tests=0, failures=0, errors=0)) pickled_testsuite = str(pickled_testsuite) testsuite = cPickle.loads(pickled_testsuite) logger.info('Test Suite Unpickled') from unittest import TextTestRunner testrunner = TextTestRunner(stream=outstream) result = testrunner.run(testsuite) result = dict(successful=result.wasSuccessful(), tests=result.testsRun, failures=list(str(x) for x in result.failures), errors=list(str(x) for x in result.errors)) return cPickle.dumps(result) import contextlib @contextlib.contextmanager def sandbox(working_dir, **kwargs): import os import sys backup = dict() class NOTHING: pass if not hasattr(sys, 'argv'): sys.argv = NOTHING NAMES = ['path', 'argv', 'stdin', 'stdout', 'stderr'] for x in NAMES: assert x in kwargs backup['cwd'] = os.getcwd() os.chdir(working_dir) for x in NAMES: backup[x] = getattr(sys, x) setattr(sys, x, kwargs[x]) try: yield finally: for x in NAMES: setattr(sys, x, backup[x]) os.chdir(backup['cwd']) if sys.argv is NOTHING: del sys.argv @implementation('backend.RemoteRunJob') class RemoteRunJob(unohelper.Base, XJob): def __init__(self, context): self.context = context def execute(self, arguments): args = dict((nv.Name, nv.Value) for nv in arguments) logpath = args.get('logfile') if logpath is not None: logfile = file(logpath, 'a') loghandler = logging.StreamHandler(logfile) logger.addHandler(loghandler) import datetime logger.info('-'*10 + (' start at %s' % datetime.datetime.now()) + '-'*10) try: return self.run(args) finally: logger.info('-'*10 + (' stop at %s' % datetime.datetime.now()) + '-'*10) if logpath is not None: logger.removeHandler(loghandler) def run(self, args): import cPickle working_dir = args['working_dir'] path = cPickle.loads(str(args['path'])) argv = cPickle.loads(str(args['argv'])) stdin = FileFromStream(args['stdin']) stdout = FileFromStream(args['stdout']) stderr = FileFromStream(args['stderr']) script = argv[0] with sandbox(working_dir, path=path, argv=argv, stdin=stdin, stdout=stdout, stderr=stderr): g = dict(__name__='__main__') try: execfile(script, g) except SystemExit, e: return e.code except Exception, e: logger.exception(e) raise except: import traceback logger.error('%s' % traceback.format_exc()) raise @implementation('backend.ConsoleJob') class ConsoleJob(unohelper.Base, XJob): def __init__(self, context): self.context = context def execute(self, arguments): args = dict((nv.Name, nv.Value) for nv in arguments) cwd = os.getcwd() try: inp = args['inp'] outstream = args['outstream'] outfile = FileFromStream(outstream) import sys orig = sys.stdout, sys.stderr sys.stdout = sys.stderr = outfile try: console = Console(inp, outfile) try: console.interact('LibreOffice Python Console (pid: %s)' % os.getpid()) return 0 except SystemExit, e: return e.code finally: sys.stdout, sys.stderr = orig finally: os.chdir(cwd) from code import InteractiveConsole class Console(InteractiveConsole): def __init__(self, inp, outfile): InteractiveConsole.__init__(self) self.inp = inp self.outfile = outfile def write(self, data): self.outfile.write(data) self.outfile.flush() def raw_input(self, prompt=''): import uno arg = uno.createUnoStruct('com.sun.star.beans.NamedValue') arg.Name = 'prompt' arg.Value = prompt args = arg, result = self.inp.execute(args) if result is None: raise EOFError() return result class FileFromStream(object): ''' A file-like object based on XInputStream/XOuputStream/XSeekable :param stream: a stream object which implements com.sun.star.io.XInputStream, com.sun.star.io.XOutputStream or com.sun.star.io.XSeekable ''' def __init__(self, stream, encoding='utf-8'): import uno self.stream = stream self.encoding = encoding if hasattr(stream, 'readBytes'): def read(size=None): if size is None: data = '' while True: bytes = uno.ByteSequence('') n_read, bytes = stream.readBytes(bytes, 4096) if n_read == 0: return data data += bytes.value bytes = uno.ByteSequence('') n_read, bytes = stream.readBytes(bytes, size) return bytes.value self.read = read if hasattr(stream, 'seek'): self.tell = stream.getPosition def seek(offset, whence=0): if whence == 0: pass elif whence == 1: offset += stream.getPosition() elif whence == 2: offset += stream.getLength() stream.seek(offset) self.seek = seek if hasattr(stream, 'writeBytes'): def write(s): if isinstance(s, unicode): s = s.encode(self.encoding) stream.writeBytes(uno.ByteSequence(s)) self.write = write def flush(): stream.flush() self.flush = flush def close(self): if hasattr(self.stream, 'closeInput'): self.stream.closeInput() elif hasattr(self.stream, 'closeOutput'): self.stream.closeOutput() ''' import os.path from uno import systemPathToFileUrl from unokit.ucb import open_url from unokit.services import css path = os.path.abspath('samples/sample-5017.hwp') print path url = systemPathToFileUrl(path) print url inp = open_url(url) print inp # 여기서 segfault stg = css.embed.OLESimpleStorage(inp) print stg ''' ''' SegFault가 나는 stacktrace는 다음과 같다 StgDirEntry::StgDirEntry sot/source/sdstor/ StgEntry::Load sot/source/sdstor/ ToUpperUnicode sot/source/sdstor/stgelem.cxx CharClass unotools/source/i18n/charclass.cxx intl_createInstance unotools/source/i18n/instance.hxx 여기서 ::comphelper::getProcessServiceFactory로 얻은 XMultiServiceFactory가 null 값인 것 같다. ---- uno를 사용하는 프로그램들 (desktop app/unopkg, 각종 unittest 프로그램)은 처음 실행 시 다음과 같은 과정을 거치는 듯 하다. 1. ::cppu::defaultBootstrap_InitialComponentContext()을 호출, local context 생성 - unorc 등을 검색, application.rdb, user.rdb 등에 접근 - pyuno.so 의 getComponentContext()에서 수행: PYUNOLIBDIR 즉 pyuno.so가 있는 디렉토리에서 pyuno.rc가 있으면 그것으로 초기화) - desktop app: appinit.cxx의 CreateApplicationServiceManager()에서 수행 - unopkg: unopkg_misc.cxx의 bootstrapStandAlone()에서 수행. ucbhelper::ContentBroker도 함께 초기화한다. 2. 이렇게 생성한 local context로부터 ServiceManager를 얻어 ::comphelper::setProcessServiceFactory()를 사용하여 프로세스 전역 service factory로 등록 - uno.py와 pyuno.so는 이 작업을 하지 않는다. - desktop app: app.cxx의 ensureProcessServiceFactory()에서 수행 - unopkg: unopkg_misc.cxx의 bootstrapStandAlone()에서 함께 수행. * desktop app: desktop/source/app/ * unopkg: desktop/source/pkgchk/unopkg/ ''' logger.info('%s: end of file', __name__)