summaryrefslogtreecommitdiff
path: root/toolbin
diff options
context:
space:
mode:
authorMichael Vrhel <michael.vrhel@artifex.com>2020-07-02 15:38:27 -0700
committerMichael Vrhel <michael.vrhel@artifex.com>2020-07-02 15:38:27 -0700
commit20757da77706d4227cd7268113bbffbe7716a6fc (patch)
treee9e951cc620fd7a7ad820af2b285f82c744d0cff /toolbin
parentade938cc74549ffc8d58b0c1fd5e9be7f5429855 (diff)
downloadghostpdl-20757da77706d4227cd7268113bbffbe7716a6fc.tar.gz
Introduce demos folder with csharp and python API examples
Current csharp demo shows creation of wpf viewer. Goal will be to next show a Linux viewer using mono and the same API file. Python demo/API brought over from toolbin.
Diffstat (limited to 'toolbin')
-rwxr-xr-xtoolbin/gsapi.py538
-rwxr-xr-xtoolbin/gsapiwrap.py699
-rw-r--r--toolbin/jlib.py1354
3 files changed, 0 insertions, 2591 deletions
diff --git a/toolbin/gsapi.py b/toolbin/gsapi.py
deleted file mode 100755
index a2d67dbd7..000000000
--- a/toolbin/gsapi.py
+++ /dev/null
@@ -1,538 +0,0 @@
-#! /usr/bin/env python3
-
-'''
-Python version of the C API in psi/iapi.h, using ctypes.
-
-Overview:
-
- All functions have the same name as the C function that they wrap.
-
- All functions return an integer error code unless otherwise stated. [The
- exceptions are usually for out-parameters, which are returned directly as
- part of a tuple.]
-
-Usage:
-
- make sodebug
- LD_LIBRARY_PATH=sodebugbin ./toolbin/gsapi.py
-
-Requirements:
-
- Requires python3.7 or later.
-
-Limitations as of 2020-06-18:
-
- Only tested on Linux.
-
- Only very limited testing on has been done.
-
- We don't provide gsapi_add_fs() or gsapi_remove_fs().
-
-'''
-
-import collections
-import ctypes
-import sys
-
-
-
-gsapi_revision_t = collections.namedtuple('gsapi_revision_t',
- 'product copyright revision revisiondate'
- )
-
-
-def gsapi_revision():
- '''
- Returns (e, r) where <r> is a gsapi_revision_t.
- '''
- _r = _gsapi_revision_t()
- e = _libgs.gsapi_revision(ctypes.byref(_r), ctypes.sizeof(_r))
- if e:
- return e, None
- r = gsapi_revision_t(
- _r.product.decode('latin-1'),
- _r.copyright.decode('latin-1'),
- _r.revision,
- _r.revisiondate,
- )
- return e, r
-
-
-def gsapi_new_instance(caller_handle):
- '''
- Returns (e, instance).
- '''
- instance = ctypes.c_void_p()
- e = _libgs.gsapi_new_instance(ctypes.byref(instance), ctypes.c_void_p(caller_handle))
- return e, instance
-
-
-def gsapi_delete_instance(instance):
- e = _libgs.gsapi_delete_instance(instance)
- return e
-
-
-def gsapi_set_stdio(instance, stdin_fn, stdout_fn, stderr_fn):
- stdin_fn2 = _stdio_fn(stdin_fn) if stdin_fn else None
- stdout_fn2 = _stdio_fn(stdout_fn) if stdout_fn else None
- stderr_fn2 = _stdio_fn(stderr_fn) if stderr_fn else None
- e = _libgs.gsapi_set_stdio(instance, stdout_fn2, stdout_fn2, stdout_fn2)
- if not e:
- # Need to keep references to call-back functions.
- global _gsapi_set_stdio_refs
- _gsapi_set_stdio_refs = stdin_fn2, stdout_fn2, stderr_fn2
- return e
-
-
-def gsapi_set_poll(instance, poll_fn):
- poll_fn2 = _poll_fn(poll_fn)
- e = _libgs.gsapi_set_poll(instance, poll_fn2)
- if not e:
- global _gsapi_set_poll_refs
- _gsapi_set_poll_refs = poll_fn2
- return e
-
-
-display_callback = collections.namedtuple('display_callback',
- ' size'
- ' version_major'
- ' version_minor'
- ' display_open'
- ' display_preclose'
- ' display_close'
- ' display_presize'
- ' display_size'
- ' display_sync'
- ' display_page'
- ' display_update'
- ' display_memalloc'
- ' display_memfree'
- ' display_separation'
- ,
- defaults=[0]*14,
- )
-
-
-def gsapi_set_display_callback(instance, callback):
- assert isinstance(callback, display_callback)
- callback2 = _display_callback()
-
- # Copy from <callback> into <callback2>.
- for name, type_ in _display_callback._fields_:
- value = getattr(callback, name)
- value2 = type_(value)
- setattr(callback2, name, value2)
-
- e = _libgs.gsapi_set_display_callback(instance, ctypes.byref(callback2))
- if not e:
- # Ensure that we keep references to callbacks.
- global _gsapi_set_display_callback_refs
- _gsapi_set_display_callback_refs = callback2
- return e
-
-
-def gsapi_set_default_device_list(instance, list_):
- assert isinstance(list_, str)
- e = _libgs.gsapi_set_default_device_list(
- instance,
- list_.encode('latin-1'),
- len(list_),
- )
- return e
-
-
-def gsapi_get_default_device_list(instance):
- '''
- Returns (e, list) where <list> is a string.
- '''
- list_ = ctypes.POINTER(ctypes.c_char)()
- len_ = ctypes.c_int()
- e = _libgs.gsapi_get_default_device_list(
- instance,
- ctypes.byref(list_),
- ctypes.byref(len_),
- )
- if e:
- return e, ''
- return e, list_[:len_.value]
-
-
-GS_ARG_ENCODING_LOCAL = 0
-GS_ARG_ENCODING_UTF8 = 1
-GS_ARG_ENCODING_UTF16LE = 2
-
-
-def gsapi_set_arg_encoding(instance, encoding):
- e = _libgs.gsapi_set_arg_encoding(instance, encoding)
- return e
-
-
-def gsapi_init_with_args(instance, args):
- # Create copy of args in format expected by C.
- argc = len(args)
- argv = (_pchar * (argc + 1))()
- for i, arg in enumerate(args):
- enc_arg = arg.encode('utf-8')
- argv[i] = ctypes.create_string_buffer(enc_arg)
- argv[argc] = None
-
- e = _libgs.gsapi_init_with_args(instance, argc, argv)
- return e
-
-
-def gsapi_run_string_begin(instance, user_errors):
- '''
- Returns (e, exit_code).
- '''
- pexit_code = ctypes.c_int()
- e = _libgs.gsapi_run_string_begin(instance, user_errors, ctypes.byref(pexit_code))
- return e, pexit_code.value
-
-
-def gsapi_run_string_continue(instance, str_, user_errors):
- '''
- Returns (e, exit_code).
- '''
- pexit_code = ctypes.c_int()
- e = _libgs.gsapi_run_string_continue(
- instance,
- str_,
- len(str_),
- user_errors,
- ctypes.byref(pexit_code),
- )
- return e, pexit_code.value
-
-
-def gsapi_run_string_end(instance, user_errors):
- '''
- Returns (e, exit_code).
- '''
- pexit_code = ctypes.c_int()
- e = _libgs.gsapi_run_string_end(instance, user_errors, ctypes.byref(pexit_code))
- return e, pexit_code.value
-
-
-def gsapi_run_string_with_length(instance, str_, length, user_errors):
- '''
- Returns (e, exit_code).
- '''
- pexit_code = ctypes.c_int()
- e = _libgs.gsapi_run_string_with_length(
- instance,
- str_,
- length,
- user_errors,
- ctypes.byref(pexit_code),
- )
- return e, pexit_code.value
-
-
-def gsapi_run_string(instance, str_, user_errors):
- '''
- Returns (e, exit_code).
- '''
- pexit_code = ctypes.c_int()
- e = _libgs.gsapi_run_string(instance, str_, user_errors, ctypes.byref(pexit_code))
- return e, pexit_code.value
-
-
-def gsapi_run_file(instance, filename, user_errors):
- '''
- Returns (e, exit_code).
- '''
- pexit_code = ctypes.c_int()
- e = _libgs.gsapi_run_file(instance, filename, user_errors, ctypes.byref(pexit_code))
- return e, pexit_code.value
-
-
-def gsapi_exit(instance):
- e = _libgs.gsapi_exit(instance)
- return e
-
-
-gs_spt_invalid = -1
-gs_spt_null = 0 # void * is NULL.
-gs_spt_bool = 1 # void * is NULL (false) or non-NULL (true).
-gs_spt_int = 2 # void * is a pointer to an int.
-gs_spt_float = 3 # void * is a float *.
-gs_spt_name = 4 # void * is a char *.
-gs_spt_string = 5 # void * is a char *.
-gs_spt_long = 6 # void * is a long *.
-gs_spt_i64 = 7 # void * is an int64_t *.
-gs_spt_size_t = 8 # void * is a size_t *.
-
-
-def gsapi_set_param(instance, param, value):
- param2 = param.encode('latin-1')
- if 0: pass
- elif isinstance(value, bool):
- type2 = gs_spt_bool
- value2 = ctypes.byref(ctypes.c_bool(value))
- elif isinstance(value, int):
- type2 = gs_spt_i64
- value2 = ctypes.byref(ctypes.c_longlong(value))
- elif isinstance(value, float):
- type2 = gs_spt_float
- value2 = ctypes.byref(ctypes.c_float(value))
- elif isinstance(value, str):
- # We use gs_spt_string, not psapi_spt_name, because the latter doesn't
- # copy the string.
- type2 = gs_spt_string
- value2 = ctypes.c_char_p(value.encode('latin-1'))
- else:
- assert 0, 'unrecognised type: %s' % type(value)
- e = _libgs.gsapi_set_param(instance, type2, param2, value2)
- return e
-
-
-GS_PERMIT_FILE_READING = 0
-GS_PERMIT_FILE_WRITING = 1
-GS_PERMIT_FILE_CONTROL = 2
-
-
-def gsapi_add_control_path(instance, type_, path):
- e = _libgs.gsapi_add_control_path(instance, type_, path)
- return e
-
-
-def gsapi_remove_control_path(instance, type_, path):
- e = _libgs.gsapi_remove_control_path(instance, type_, path)
- return e
-
-
-def gsapi_purge_control_paths(instance, type_):
- e = _libgs.gsapi_purge_control_paths(instance, type_)
- return e
-
-
-def gsapi_activate_path_control(instance, enable):
- e = _libgs.gsapi_activate_path_control(instance, enable)
- return e
-
-
-def gsapi_is_path_control_active(instance):
- e = gsapi.gsapi_is_path_control_active(instance)
- return e
-
-
-
-# Implementation details.
-#
-
-
-_libgs = ctypes.CDLL('libgs.so')
-
-
-class _gsapi_revision_t(ctypes.Structure):
- _fields_ = [
- ('product', ctypes.c_char_p),
- ('copyright', ctypes.c_char_p),
- ('revision', ctypes.c_long),
- ('revisiondate', ctypes.c_long),
- ]
-
-
-_stdio_fn = ctypes.CFUNCTYPE(
- ctypes.c_int, # return
- ctypes.c_void_p, # caller_handle
- ctypes.POINTER(ctypes.c_char), # str
- ctypes.c_int, # len
- )
-
-_gsapi_set_stdio_refs = None
-
-
-# ctypes representation of int (*poll_fn)(void* caller_handle).
-#
-_poll_fn = ctypes.CFUNCTYPE(
- ctypes.c_int, # return
- ctypes.c_void_p, # caller_handle
- )
-
-_gsapi_set_poll_refs = None
-
-
-# ctypes representation of display_callback.
-#
-class _display_callback(ctypes.Structure):
- _fields_ = [
- ('size', ctypes.c_int),
- ('version_major', ctypes.c_int),
- ('version_minor', ctypes.c_int),
- ('display_open',
- ctypes.CFUNCTYPE(ctypes.c_int,
- ctypes.c_void_p, # handle
- ctypes.c_void_p, # device
- )),
- ('display_preclose',
- ctypes.CFUNCTYPE(ctypes.c_int,
- ctypes.c_void_p, # handle
- ctypes.c_void_p, # device
- )),
- ('display_close',
- ctypes.CFUNCTYPE(ctypes.c_int,
- ctypes.c_void_p, # handle
- ctypes.c_void_p, # device
- )),
- ('display_presize',
- ctypes.CFUNCTYPE(ctypes.c_int,
- ctypes.c_void_p, # handle
- ctypes.c_void_p, # device
- ctypes.c_int, # width
- ctypes.c_int, # height
- ctypes.c_int, # raster
- ctypes.c_uint, # format
- )),
- ('display_size',
- ctypes.CFUNCTYPE(ctypes.c_int,
- ctypes.c_void_p, # handle
- ctypes.c_void_p, # device
- ctypes.c_int, # width
- ctypes.c_int, # height
- ctypes.c_int, # raster
- ctypes.c_uint, # format
- ctypes.c_char_p, # pimage
- )),
- ('display_sync',
- ctypes.CFUNCTYPE(ctypes.c_int,
- ctypes.c_void_p, # handle
- ctypes.c_void_p, # device
- )),
- ('display_page',
- ctypes.CFUNCTYPE(ctypes.c_int,
- ctypes.c_void_p, # handle
- ctypes.c_void_p, # device
- ctypes.c_int, # copies
- ctypes.c_int, # flush
- )),
- ('display_update',
- ctypes.CFUNCTYPE(ctypes.c_int,
- ctypes.c_void_p, # handle
- ctypes.c_void_p, # device
- ctypes.c_int, # x
- ctypes.c_int, # y
- ctypes.c_int, # w
- ctypes.c_int, # h
- )),
- ('display_memalloc',
- ctypes.CFUNCTYPE(ctypes.c_int,
- ctypes.c_void_p, # handle
- ctypes.c_void_p, # device
- ctypes.c_ulong, # size
- )),
- ('display_memfree',
- ctypes.CFUNCTYPE(ctypes.c_int,
- ctypes.c_void_p, # handle
- ctypes.c_void_p, # device
- ctypes.c_void_p, # mem
- )),
- ('display_separation',
- ctypes.CFUNCTYPE(ctypes.c_int,
- ctypes.c_void_p, # handle
- ctypes.c_void_p, # device
- ctypes.c_int, # component
- ctypes.c_char_p, # component_name
- ctypes.c_ushort, # c
- ctypes.c_ushort, # m
- ctypes.c_ushort, # y
- ctypes.c_ushort, # k
- )),
- ]
-
-
-_libgs.gsapi_set_display_callback.argtypes = (
- ctypes.c_void_p, # instance
- ctypes.POINTER(_display_callback), # callback
- )
-
-
-_gsapi_set_display_callback_refs = None
-
-
-# See:
-#
-# https://stackoverflow.com/questions/58598012/ctypes-errors-with-argv
-#
-_pchar = ctypes.POINTER(ctypes.c_char)
-_ppchar = ctypes.POINTER(_pchar)
-
-_libgs.gsapi_init_with_args.argtypes = (
- ctypes.c_void_p, # instance
- ctypes.c_int, # argc
- _ppchar, # argv
- )
-
-
-if 0:
- # Not implemented yet:
- # gsapi_add_fs()
- # gsapi_remove_fs()
- #
- class gsapi_fs_t(ctypes.Structure):
- _fields_ = [
- ('open_file',
- ctypes.CFUNCTYPE(ctypes.c_int,
- ctypes.c_pvoid, # const gs_memory_t *mem
- ctypes.c_pvoid, # secret
- ctypes.c_char_p, # fname
- ctypes.c_char_p, # mode
- )),
- ]
-
-
-
-if __name__ == '__main__':
-
- # test
- #
-
- print('Running some very simple and incomplete tests...')
-
- print('libgs: %s' % _libgs)
-
- e, revision = gsapi_revision()
- print('libgs.gsapi_revision => e=%s revision=%s' % (e, revision))
- assert not e
-
- e, revision = gsapi_revision()
- print('gsapi_revision() => e=%s: %s' % (e, revision))
- assert not e
-
-
- e, instance = gsapi_new_instance(0)
- print('gsapi_new_instance => e=%s: %s' % (e, instance))
- assert not e
-
- e = gsapi_set_arg_encoding(instance, GS_ARG_ENCODING_UTF8)
- print('gsapi_set_arg_encoding => e=%s' % e)
- assert not e
-
- def stdout_fn(caller_handle, str_, len_):
- sys.stdout.write(str_[:len_].decode('latin-1').replace('\n', '\n*** '))
- return len_
- e = gsapi_set_stdio(instance, None, stdout_fn, None)
- print('gsapi_set_stdio => e=%s' % e)
- assert not e
-
- d = display_callback()
- e = gsapi_set_display_callback(instance, d)
- print('gsapi_set_display_callback => e=%s' % e)
- assert not e
-
- e = gsapi_set_default_device_list(instance, 'bmp256 bmp32b bmpgray bmpmono bmpsep1 bmpsep8 ccr cdeskjet cdj1600 cdj500')
- print('gsapi_set_default_device_list => e=%s' % e)
- assert not e
-
- e, l = gsapi_get_default_device_list(instance)
- print('gsapi_get_default_device_list => e=%s l=%s' % (e, l))
- assert not e
-
- e = gsapi_init_with_args(instance, ['gs',])
- print('gsapi_init_with_args => e=%s' % e)
- assert not e
-
- for value in 32, True, 3.14, 'hello world':
- e = gsapi_set_param(instance, "foo", value);
- print('gsapi_set_param %s => e=%s' % (value, e))
- assert not e
diff --git a/toolbin/gsapiwrap.py b/toolbin/gsapiwrap.py
deleted file mode 100755
index 0ef0bb088..000000000
--- a/toolbin/gsapiwrap.py
+++ /dev/null
@@ -1,699 +0,0 @@
-#! /usr/bin/env python3
-
-'''
-Use Swig to build wrappers for gsapi.
-
-Example usage:
-
- Note that we use mupdf's scripts/jlib.py, and assume that there is a mupdf
- checkout in the parent directory of the ghostpdl checkout - see 'import
- jlib' below.
-
- ./toolbin/gsapiwrap.py --python -l -0 -1 -t
- Build python wrapper for gsapi and run simple test.
-
- ./toolbin/gsapiwrap.py --csharp -l -0 -1 -t
- Build C# wrapper for gsapi and run simple test.
-
-Args:
-
- -c:
- Clean language-specific out-dir.
-
- -l:
- Build libgs.so (by running make).
-
- -0:
- Run swig to generate language-specific files.
-
- -1:
- Generate language wrappers by compiling/linking the files generated by
- -0.
-
- --csharp:
- Generate C# wrappers (requires Mono on Linux). Should usually be first
- param.
-
- --python
- Generate Python wrappers. Should usually be first param.
-
- --swig <swig>
- Set location of swig binary.
-
- -t
- Run simple test of language wrappers generated by -1.
-
-Status:
- As of 2020-05-22:
- Some python wrappers seem to work ok.
-
- C# wrappers are not implemented for gsapi_set_poll() and
- gsapi_set_stdio().
-'''
-
-import os
-import re
-import sys
-import textwrap
-
-import jlib
-
-
-def devpython_info():
- '''
- Use python3-config to find libpython.so and python-dev include path etc.
- '''
- python_configdir = jlib.system( 'python3-config --configdir', out='return')
- libpython_so = os.path.join(
- python_configdir.strip(),
- f'libpython{sys.version_info[0]}.{sys.version_info[1]}.so',
- )
- assert os.path.isfile( libpython_so), f'cannot find libpython_so={libpython_so}'
-
- python_includes = jlib.system( 'python3-config --includes', out='return')
- python_includes = python_includes.strip()
- return python_includes, libpython_so
-
-def swig_version( swig='swig'):
- t = jlib.system( f'{swig} -version', out='return')
- m = re.search( 'SWIG Version ([0-9]+)[.]([0-9]+)[.]([0-9]+)', t)
- assert m
- swig_major = int( m.group(1))
- return swig_major
-
-
-dir_ghostpdl = os.path.abspath( f'{__file__}/../../') + '/'
-
-
-def out_dir( language):
- if language == 'python':
- return 'gsapiwrap/python/'
- if language == 'csharp':
- return 'gsapiwrap/csharp/'
- assert 0
-
-def out_so( language):
- '''
- Returns name of .so that implements language-specific wrapper. I think
- these names have to match what the language runtime requires.
-
- For python, Swig generates a module foo.py which does 'import _foo'.
-
- Similarly C# assumes a file called 'libfoo.so'.
- '''
- if language == 'python':
- return f'{out_dir(language)}_gsapi.so'
- if language == 'csharp':
- return f'{out_dir(language)}libgsapi.so'
- assert 0
-
-def lib_gs_info():
- return f'{dir_ghostpdl}sodebugbin/libgs.so', 'make sodebug'
- return f'{dir_ghostpdl}sobin/libgs.so', 'make so'
-
-def lib_gs():
- '''
- Returns name of the gs shared-library.
- '''
- return lib_gs_info()[0]
-
-
-def swig_i( swig, language):
- '''
- Returns text for a swig .i file for psi/iapi.h.
- '''
- swig_major = swig_version( swig)
-
-
- # We need to redeclare or wrap some functions, e.g. to add OUTPUT
- # annotations. We use #define, %ignore and #undef to hide the original
- # declarations in the .h file.
- #
- fns_redeclare = (
- 'gsapi_run_file',
- 'gsapi_run_string',
- 'gsapi_run_string_begin',
- 'gsapi_run_string_continue',
- 'gsapi_run_string_end',
- 'gsapi_run_string_with_length',
- 'gsapi_set_poll',
- 'gsapi_set_poll_with_handle',
- 'gsapi_set_stdio',
- 'gsapi_set_stdio_with_handle',
- 'gsapi_new_instance',
- )
-
-
- swig_i_text = textwrap.dedent(f'''
- %module(directors="1") gsapi
-
- %include cpointer.i
- %pointer_functions(int, pint);
-
- // This seems to be necessary to make csharp handle OUTPUT args.
- //
- %include typemaps.i
-
- // For gsapi_init_with_args().
- %include argcargv.i
-
- %include cstring.i
-
- // Include type information in python doc strings. If we have
- // swig-4, we can propogate comments from the C api instead, which
- // is preferred.
- //
- {'%feature("autodoc", "3");' if swig_major < 4 else ''}
-
- %{{
- #include "psi/iapi.h"
- //#include "base/gserrors.h"
-
- // Define wrapper functions that present a modified API that
- // swig can cope with.
- //
-
- // Swig cannot handle void** out-param.
- //
- static void* new_instance( void* caller_handle, int* out)
- {{
- void* ret = NULL;
- *out = gsapi_new_instance( &ret, caller_handle);
- printf( "gsapi_new_instance() returned *out=%i ret=%p\\n", *out, ret);
- fflush( stdout);
- return ret;
- }}
-
- // Swig cannot handle (const char* str, int strlen) args.
- //
- static int run_string_continue(void *instance, const char *str, int user_errors, int *pexit_code) {{
-
- return gsapi_run_string_continue( instance, str, strlen(str), user_errors, pexit_code);
- }}
- %}}
-
- // Strip gsapi_ prefix from all generated names.
- //
- %rename("%(strip:[gsapi_])s") "";
-
- // Tell Swig about gsapi_get_default_device_list()'s out-params, so
- // it adds them to the returned object.
- //
- // I think the '(void) *$1' will ensure that swig code doesn't
- // attempt to free() the returned string.
- //
- {'%cstring_output_allocate_size(char **list, int *listlen, (void) *$1);' if language == 'python' else ''}
-
- // Tell swig about the (argc,argv) args in gsapi_init_with_args().
- //
- %apply (int ARGC, char **ARGV) {{ (int argc, char **argv) }}
-
- // Support for wrapping various functions that take function
- // pointer args. For each, we define a wrapper function that,
- // instead of having function pointer args, takes a class with
- // virtual methods. This allows swig to wrap things - python/c# etc
- // can create a derived class that implements these virtual methods
- // in the python/c# world.
- //
-
- // Wrap gsapi_set_stdio_with_handle().
- //
- %feature("director") set_stdio_class;
-
- %inline {{
- struct set_stdio_class {{
-
- virtual int stdin_fn( char* buf, int len) = 0;
- virtual int stdout_fn( const char* buf, int len) = 0;
- virtual int stderr_fn( const char* buf, int len) = 0;
-
- static int stdin_fn_wrap( void *caller_handle, char *buf, int len) {{
- return ((set_stdio_class*) caller_handle)->stdin_fn(buf, len);
- }}
- static int stdout_fn_wrap( void *caller_handle, const char *buf, int len) {{
- return ((set_stdio_class*) caller_handle)->stdout_fn(buf, len);
- }}
- static int stderr_fn_wrap( void *caller_handle, const char *buf, int len) {{
- return ((set_stdio_class*) caller_handle)->stderr_fn(buf, len);
- }}
-
- virtual ~set_stdio_class() {{}}
- }};
-
- int set_stdio_with_class( void *instance, set_stdio_class* class_) {{
- return gsapi_set_stdio_with_handle(
- instance,
- set_stdio_class::stdin_fn_wrap,
- set_stdio_class::stdout_fn_wrap,
- set_stdio_class::stderr_fn_wrap,
- (void*) class_
- );
- }}
-
-
- }}
-
- // Wrap gsapi_set_poll().
- //
- %feature("director") set_poll_class;
-
- %inline {{
- struct set_poll_class {{
- virtual int poll_fn() = 0;
-
- static int poll_fn_wrap( void* caller_handle) {{
- return ((set_poll_class*) caller_handle)->poll_fn();
- }}
-
- virtual ~set_poll_class() {{}}
- }};
-
- int set_poll_with_class( void* instance, set_poll_class* class_) {{
- return gsapi_set_poll_with_handle(
- instance,
- set_poll_class::poll_fn_wrap,
- (void*) class_
- );
- }}
-
- }}
-
- // For functions that we re-declare (typically to specify OUTPUT on
- // one or more args), use a macro to rename the declaration in the
- // header file and tell swig to ignore these renamed declarations.
- //
- ''')
-
- for fn in fns_redeclare:
- swig_i_text += f'#define {fn} {fn}0\n'
-
- for fn in fns_redeclare:
- swig_i_text += f'%ignore {fn}0;\n'
-
- swig_i_text += textwrap.dedent(f'''
- #include "psi/iapi.h"
- //#include "base/gserrors.h"
- ''')
-
- for fn in fns_redeclare:
- swig_i_text += f'#undef {fn}\n'
-
-
- swig_i_text += textwrap.dedent(f'''
- // Tell swig about our wrappers and altered declarations.
- //
-
- // Use swig's OUTPUT annotation for out-parameters.
- //
- int gsapi_run_file(void *instance, const char *file_name, int user_errors, int *OUTPUT);
- int gsapi_run_string_begin(void *instance, int user_errors, int *OUTPUT);
- int gsapi_run_string_end(void *instance, int user_errors, int *OUTPUT);
- //int gsapi_run_string_with_length(void *instance, const char *str, unsigned int length, int user_errors, int *OUTPUT);
- int gsapi_run_string(void *instance, const char *str, int user_errors, int *OUTPUT);
-
- // Declare functions defined above that we want swig to wrap. These
- // don't have the gsapi_ prefix, so that they can internally call
- // the wrapped gsapi_*() function. [We've told swig to strip the
- // gsapi_ prefix on generated functions anyway, so this doesn't
- // afffect the generated names.]
- //
- static int run_string_continue(void *instance, const char *str, int user_errors, int *OUTPUT);
- static void* new_instance(void* caller_handle, int* OUTPUT);
- ''')
-
- if language == 'python':
- swig_i_text += textwrap.dedent(f'''
-
- // Define python code that is needed to handle functions with
- // function-pointer args.
- //
- %pythoncode %{{
-
- set_stdio_g = None
- def set_stdio( instance, stdin, stdout, stderr):
- class derived( set_stdio_class):
- def stdin_fn( self):
- return stdin()
- def stdout_fn( self, text, len):
- return stdout( text, len)
- def stderr_fn( self, text, len):
- return stderr( text)
-
- global set_stdio_g
- set_stdio_g = derived()
- return set_stdio_with_class( instance, set_stdio_g)
-
- set_poll_g = None
- def set_poll( instance, fn):
- class derived( set_poll_class):
- def poll_fn( self):
- return fn()
- global set_poll_g
- set_poll_g = derived()
- return set_poll_with_class( instance, set_poll_g)
- %}}
- ''')
-
- return swig_i_text
-
-
-
-def run_swig( swig, language):
- '''
- Runs swig using a generated .i file.
-
- The .i file modifies the gsapi API in places to allow specification of
- out-parameters that swig understands - e.g. void** OUTPUT doesn't work.
- '''
- os.makedirs( out_dir(language), exist_ok=True)
- swig_major = swig_version( swig)
-
- swig_i_text = swig_i( swig, language)
- swig_i_filename = f'{out_dir(language)}iapi.i'
- jlib.update_file( swig_i_text, swig_i_filename)
-
- out_cpp = f'{out_dir(language)}gsapi.cpp'
-
- if language == 'python':
- out_lang = f'{out_dir(language)}gsapi.py'
- elif language == 'csharp':
- out_lang = f'{out_dir(language)}gsapi.cs'
- else:
- assert 0
-
- out_files = (out_cpp, out_lang)
-
- doxygen_arg = ''
- if swig_major >= 4 and language == 'python':
- doxygen_arg = '-doxygen'
-
- extra = ''
- if language == 'csharp':
- # Tell swig to put all generated csharp code into a single file.
- extra = f'-outfile gsapi.cs'
-
- command = (textwrap.dedent(f'''
- {swig}
- -Wall
- -c++
- -{language}
- {doxygen_arg}
- -module gsapi
- -outdir {out_dir(language)}
- -o {out_cpp}
- {extra}
- -includeall
- -I{dir_ghostpdl}
- -ignoremissing
- {swig_i_filename}
- ''').strip().replace( '\n', ' \\\n')
- )
-
- jlib.build(
- (swig_i_filename,),
- out_files,
- command,
- prefix=' ',
- )
-
-
-def main( argv):
-
- swig = 'swig'
- language = 'python'
-
- args = jlib.Args( sys.argv[1:])
- while 1:
- try:
- arg = args.next()
- except StopIteration:
- break
-
- if 0:
- pass
-
- elif arg == '-c':
- jlib.system( f'rm {out_dir(language)}* || true', verbose=1, prefix=' ')
-
- elif arg == '-l':
- command = lib_gs_info()[1]
- jlib.system( command, verbose=1, prefix=' ')
-
- elif arg == '-0':
- run_swig( swig, language)
-
- elif arg == '-1':
-
- libs = [lib_gs()]
- includes = [dir_ghostpdl]
- file_cpp = f'{out_dir(language)}gsapi.cpp'
-
- if language == 'python':
- python_includes, libpython_so = devpython_info()
- libs.append( libpython_so)
- includes.append( python_includes)
-
- includes_text = ''
- for i in includes:
- includes_text += f' -I{i}'
- command = textwrap.dedent(f'''
- g++
- -g
- -Wall -W
- -o {out_so(language)}
- -fPIC
- -shared
- {includes_text}
- {jlib.link_l_flags(libs)}
- {file_cpp}
- ''').strip().replace( '\n', ' \\\n')
- jlib.build(
- (file_cpp, lib_gs(), 'psi/iapi.h'),
- (out_so(language),),
- command,
- prefix=' ',
- )
-
- elif arg == '--csharp':
- language = 'csharp'
-
- elif arg == '--python':
- language = 'python'
-
- elif arg == '--swig':
- swig = args.next()
-
- elif arg == '-t':
-
- if language == 'python':
- text = textwrap.dedent('''
- #!/usr/bin/env python3
-
- import os
- import sys
-
- import gsapi
-
- gsapi.gs_error_Quit = -101
-
- def main():
- minst, code = gsapi.new_instance(None)
- print( f'minst={minst} code={code}')
-
- if 1:
- def stdin_local(len):
- # Not sure whether this is right.
- return sys.stdin.read(len)
- def stdout_local(text, l):
- sys.stdout.write(text[:l])
- return l
- def stderr_local(text, l):
- sys.stderr.write(text[:l])
- return l
- gsapi.set_stdio( minst, None, stdout_local, stderr_local);
-
- if 1:
- def poll_fn():
- return 0
- gsapi.set_poll(minst, poll_fn)
- if 1:
- s = 'display x11alpha x11 bbox'
- gsapi.set_default_device_list( minst, s, len(s))
-
- e, text = gsapi.get_default_device_list( minst)
- print( f'gsapi.get_default_device_list() returned e={e} text={text!r}')
-
- out = 'out.pdf'
- if os.path.exists( out):
- os.remove( out)
- assert not os.path.exists( out)
-
- gsargv = ['']
- gsargv += f'-dNOPAUSE -dBATCH -dSAFER -sDEVICE=pdfwrite -sOutputFile={out} contrib/pcl3/ps/levels-test.ps'.split()
- print( f'gsargv={gsargv}')
- code = gsapi.set_arg_encoding(minst, gsapi.GS_ARG_ENCODING_UTF8)
- if code == 0:
- code = gsapi.init_with_args(minst, gsargv)
-
- code, exit_code = gsapi.run_string_begin( minst, 0)
- print( f'gsapi.run_string_begin() returned code={code} exit_code={exit_code}')
- assert code == 0
- assert exit_code == 0
-
- gsapi.run_string
-
- code1 = gsapi.exit(minst)
- if (code == 0 or code == gsapi.gs_error_Quit):
- code = code1
- gsapi.delete_instance(minst)
- assert os.path.isfile( out)
- if code == 0 or code == gsapi.gs_error_Quit:
- return 0
- return 1
-
- if __name__ == '__main__':
- code = main()
- assert code == 0
- sys.exit( code)
- ''')
- text = text[1:] # skip leading \n.
- test_py = f'{out_dir(language)}test.py'
- jlib.update_file( text, test_py)
- os.chmod( test_py, 0o744)
-
- jlib.system(
- f'LD_LIBRARY_PATH={os.path.abspath( f"{lib_gs()}/..")}'
- f' PYTHONPATH={out_dir(language)}'
- f' {test_py}'
- ,
- verbose = 1,
- prefix=' ',
- )
-
- elif language == 'csharp':
- # See: https://github.com/swig/swig/blob/master/Lib/csharp/typemaps.i
- #
- text = textwrap.dedent('''
- using System;
- public class runme {
- static void Main() {
- int code;
- SWIGTYPE_p_void instance;
- Console.WriteLine("hello world");
- instance = gsapi.new_instance(null, out code);
- Console.WriteLine("code is: " + code);
- gsapi.add_control_path(instance, 0, "hello");
- }
- }
- ''')
- test_cs = f'{out_dir(language)}test.cs'
- jlib.update_file( text, test_cs)
- files_in = f'{out_dir(language)}gsapi.cs', test_cs
- file_out = f'{out_dir(language)}test.exe'
- command = f'mono-csc -debug+ -out:{file_out} {" ".join(files_in)}'
- jlib.build( files_in, (file_out,), command, prefix=' ')
-
- ld_library_path = f'{dir_ghostpdl}sobin'
- jlib.system( f'LD_LIBRARY_PATH={ld_library_path} {file_out}', verbose=jlib.log, prefix=' ')
-
- elif arg == '--tt':
- # small swig test case.
- os.makedirs( 'swig-tt', exist_ok=True)
- i = textwrap.dedent(f'''
- %include cpointer.i
- %include cstring.i
- %feature("autodoc", "3");
- %cstring_output_allocate_size(char **list, int *listlen, (void) *$1);
- %inline {{
- static inline int gsapi_get_default_device_list(void *instance, char **list, int *listlen)
- {{
- *list = (char*) "hello world";
- *listlen = 6;
- return 0;
- }}
- }}
- ''')
- jlib.update_file(i, 'swig-tt/tt.i')
- jlib.system('swig -c++ -python -module tt -outdir swig-tt -o swig-tt/tt.cpp swig-tt/tt.i', verbose=1)
- p = textwrap.dedent(f'''
- #!/usr/bin/env python3
- import tt
- print( tt.gsapi_get_default_device_list(None))
- ''')[1:]
- jlib.update_file( p, 'swig-tt/test.py')
- python_includes, python_so = devpython_info()
- includes = f'-I {python_includes}'
- link_flags = jlib.link_l_flags( [python_so])
- jlib.system( f'g++ -shared -fPIC {includes} {link_flags} -o swig-tt/_tt.so swig-tt/tt.cpp', verbose=1)
- jlib.system( f'cd swig-tt; python3 test.py', verbose=1)
-
- elif arg == '-T':
- # Very simple test that we can create c# wrapper for trivial code.
- os.makedirs( 'swig-cs-test', exist_ok=True)
- example_cpp = textwrap.dedent('''
- #include <time.h>
- double My_variable = 3.0;
-
- int fact(int n) {
- if (n <= 1) return 1;
- else return n*fact(n-1);
- }
-
- int my_mod(int x, int y) {
- return (x%y);
- }
-
- char *get_time()
- {
- time_t ltime;
- time(&ltime);
- return ctime(&ltime);
- }
- ''')
- jlib.update_file( example_cpp, 'swig-cs-test/example.cpp')
-
- example_i = textwrap.dedent('''
- %module example
- %{
- /* Put header files here or function declarations like below */
- extern double My_variable;
- extern int fact(int n);
- extern int my_mod(int x, int y);
- extern char *get_time();
- %}
-
- extern double My_variable;
- extern int fact(int n);
- extern int my_mod(int x, int y);
- extern char *get_time();
- ''')
- jlib.update_file( example_i, 'swig-cs-test/example.i')
-
- runme_cs = textwrap.dedent('''
- using System;
- public class runme {
- static void Main() {
- Console.WriteLine(example.My_variable);
- Console.WriteLine(example.fact(5));
- Console.WriteLine(example.get_time());
- }
- }
- ''')
- jlib.update_file( runme_cs, 'swig-cs-test/runme.cs')
- jlib.system( 'g++ -g -fPIC -shared -o swig-cs-test/libfoo.so swig-cs-test/example.cpp', verbose=1)
- jlib.system( 'swig -c++ -csharp -module example -outdir swig-cs-test -o swig-cs-test/example_wrap.cpp -outfile example.cs swig-cs-test/example.i', verbose=1)
- jlib.system( 'g++ -g -fPIC -shared -L swig-cs-test -l foo swig-cs-test/example_wrap.cpp -o swig-cs-test/libexample.so', verbose=1)
- jlib.system( 'cd swig-cs-test; mono-csc -out:runme.exe example.cs runme.cs', verbose=1)
- jlib.system( 'cd swig-cs-test; LD_LIBRARY_PATH=`pwd` ./runme.exe', verbose=1)
- jlib.system( 'ls -l swig-cs-test', verbose=1)
-
-
- else:
- raise Exception( f'unrecognised arg: {arg}')
-
-if __name__ == '__main__':
- try:
- main( sys.argv)
- except Exception as e:
- jlib.exception_info( out=sys.stdout)
- sys.exit(1)
diff --git a/toolbin/jlib.py b/toolbin/jlib.py
deleted file mode 100644
index 54ee0a222..000000000
--- a/toolbin/jlib.py
+++ /dev/null
@@ -1,1354 +0,0 @@
-from __future__ import print_function
-
-import codecs
-import inspect
-import io
-import os
-import shutil
-import subprocess
-import sys
-import time
-import traceback
-
-
-def place( frame_record):
- '''
- Useful debugging function - returns representation of source position of
- caller.
- '''
- filename = frame_record.filename
- line = frame_record.lineno
- function = frame_record.function
- ret = os.path.split( filename)[1] + ':' + str( line) + ':' + function + ':'
- if 0:
- tid = str( threading.currentThread())
- ret = '[' + tid + '] ' + ret
- return ret
-
-
-def expand_nv( text, caller):
- '''
- Returns <text> with special handling of {<expression>} items.
-
- text:
- String containing {<expression>} items.
- caller:
- If an int, the number of frames to step up when looking for file:line
- information or evaluating expressions.
-
- Otherwise should be a frame record as returned by inspect.stack()[].
-
- <expression> is evaluated in <caller>'s context using eval(), and expanded
- to <expression> or <expression>=<value>.
-
- If <expression> ends with '=', this character is removed and we prefix the
- result with <expression>=.
-
- E.g.:
- x = 45
- y = 'hello'
- expand_nv( 'foo {x} {y=}')
- returns:
- foo 45 y=hello
-
- <expression> can also use ':' and '!' to control formatting, like
- str.format().
- '''
- if isinstance( caller, int):
- frame_record = inspect.stack()[ caller]
- else:
- frame_record = caller
- frame = frame_record.frame
- try:
- def get_items():
- '''
- Yields (pre, item), where <item> is contents of next {...} or None,
- and <pre> is preceding text.
- '''
- pos = 0
- pre = ''
- while 1:
- if pos == len( text):
- yield pre, None
- break
- rest = text[ pos:]
- if rest.startswith( '{{') or rest.startswith( '}}'):
- pre += rest[0]
- pos += 2
- elif text[ pos] == '{':
- close = text.find( '}', pos)
- if close < 0:
- raise Exception( 'After "{" at offset %s, cannot find closing "}". text is: %r' % (
- pos, text))
- yield pre, text[ pos+1 : close]
- pre = ''
- pos = close + 1
- else:
- pre += text[ pos]
- pos += 1
-
- ret = ''
- for pre, item in get_items():
- ret += pre
- nv = False
- if item:
- if item.endswith( '='):
- nv = True
- item = item[:-1]
- expression, tail = split_first_of( item, '!:')
- try:
- value = eval( expression, frame.f_globals, frame.f_locals)
- value_text = ('{0%s}' % tail).format( value)
- except Exception as e:
- value_text = '{??Failed to evaluate %r in context %s:%s because: %s??}' % (
- expression,
- frame_record.filename,
- frame_record.lineno,
- e,
- )
- if nv:
- ret += '%s=' % expression
- ret += value_text
-
- return ret
-
- finally:
- del frame
-
-
-class LogPrefixTime:
- def __init__( self, date=False, time_=True, elapsed=False):
- self.date = date
- self.time = time_
- self.elapsed = elapsed
- self.t0 = time.time()
- def __call__( self):
- ret = ''
- if self.date:
- ret += time.strftime( ' %F')
- if self.time:
- ret += time.strftime( ' %T')
- if self.elapsed:
- ret += ' (+%s)' % time_duration( time.time() - self.t0, s_format='%.1f')
- if ret:
- ret = ret.strip() + ': '
- return ret
-
-class LogPrefixFileLine:
- def __call__( self, caller):
- if isinstance( caller, int):
- caller = inspect.stack()[ caller]
- return place( caller) + ' '
-
-class LogPrefixScopes:
- '''
- Internal use only.
- '''
- def __init__( self):
- self.items = []
- def __call__( self):
- ret = ''
- for item in self.items:
- if callable( item):
- item = item()
- ret += item
- return ret
-
-
-class LogPrefixScope:
- '''
- Can be used to insert scoped prefix to log output.
- '''
- def __init__( self, prefix):
- g_log_prefixe_scopes.items.append( prefix)
- def __enter__( self):
- pass
- def __exit__( self, exc_type, exc_value, traceback):
- global g_log_prefix
- g_log_prefixe_scopes.items.pop()
-
-
-g_log_delta = 0
-
-class LogDeltaScope:
- '''
- Can be used to temporarily change verbose level of logging.
-
- E.g to temporarily increase logging:
-
- with jlib.LogDeltaScope(-1):
- ...
- '''
- def __init__( self, delta):
- self.delta = delta
- global g_log_delta
- g_log_delta += self.delta
- def __enter__( self):
- pass
- def __exit__( self, exc_type, exc_value, traceback):
- global g_log_delta
- g_log_delta -= self.delta
-
-# Special item that can be inserted into <g_log_prefixes> to enable
-# temporary addition of text into log prefixes.
-#
-g_log_prefixe_scopes = LogPrefixScopes()
-
-# List of items that form prefix for all output from log().
-#
-g_log_prefixes = []
-
-
-def log_text( text=None, caller=1, nv=True):
- '''
- Returns log text, prepending all lines with text from g_log_prefixes.
-
- text:
- The text to output. Each line is prepended with prefix text.
- caller:
- If an int, the number of frames to step up when looking for file:line
- information or evaluating expressions.
-
- Otherwise should be a frame record as returned by inspect.stack()[].
- nv:
- If true, we expand {...} in <text> using expand_nv().
- '''
- if isinstance( caller, int):
- caller += 1
- prefix = ''
- for p in g_log_prefixes:
- if callable( p):
- if isinstance( p, LogPrefixFileLine):
- p = p(caller)
- else:
- p = p()
- prefix += p
-
- if text is None:
- return prefix
-
- if nv:
- text = expand_nv( text, caller)
-
- if text.endswith( '\n'):
- text = text[:-1]
- lines = text.split( '\n')
-
- text = ''
- for line in lines:
- text += prefix + line + '\n'
- return text
-
-
-
-s_log_levels_cache = dict()
-s_log_levels_items = []
-
-def log_levels_find( caller):
- if not s_log_levels_items:
- return 0
-
- tb = traceback.extract_stack( None, 1+caller)
- if len(tb) == 0:
- return 0
- filename, line, function, text = tb[0]
-
- key = function, filename, line,
- delta = s_log_levels_cache.get( key)
-
- if delta is None:
- # Calculate and populate cache.
- delta = 0
- for item_function, item_filename, item_delta in s_log_levels_items:
- if item_function and not function.startswith( item_function):
- continue
- if item_filename and not filename.startswith( item_filename):
- continue
- delta = item_delta
- break
-
- s_log_levels_cache[ key] = delta
-
- return delta
-
-
-def log_levels_add( delta, filename_prefix, function_prefix):
- '''
- log() calls from locations with filenames starting with <filename_prefix>
- and/or function names starting with <function_prefix> will have <delta>
- added to their level.
-
- Use -ve delta to increase verbosity from particular filename or function
- prefixes.
- '''
- log( 'adding level: {filename_prefix=!r} {function_prefix=!r}')
-
- # Sort in reverse order so that long functions and filename specs come
- # first.
- #
- s_log_levels_items.append( (function_prefix, filename_prefix, delta))
- s_log_levels_items.sort( reverse=True)
-
-
-def log( text, level=0, caller=1, nv=True, out=None):
- '''
- Writes log text, with special handling of {<expression>} items in <text>
- similar to python3's f-strings.
-
- text:
- The text to output.
- caller:
- How many frames to step up to get caller's context when evaluating
- file:line information and/or expressions. Or frame record as returned
- by inspect.stack()[].
- nv:
- If true, we expand {...} in <text> using expand_nv().
- out:
- Where to send output. If None we use sys.stdout.
-
- <expression> is evaluated in our caller's context (<n> stack frames up)
- using eval(), and expanded to <expression> or <expression>=<value>.
-
- If <expression> ends with '=', this character is removed and we prefix the
- result with <expression>=.
-
- E.g.:
- x = 45
- y = 'hello'
- expand_nv( 'foo {x} {y=}')
- returns:
- foo 45 y=hello
-
- <expression> can also use ':' and '!' to control formatting, like
- str.format().
- '''
- if out is None:
- out = sys.stdout
- level += g_log_delta
- if isinstance( caller, int):
- caller += 1
- level += log_levels_find( caller)
- if level <= 0:
- text = log_text( text, caller, nv=nv)
- out.write( text)
- out.flush()
-
-
-def log0( text, caller=1, nv=True, out=None):
- '''
- Most verbose log. Same as log().
- '''
- log( text, level=0, caller=caller+1, nv=nv, out=out)
-
-def log1( text, caller=1, nv=True, out=None):
- log( text, level=1, caller=caller+1, nv=nv, out=out)
-
-def log2( text, caller=1, nv=True, out=None):
- log( text, level=2, caller=caller+1, nv=nv, out=out)
-
-def log3( text, caller=1, nv=True, out=None):
- log( text, level=3, caller=caller+1, nv=nv, out=out)
-
-def log4( text, caller=1, nv=True, out=None):
- log( text, level=4, caller=caller+1, nv=nv, out=out)
-
-def log5( text, caller=1, nv=True, out=None):
- '''
- Least verbose log.
- '''
- log( text, level=5, caller=caller+1, nv=nv, out=out)
-
-def logx( text, caller=1, nv=True, out=None):
- '''
- Does nothing, useful when commenting out a log().
- '''
- pass
-
-
-def log_levels_add_env( name='JLIB_log_levels'):
- '''
- Added log levels encoded in an environmental variable.
- '''
- t = os.environ.get( name)
- if t:
- for ffll in t.split( ','):
- ffl, delta = ffll.split( '=', 1)
- delta = int( delta)
- ffl = ffl.split( ':')
- if 0:
- pass
- elif len( ffl) == 1:
- filename = ffl
- function = None
- elif len( ffl) == 2:
- filename, function = ffl
- else:
- assert 0
- log_levels_add( delta, filename, function)
-
-
-def strpbrk( text, substrings):
- '''
- Finds first occurrence of any item in <substrings> in <text>.
-
- Returns (pos, substring) or (len(text), None) if not found.
- '''
- ret_pos = len( text)
- ret_substring = None
- for substring in substrings:
- pos = text.find( substring)
- if pos >= 0 and pos < ret_pos:
- ret_pos = pos
- ret_substring = substring
- return ret_pos, ret_substring
-
-
-def split_first_of( text, substrings):
- '''
- Returns (pre, post), where <pre> doesn't contain any item in <substrings>
- and <post> is empty or starts with an item in <substrings>.
- '''
- pos, _ = strpbrk( text, substrings)
- return text[ :pos], text[ pos:]
-
-
-
-log_levels_add_env()
-
-
-def force_line_buffering():
- '''
- Ensure sys.stdout and sys.stderr are line-buffered. E.g. makes things work
- better if output is piped to a file via 'tee'.
-
- Returns original out,err streams.
- '''
- stdout0 = sys.stdout
- stderr0 = sys.stderr
- sys.stdout = os.fdopen( os.dup( sys.stdout.fileno()), 'w', 1)
- sys.stderr = os.fdopen( os.dup( sys.stderr.fileno()), 'w', 1)
- return stdout0, stderr0
-
-
-def exception_info( exception=None, limit=None, out=None, prefix='', oneline=False):
- '''
- General replacement for traceback.* functions that print/return information
- about exceptions. This function provides a simple way of getting the
- functionality provided by these traceback functions:
-
- traceback.format_exc()
- traceback.format_exception()
- traceback.print_exc()
- traceback.print_exception()
-
- Returns:
- A string containing description of specified exception and backtrace.
-
- Inclusion of outer frames:
- We improve upon traceback.* in that we also include stack frames above
- the point at which an exception was caught - frames from the top-level
- <module> or thread creation fn to the try..catch block, which makes
- backtraces much more useful.
-
- Google 'sys.exc_info backtrace incomplete' for more details.
-
- We deliberately leave a slightly curious pair of items in the backtrace
- - the point in the try: block that ended up raising an exception, and
- the point in the associated except: block from which we were called.
-
- For clarity, we insert an empty frame in-between these two items, so
- that one can easily distinguish the two parts of the backtrace.
-
- So the backtrace looks like this:
-
- root (e.g. <module> or /usr/lib/python2.7/threading.py:778:__bootstrap():
- ...
- file:line in the except: block where the exception was caught.
- ::(): marker
- file:line in the try: block.
- ...
- file:line where the exception was raised.
-
- The items after the ::(): marker are the usual items that traceback.*
- shows for an exception.
-
- Also the backtraces that are generated are more concise than those provided
- by traceback.* - just one line per frame instead of two - and filenames are
- output relative to the current directory if applicatble. And one can easily
- prefix all lines with a specified string, e.g. to indent the text.
-
- Returns a string containing backtrace and exception information, and sends
- returned string to <out> if specified.
-
- exception:
- None, or a (type, value, traceback) tuple, e.g. from sys.exc_info(). If
- None, we call sys.exc_info() and use its return value.
- limit:
- None or maximum number of stackframes to output.
- out:
- None or callable taking single <text> parameter or object with a
- 'write' member that takes a single <text> parameter.
- prefix:
- Used to prefix all lines of text.
- '''
- if exception is None:
- exception = sys.exc_info()
- etype, value, tb = exception
-
- if sys.version_info[0] == 2:
- out2 = io.BytesIO()
- else:
- out2 = io.StringIO()
- try:
-
- frames = []
-
- # Get frames above point at which exception was caught - frames
- # starting at top-level <module> or thread creation fn, and ending
- # at the point in the catch: block from which we were called.
- #
- # These frames are not included explicitly in sys.exc_info()[2] and are
- # also omitted by traceback.* functions, which makes for incomplete
- # backtraces that miss much useful information.
- #
- for f in reversed(inspect.getouterframes(tb.tb_frame)):
- ff = f[1], f[2], f[3], f[4][0].strip()
- frames.append(ff)
-
- if 1:
- # It's useful to see boundary between upper and lower frames.
- frames.append( None)
-
- # Append frames from point in the try: block that caused the exception
- # to be raised, to the point at which the exception was thrown.
- #
- # [One can get similar information using traceback.extract_tb(tb):
- # for f in traceback.extract_tb(tb):
- # frames.append(f)
- # ]
- for f in inspect.getinnerframes(tb):
- ff = f[1], f[2], f[3], f[4][0].strip()
- frames.append(ff)
-
- cwd = os.getcwd() + os.sep
- if oneline:
- if etype and value:
- # The 'exception_text' variable below will usually be assigned
- # something like '<ExceptionType>: <ExceptionValue>', unless
- # there was no explanatory text provided (e.g. "raise Exception()").
- # In this case, str(value) will evaluate to ''.
- exception_text = traceback.format_exception_only(etype, value)[0].strip()
- filename, line, fnname, text = frames[-1]
- if filename.startswith(cwd):
- filename = filename[len(cwd):]
- if not str(value):
- # The exception doesn't have any useful explanatory text
- # (for example, maybe it was raised by an expression like
- # "assert <expression>" without a subsequent comma). In
- # the absence of anything more helpful, print the code that
- # raised the exception.
- exception_text += ' (%s)' % text
- line = '%s%s at %s:%s:%s()' % (prefix, exception_text, filename, line, fnname)
- out2.write(line)
- else:
- out2.write( '%sBacktrace:\n' % prefix)
- for frame in frames:
- if frame is None:
- out2.write( '%s ^except raise:\n' % prefix)
- continue
- filename, line, fnname, text = frame
- if filename.startswith( cwd):
- filename = filename[ len(cwd):]
- if filename.startswith( './'):
- filename = filename[ 2:]
- out2.write( '%s %s:%s:%s(): %s\n' % (
- prefix, filename, line, fnname, text))
-
- if etype and value:
- out2.write( '%sException:\n' % prefix)
- lines = traceback.format_exception_only( etype, value)
- for line in lines:
- out2.write( '%s %s' % ( prefix, line))
-
- text = out2.getvalue()
-
- # Write text to <out> if specified.
- out = getattr( out, 'write', out)
- if callable( out):
- out( text)
- return text
-
- finally:
- # clear things to avoid cycles.
- exception = None
- etype = None
- value = None
- tb = None
- frames = None
-
-
-def number_sep( s):
- '''
- Simple number formatter, adds commas in-between thousands. <s> can
- be a number or a string. Returns a string.
- '''
- if not isinstance( s, str):
- s = str( s)
- c = s.find( '.')
- if c==-1: c = len(s)
- end = s.find('e')
- if end == -1: end = s.find('E')
- if end == -1: end = len(s)
- ret = ''
- for i in range( end):
- ret += s[i]
- if i<c-1 and (c-i-1)%3==0:
- ret += ','
- elif i>c and i<end-1 and (i-c)%3==0:
- ret += ','
- ret += s[end:]
- return ret
-
-assert number_sep(1)=='1'
-assert number_sep(12)=='12'
-assert number_sep(123)=='123'
-assert number_sep(1234)=='1,234'
-assert number_sep(12345)=='12,345'
-assert number_sep(123456)=='123,456'
-assert number_sep(1234567)=='1,234,567'
-
-
-class Stream:
- '''
- Base layering abstraction for streams - abstraction for things like
- sys.stdout to allow prefixing of all output, e.g. with a timestamp.
- '''
- def __init__( self, stream):
- self.stream = stream
- def write( self, text):
- self.stream.write( text)
-
-class StreamPrefix:
- '''
- Prefixes output with a prefix, which can be a string or a callable that
- takes no parameters and return a string.
- '''
- def __init__( self, stream, prefix):
- self.stream = stream
- self.at_start = True
- if callable(prefix):
- self.prefix = prefix
- else:
- self.prefix = lambda : prefix
-
- def write( self, text):
- if self.at_start:
- text = self.prefix() + text
- self.at_start = False
- append_newline = False
- if text.endswith( '\n'):
- text = text[:-1]
- self.at_start = True
- append_newline = True
- text = text.replace( '\n', '\n%s' % self.prefix())
- if append_newline:
- text += '\n'
- self.stream.write( text)
-
- def flush( self):
- self.stream.flush()
-
-
-def debug( text):
- if callable(text):
- text = text()
- print( text)
-
-debug_periodic_t0 = [0]
-def debug_periodic( text, override=0):
- interval = 10
- t = time.time()
- if t - debug_periodic_t0[0] > interval or override:
- debug_periodic_t0[0] = t
- debug(text)
-
-
-def time_duration( seconds, verbose=False, s_format='%i'):
- '''
- Returns string expressing an interval.
-
- seconds:
- The duration in seconds
- verbose:
- If true, return like '4 days 1 hour 2 mins 23 secs', otherwise as
- '4d3h2m23s'.
- s_format:
- If specified, use as printf-style format string for seconds.
- '''
- x = abs(seconds)
- ret = ''
- i = 0
- for div, text in [
- ( 60, 'sec'),
- ( 60, 'min'),
- ( 24, 'hour'),
- ( None, 'day'),
- ]:
- force = ( x == 0 and i == 0)
- if div:
- remainder = x % div
- x = int( x/div)
- else:
- remainder = x
- if not verbose:
- text = text[0]
- if remainder or force:
- if verbose and remainder > 1:
- # plural.
- text += 's'
- if verbose:
- text = ' %s ' % text
- if i == 0:
- remainder = s_format % remainder
- ret = '%s%s%s' % ( remainder, text, ret)
- i += 1
- ret = ret.strip()
- if ret == '':
- ret = '0s'
- if seconds < 0:
- ret = '-%s' % ret
- return ret
-
-assert time_duration( 303333) == '3d12h15m33s'
-assert time_duration( 303333.33, s_format='%.1f') == '3d12h15m33.3s'
-assert time_duration( 303333, verbose=True) == '3 days 12 hours 15 mins 33 secs'
-assert time_duration( 303333.33, verbose=True, s_format='%.1f') == '3 days 12 hours 15 mins 33.3 secs'
-
-assert time_duration( 0) == '0s'
-assert time_duration( 0, verbose=True) == '0 sec'
-
-
-def date_time( t=None):
- if t is None:
- t = time.time()
- return time.strftime( "%F-%T", time.gmtime( t))
-
-def stream_prefix_time( stream):
- '''
- Returns StreamPrefix that prefixes lines with time and elapsed time.
- '''
- t_start = time.time()
- def prefix_time():
- return '%s (+%s): ' % (
- time.strftime( '%T'),
- time_duration( time.time() - t_start, s_format='0.1f'),
- )
- return StreamPrefix( stream, prefix_time)
-
-def stdout_prefix_time():
- '''
- Changes sys.stdout to prefix time and elapsed time; returns original
- sys.stdout.
- '''
- ret = sys.stdout
- sys.stdout = stream_prefix_time( sys.stdout)
- return ret
-
-
-def make_stream( out):
- '''
- If <out> already has a .write() member, returns <out>.
-
- Otherwise a stream-like object with a .write() method that writes to <out>.
-
- out:
- Where output is sent.
- If None, output is lost.
- Otherwise if an integer, we do: os.write( out, text)
- Otherwise if callable, we do: out( text)
- Otherwise we assume <out> is python stream or similar already.
- '''
- if getattr( out, 'write', None):
- return out
- class Ret:
- def flush():
- pass
- ret = Ret()
- if out is None:
- ret.write = lambda text: None
- elif isinstance( out, int):
- ret.write = lambda text: os.write( out, text)
- elif callable( out):
- ret.write = out
- else:
- ret.write = lambda text: out.write( text)
- return ret
-
-
-def system_raw(
- command,
- out=None,
- shell=True,
- encoding='latin_1',
- errors='strict',
- buffer_len=-1,
- ):
- '''
- Runs command, writing output to <out> which can be an int fd, a python
- stream or a Stream object.
-
- Args:
- command:
- The command to run.
- out:
- Where output is sent.
- If None, output is lost.
- If -1, output is sent to stdout and stderr.
- Otherwise if an integer, we do: os.write( out, text)
- Otherwise if callable, we do: out( text)
- Otherwise we assume <out> is python stream or similar, and do: out.write(text)
- shell:
- Whether to run command inside a shell (see subprocess.Popen).
- encoding:
- Sepecify the encoding used to translate the command's output
- to characters.
-
- Note that if <encoding> is None and we are being run by python3,
- <out> will be passed bytes, not a string.
-
- Note that latin_1 will never raise a UnicodeDecodeError.
- errors:
- How to handle encoding errors; see docs for codecs module for
- details.
- buffer_len:
- The number of bytes we attempt to read at a time. If -1 we read
- output one line at a time.
-
- Returns:
- subprocess's <returncode>, i.e. -N means killed by signal N, otherwise
- the exit value (e.g. 12 if command terminated with exit(12)).
- '''
- if out == -1:
- stdin = 0
- stdout = 1
- stderr = 2
- else:
- stdin = None
- stdout = subprocess.PIPE
- stderr = subprocess.STDOUT
- child = subprocess.Popen(
- command,
- shell=shell,
- stdin=stdin,
- stdout=stdout,
- stderr=stderr,
- close_fds=True,
- #encoding=encoding - only python-3.6+.
- )
-
- child_out = child.stdout
- if encoding:
- child_out = codecs.getreader( encoding)( child_out, errors)
-
- out = make_stream( out)
-
- if stdout == subprocess.PIPE:
- if buffer_len == -1:
- for line in child_out:
- out.write( line)
- else:
- while 1:
- text = child_out.read( buffer_len)
- if not text:
- break
- out.write( text)
- #decode( lambda : os.read( child_out.fileno(), 100), outfn, encoding)
-
- return child.wait()
-
-if __name__ == '__main__':
-
- if os.getenv( 'jtest_py_system_raw_test') == '1':
- out = io.StringIO()
- system_raw(
- 'jtest_py_system_raw_test=2 python jlib.py',
- sys.stdout,
- encoding='utf-8',
- #'latin_1',
- errors='replace',
- )
- print( repr( out.getvalue()))
-
- elif os.getenv( 'jtest_py_system_raw_test') == '2':
- for i in range(256):
- sys.stdout.write( chr(i))
-
-
-def system(
- command,
- verbose=None,
- raise_errors=True,
- out=None,
- prefix=None,
- rusage=False,
- shell=True,
- encoding=None,
- errors='replace',
- buffer_len=-1,
- ):
- '''
- Runs a command like os.system() or subprocess.*, but with more flexibility.
-
- We give control over where the command's output is sent, whether to return
- the output and/or exit code, and whether to raise an exception if the
- command fails.
-
- We also support the use of /usr/bin/time to gather rusage information.
-
- command:
- The command to run.
- verbose:
- If true, we output information about the command that we run, and
- its result.
-
- If callable or something with a .write() method, information is
- sent to <verbose> itself. Otherwise it is sent to <out> (without
- applying <prefix>).
- raise_errors:
- If true, we raise an exception if the command fails, otherwise we
- return the failing error code or zero.
- out:
- Python stream, fd, callable or Stream instance to which output is
- sent.
-
- If <out> is 'return', we buffer the output and return (e,
- <output>). Note that if raise_errors is true, we only return if <e>
- is zero.
-
- If -1, output is sent to stdout and stderr.
- prefix:
- If not None, should be prefix string or callable used to prefix
- all output. [This is for convenience to avoid the need to do
- out=StreamPrefix(...).]
- rusage:
- If true, we run via /usr/bin/time and return rusage string
- containing information on execution. <raise_errors> and
- out='return' are ignored.
- shell:
- Passed to underlying subprocess.Popen() call.
- encoding:
- Sepecify the encoding used to translate the command's output
- to characters. Defaults to utf-8.
- errors:
- How to handle encoding errors; see docs for codecs module
- for details. Defaults to 'replace' so we never raise a
- UnicodeDecodeError.
- buffer_len:
- The number of bytes we attempt to read at a time. If -1 we read
- output one line at a time.
-
- Returns:
- If <rusage> is true, we return the rusage text.
-
- Else if raise_errors is true:
- If the command failed, we raise an exception.
- Else if <out> is 'return' we return the text output from the command.
- Else we return None
-
- Else if <out> is 'return', we return (e, text) where <e> is the
- command's exit code and <text> is the output from the command.
-
- Else we return <e>, the command's exit code.
- '''
- if encoding is None:
- if sys.version_info[0] == 2:
- # python-2 doesn't seem to implement 'replace' properly.
- encoding = None
- errors = None
- else:
- encoding = 'utf-8'
- errors = 'replace'
-
- out_original = out
- if out is None:
- out = sys.stdout
- elif out == 'return':
- # Store the output ourselves so we can return it.
- out = io.StringIO()
- else:
- out = make_stream( out)
-
- if verbose:
- if getattr( verbose, 'write', None):
- pass
- elif callable( verbose):
- verbose = make_stream( verbose)
- else:
- verbose = out
-
- if prefix:
- out = StreamPrefix( out, prefix)
-
- if verbose:
- verbose.write( 'running: %s\n' % command)
-
- if rusage:
- command2 = ''
- command2 += '/usr/bin/time -o ubt-out -f "D=%D E=%D F=%F I=%I K=%K M=%M O=%O P=%P R=%r S=%S U=%U W=%W X=%X Z=%Z c=%c e=%e k=%k p=%p r=%r s=%s t=%t w=%w x=%x C=%C"'
- command2 += ' '
- command2 += command
- system_raw( command2, out, shell, encoding, errors, buffer_len=buffer_len)
- with open('ubt-out') as f:
- rusage_text = f.read()
- #print 'have read rusage output: %r' % rusage_text
- if rusage_text.startswith( 'Command '):
- # Annoyingly, /usr/bin/time appears to write 'Command
- # exited with ...' or 'Command terminated by ...' to the
- # output file before the rusage info if command doesn't
- # exit 0.
- nl = rusage_text.find('\n')
- rusage_text = rusage_text[ nl+1:]
- return rusage_text
- else:
- e = system_raw( command, out, shell, encoding, errors, buffer_len=buffer_len)
-
- if verbose:
- verbose.write( '[returned e=%s]\n' % e)
-
- if raise_errors:
- if e:
- raise Exception( 'command failed: %s' % command)
- if out_original == 'return':
- return out.getvalue()
- return
-
- if out_original == 'return':
- return e, out.getvalue()
- return e
-
-def get_gitfiles( directory, submodules=False):
- '''
- Returns list of all files known to git in <directory>; <directory> must be
- somewhere within a git checkout.
-
- Returned names are all relative to <directory>.
-
- If .git directory, we also create <directory>/jtest-git-files. Otherwise we
- assume a this file already exists.
- '''
- if os.path.isdir( '%s/.git' % directory):
- command = 'cd ' + directory + ' && git ls-files'
- if submodules:
- command += ' --recurse-submodules'
- command += ' > jtest-git-files'
- system( command, verbose=sys.stdout)
-
- with open( '%s/jtest-git-files' % directory, 'r') as f:
- text = f.read()
- ret = text.split( '\n')
- return ret
-
-def get_git_id_raw( directory):
- if not os.path.isdir( '%s/.git' % directory):
- return
- text = system(
- f'cd {directory} && (PAGER= git show --pretty=oneline|head -n 1 && git diff)',
- out='return',
- )
- return text
-
-def get_git_id( directory, allow_none=False):
- '''
- Returns text where first line is '<git-sha> <commit summary>' and remaining
- lines contain output from 'git diff' in <directory>.
-
- directory:
- Root of git checkout.
- allow_none:
- If true, we return None if <directory> is not a git checkout and
- jtest-git-id file does not exist.
- '''
- filename = f'{directory}/jtest-git-id'
- text = get_git_id_raw( directory)
- if text:
- with open( filename, 'w') as f:
- f.write( text)
- elif os.path.isfile( filename):
- with open( filename) as f:
- text = f.read()
- else:
- if not allow_none:
- raise Exception( f'Not in git checkout, and no file {filename}.')
- text = None
- return text
-
-class Args:
- '''
- Iterates over argv items. Does getopt-style splitting of args starting with
- single '-' character.
- '''
- def __init__( self, argv):
- self.argv = argv
- self.pos = 0
- self.pos_sub = None
- def next( self):
- while 1:
- if self.pos >= len(self.argv):
- raise StopIteration()
- arg = self.argv[self.pos]
- if (not self.pos_sub
- and arg.startswith('-')
- and not arg.startswith('--')
- ):
- # Start splitting current arg.
- self.pos_sub = 1
- if self.pos_sub and self.pos_sub >= len(arg):
- # End of '-' sub-arg.
- self.pos += 1
- self.pos_sub = None
- continue
- if self.pos_sub:
- # Return '-' sub-arg.
- ret = arg[self.pos_sub]
- self.pos_sub += 1
- return f'-{ret}'
- # Return normal arg.
- self.pos += 1
- return arg
-
-def update_file( text, filename):
- '''
- Writes <text> to <filename>. Does nothing if contents of <filename> are
- already <text>.
- '''
- try:
- with open( filename) as f:
- text0 = f.read()
- except OSError:
- text0 = None
- if text == text0:
- log( 'Unchanged: ' + filename)
- else:
- log( 'Updating: ' + filename)
- # Write to temp file and rename, to ensure we are atomic.
- filename_temp = f'{filename}-jlib-temp'
- with open( filename_temp, 'w') as f:
- f.write( text)
- os.rename( filename_temp, filename)
-
-
-def mtime( filename, default=0):
- '''
- Returns mtime of file, or <default> if error - e.g. doesn't exist.
- '''
- try:
- return os.path.getmtime( filename)
- except OSError:
- return default
-
-def get_filenames( paths):
- '''
- Yields each file in <paths>, walking any directories.
- '''
- if isinstance( paths, str):
- paths = (paths,)
- for name in paths:
- if os.path.isdir( name):
- for dirpath, dirnames, filenames in os.walk( name):
- for filename in filenames:
- path = os.path.join( dirpath, filename)
- yield path
- else:
- yield name
-
-def remove( path):
- '''
- Removes file or directory, without raising exception if it doesn't exist.
-
- We assert-fail if the path still exists when we return, in case of
- permission problems etc.
- '''
- try:
- os.remove( path)
- except Exception:
- pass
- shutil.rmtree( path, ignore_errors=1)
- assert not os.path.exists( path)
-
-
-# Things for figuring out whether files need updating, using mtimes.
-#
-def newest( names):
- '''
- Returns mtime of newest file in <filenames>. Returns 0 if no file exists.
- '''
- assert isinstance( names, (list, tuple))
- assert names
- ret_t = 0
- ret_name = None
- for filename in get_filenames( names):
- t = mtime( filename)
- if t > ret_t:
- ret_t = t
- ret_name = filename
- return ret_t, ret_name
-
-def oldest( names):
- '''
- Returns mtime of oldest file in <filenames> or 0 if no file exists.
- '''
- assert isinstance( names, (list, tuple))
- assert names
- ret_t = None
- ret_name = None
- for filename in get_filenames( names):
- t = mtime( filename)
- if ret_t is None or t < ret_t:
- ret_t = t
- ret_name = filename
- if ret_t is None:
- ret_t = 0
- return ret_t, ret_name
-
-def update_needed( infiles, outfiles):
- '''
- If any file in <infiles> is newer than any file in <outfiles>, returns
- string description. Otherwise returns None.
- '''
- in_tmax, in_tmax_name = newest( infiles)
- out_tmin, out_tmin_name = oldest( outfiles)
- if in_tmax > out_tmin:
- text = f'{in_tmax_name} is newer than {out_tmin_name}'
- return text
-
-def build(
- infiles,
- outfiles,
- command,
- force_rebuild=False,
- out=None,
- all_reasons=False,
- verbose=True,
- prefix=None,
- ):
- '''
- Ensures that <outfiles> are up to date using enhanced makefile-like
- determinism of dependencies.
-
- Rebuilds <outfiles> by running <command> if we determine that any of them
- are out of date.
-
- infiles:
- Names of files that are read by <command>. Can be a single filename. If
- an item is a directory, we expand to all filenames in the directory's
- tree.
- outfiles:
- Names of files that are written by <command>. Can also be a single
- filename.
- command:
- Command to run.
- force_rebuild:
- If true, we always re-run the command.
- out:
- A callable, passed to jlib.system(). If None, we use jlib.log() with
- our caller's stack record.
- all_reasons:
- If true we check all ways for a build being needed, even if we already
- know a build is needed; this only affects the diagnostic that we
- output.
- verbose:
- Passed to jlib.system().
- prefix:
- Passed to jlib.system().
-
- We compare mtimes of <infiles> and <outfiles>, and we also detect changes
- to the command itself.
-
- If any of infiles are newer than any of outfiles, or <command> is
- different to contents of commandfile '<outfile[0]>.cmd, then truncates
- commandfile and runs <command>. If <command> succeeds we writes <command>
- to commandfile.
- '''
- if isinstance( infiles, str):
- infiles = (infiles,)
- if isinstance( outfiles, str):
- infiles = (outfiles,)
-
- if not out:
- out_frame_record = inspect.stack()[1]
- out = lambda text: log( text, nv=0, caller=out_frame_record)
-
- command_filename = f'{outfiles[0]}.cmd'
-
- reasons = []
-
- if not reasons or all_reasons:
- if force_rebuild:
- reasons.append( 'force_rebuild was specified')
-
- if not reasons or all_reasons:
- try:
- with open( command_filename) as f:
- command0 = f.read()
- except Exception:
- command0 = None
- if command != command0:
- if command0:
- reasons.append( 'command has changed')
- else:
- reasons.append( 'no previous command')
-
- if not reasons or all_reasons:
- reason = update_needed( infiles, outfiles)
- if reason:
- reasons.append( reason)
-
- if not reasons:
- out( 'Already up to date: ' + ' '.join(outfiles))
- return
-
- if out:
- out( 'Rebuilding because %s: %s' % (
- ', and '.join( reasons),
- ' '.join(outfiles),
- ))
-
- # Empty <command_filename) while we run the command so that if command
- # fails but still creates target(s), then next time we will know target(s)
- # are not up to date.
- #
- with open( command_filename, 'w') as f:
- pass
-
- system( command, out=out, verbose=verbose, prefix=prefix)
-
- with open( command_filename, 'w') as f:
- f.write( command)
-
-
-def link_l_flags( sos):
- '''
- Returns flags needed to link with items in <sos>. For each unique item we
- use -L with parent directory, and -l with embedded name (without leading
- 'lib' or trailing '.co').
- '''
- dirs = set()
- names = []
- if isinstance( sos, str):
- sos = (sos,)
- for so in sos:
- dir_ = os.path.dirname( so)
- name = os.path.basename( so)
- assert name.startswith( 'lib')
- assert name.endswith ( '.so')
- name = name[3:-3]
- dirs.add( dir_)
- names.append( name)
- ret = ''
- # Important to use sorted() here, otherwise ordering from set() is
- # arbitrary causing occasional spurious rebuilds by jlib.build().
- for dir_ in sorted(dirs):
- ret += f' -L {dir_}'
- for name in names:
- ret += f' -l {name}'
- return ret