Mercurial > parpg-core
changeset 5:33684971cdb1
Replaced the shell script launcher with a cross-platform C executable.
* Replaced the Unix-only shell script launcher with a cross-platform compiled executable written in C.
* Added several new configuration variables to the SConsctruct script which allows the user to specify where their Python site-packages and executable are located;
* Cleaned up the SConstruct script and made it easier to grok.
author | M. George Hansen <technopolitica@gmail.com> |
---|---|
date | Tue, 17 May 2011 14:18:25 -0700 |
parents | 20c2b7a42c63 |
children | dd4ed4945411 |
files | SConstruct bin/parpg.c.in bin/unix/parpg.in |
diffstat | 3 files changed, 172 insertions(+), 57 deletions(-) [+] |
line wrap: on
line diff
--- a/SConstruct Sat May 21 09:13:52 2011 +0200 +++ b/SConstruct Tue May 17 14:18:25 2011 -0700 @@ -4,6 +4,7 @@ import compileall import fnmatch from collections import Sequence +from types import StringType from multiprocessing import cpu_count def InstallChmod(env, dest, source, mode): @@ -51,7 +52,7 @@ source_file_path = file_path.rstrip('oc') env.Depends(file_path, source_file_path) - if not isinstance(source, Sequence): + if not isinstance(source, Sequence) or isinstance(source, StringType): source = [source] for dir in source: _remove_compiled_modules(str(dir)) @@ -81,7 +82,7 @@ dest='compile', action='store_false', default=True, - help='don\'t compile any Python modules into .pyc files', + help='don\'t compile any Python modules into .pyc files before installing', ) SetOption('num_jobs', cpu_count()) @@ -115,19 +116,22 @@ if dist_name in ['debian', 'Ubuntu']: # Debian uses dist-packages instead of site-packages for Python # versions > 2.5. - PY_LIB_DIR_DEFAULT = '$EXEC_PREFIX/lib/python$PY_VERSION_SHORT/' \ - 'dist-packages' + PY_PACKAGES_DIR_DEFAULT = '$EXEC_PREFIX/lib/python$PY_VERSION_SHORT/' \ + 'dist-packages' else: - PY_LIB_DIR_DEFAULT = '$EXEC_PREFIX/lib/python$PY_VERSION_SHORT/' \ - 'site-packages' + PY_PACKAGES_DIR_DEFAULT = '$EXEC_PREFIX/lib/python$PY_VERSION_SHORT/' \ + 'site-packages' + PY_HEADERS_DIR_DEFAULT = '/usr/include/python$PY_VERSION_SHORT' + PY_LIB_DIR_DEFAULT = '/usr/lib' + PY_LIB_NAME = 'python$PY_VERSION_SHORT' elif platform_name == 'Windows': try: - PREFIX_DEFAULT = os.environ['ProgramFiles'] + r'\$PROGRAM_NAME' + PREFIX_DEFAULT = os.environ['PROGRAMFILES'] + r'\$PROGRAM_NAME' except KeyError: PREFIX_DEFAULT = '' - error_message = '%ProgramFiles% environmental variable is not ' \ + error_message = '%PROGRAMFILES% environmental variable is not ' \ 'set, unable to determine path to Program Files ' \ - 'folder (use --prefix to override)' + 'folder' raise SConfWarning(error_message) BIN_DIR_DEFAULT = '$EXEC_PREFIX' DATA_ROOT_DIR_DEFAULT = '$PREFIX/data' @@ -136,14 +140,23 @@ DOC_DIR_DEFAULT = '$PREFIX/doc/' LIB_DIR_DEFAULT = '$EXEC_PREFIX/lib' PY_LIB_DIR_DEFAULT = '$EXEC_PREFIX/lib' + # FIXME M. George Hansen 2011-05-12: Does sys.prefix include the + # PythonX.Y part on Windows? + python_prefix = sys.prefix if GetOption('stand_alone'): - PY_LIB_DIR_DEFAULT = '$PREFIX/lib' + PY_PACKAGES_DIR_DEFAULT = '$PREFIX/lib' else: - python_prefix = sys.prefix - # FIXME M. George Hansen 2011-05-12: Does sys.prefix include the - # PythonX.Y part on Windows? - PY_LIB_DIR_DEFAULT = \ + PY_PACKAGES_DIR_DEFAULT = \ os.path.join(python_prefix, 'Lib', 'site-packages') + PY_HEADERS_DIR_DEFAULT = os.path.join(python_prefix, 'include') + try: + PY_LIB_DIR_DEFAULT = os.environ['SYSTEMROOT'] + except KeyError: + PY_LIB_DIR_DEFAULT = '' + error_message = '%SYSTEMROOT% environmental variable is not set, ' \ + 'unable to determine path to Windows system folder' + raise SConfWarning(error_message) + PY_LIB_NAME = 'python$PY_VERSION_MAJOR$PY_VERSION_MINOR' # Platform-independant variables: variables.AddVariables( @@ -208,9 +221,9 @@ is_abs_path, ), PathVariable( - 'PY_LIB_DIR', + 'PY_PACKAGES_DIR', 'directory where pure Python modules and packages should be installed', - PY_LIB_DIR_DEFAULT, + PY_PACKAGES_DIR_DEFAULT, is_abs_path, ), PathVariable( @@ -219,69 +232,97 @@ INCLUDE_DIR_DEFAULT, is_abs_path, ), + PathVariable( + 'PY_HEADERS_DIR', + 'directory where Python.h can be found', + PY_HEADERS_DIR_DEFAULT, + ), + PathVariable( + 'PY_LIB_DIR', + 'directory where the Python shared library can be found', + PY_LIB_DIR_DEFAULT, + ), + BoolVariable( + 'DEBUG', + 'if True, compile the program launcher executable with debugging ' + 'symbols', + False, + ), ) platform_name = platform.system() python_version_tuple = platform.python_version_tuple() environment = Environment( - tools=['default', 'textfile', 'packaging'], + tools=['default', 'cc', 'textfile', 'packaging'], variables=variables, PROJECT_NAME='parpg', PROJECT_VERSION_MAJOR=0, PROJECT_VERSION_MINOR=2, PROJECT_VERSION_PATCH=0, - PROJECT_VERSION_SHORT='$PROJECT_VERSION_MAJOR.$PROJECT_VERSION_MAJOR', - PROJECT_VERSION_LONG='$PROJECT_VERSION_MAJOR.$PROJECT_VERSION_MINOR.' - '$PROJECT_VERSION_PATCH', + PROJECT_VERSION_SHORT='${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MAJOR}', + PROJECT_VERSION_LONG='${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.' + '${PROJECT_VERSION_PATCH}', PYTHON=sys.executable, - PY_VERSION_SHORT='.'.join(python_version_tuple[:-1]), - PY_VERSION_LONG='.'.join(python_version_tuple), + PY_VERSION_MAJOR=python_version_tuple[0], + PY_VERSION_MINOR=python_version_tuple[1], + PY_VERSION_PATCH=python_version_tuple[2], + PY_VERSION_SHORT='${PY_VERSION_MAJOR}.${PY_VERSION_MINOR}', + PY_VERSION_LONG='${PY_VERSION_MAJOR}.${PY_VERSION_MINOR}.' + '${PY_VERSION_PATCH}', + PY_LIB_NAME=PY_LIB_NAME, PY_PACKAGES=[], CONFIG_FILES=[], DATA_FILES=[], EXECUTABLES=[], ) +if environment['DEBUG']: + environment.AppendUnique(CCFLAGS=['-gdwarf-2', '-g3']) + Help(variables.GenerateHelpText(environment)) config_dict = [('@{0}@'.format(key), environment.Dictionary()[key]) for key in - ('PREFIX', 'LIB_DIR', 'PY_LIB_DIR', 'BIN_DIR', 'SYS_CONF_DIR', - 'DATA_DIR', 'PYTHON')] + ('PREFIX', 'LIB_DIR', 'PY_PACKAGES_DIR', 'BIN_DIR', + 'SYS_CONF_DIR', 'DATA_DIR', 'PYTHON')] -environment['PY_PACKAGES'] += [ - Dir('src/parpg'), -] -config_file_template = File('system.cfg.in') -subst_config_file = environment.Substfile(config_file_template, - SUBST_DICT=config_dict) -environment['CONFIG_FILES'] += [subst_config_file] -environment['DATA_FILES'] += Glob('data/*') -# TODO M. George Hansen 2011-05-12: Implement windows executable. -executable_template = File('bin/unix/parpg.in') -subst_executable = environment.Substfile(executable_template, - SUBST_DICT=config_dict) -environment['EXECUTABLES'] += [subst_executable] - -sources = environment.InstallPyPackages( - '$PY_LIB_DIR', - environment['PY_PACKAGES'], +install_py_packages = environment.InstallPyPackages( + '$PY_PACKAGES_DIR', + 'src/parpg', compile=GetOption('compile'), ) -config_files = environment.InstallChmod( + +subst_config_file = environment.Substfile( + 'system.cfg.in', + SUBST_DICT=config_dict +) +install_config_files = environment.InstallChmod( '$SYS_CONF_DIR', - environment['CONFIG_FILES'], - mode=0755, + subst_config_file, + mode=0755 ) -Requires(config_files, subst_config_file) -data_files = environment.InstallReadOnly( +Requires(install_config_files, subst_config_file) + +install_data = environment.InstallReadOnly( '$DATA_DIR', - environment['DATA_FILES'], + Glob('data/*'), +) + +executable_source = environment.Substfile( + 'bin/parpg.c.in', + SUBST_DICT=config_dict, ) -executables = environment.InstallExecutable( +build_executable = environment.Program( + executable_source, + LIBS=['$PY_LIB_NAME'], + LIBPATH='$PY_LIB_DIR', + CPPPATH=['$PY_HEADERS_DIR'], +) +# FIXME M. George Hansen 2011-05-17: Should the executable target be hard-coded +# like this? +install_executable = environment.InstallExecutable( '$BIN_DIR', - environment['EXECUTABLES'], + 'bin/parpg', ) -Requires(executables, subst_executable) # TODO M. George Hansen 2011-05-12: Implement package builder. #package = environment.Package( @@ -294,5 +335,8 @@ # X_RPM_GROUP='Application/parpg', #) -Default([sources, config_files, data_files, executables]) -Alias('install', [sources, config_files, data_files, executables]) +build = Alias('build', [build_executable]) +install = Alias('install', [build, install_executable, install_py_packages, + install_config_files, install_data]) + +Default(install)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/parpg.c.in Tue May 17 14:18:25 2011 -0700 @@ -0,0 +1,76 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#ifdef _MSC_VER +# include <process.h> +#else +# include <sys/types.h> +# include <sys/wait.h> +#endif + +#define CONCAT_ARRAYS(TYPE, ARRAY_A, SIZE_A, ARRAY_B, SIZE_B) \ + (TYPE*)concatArrays((const void**)(ARRAY_A), (SIZE_A), \ + (const void**)(ARRAY_B), (SIZE_B), sizeof(TYPE)) + +#define NULL_TERMINATE(TYPE, ARRAY, SIZE) \ + (TYPE*)realloc(ARRAY, sizeof(TYPE) * (SIZE + 1)) + +#define ARRAY_LEN(ARRAY) (sizeof(ARRAY) / sizeof *(ARRAY)) + +void* +concatArrays(const void* arrayA, size_t sizeA, + const void* arrayB, size_t sizeB, size_t sizeType) { + char* concatenatedArray = malloc(sizeType * (sizeA + sizeB)); + memcpy(concatenatedArray, arrayA, sizeA * sizeType); + memcpy(concatenatedArray + sizeA * sizeType, arrayB, sizeB * sizeType); + return concatenatedArray; +} + +char* +joinStr(char* strA, char* strB, char* separator) { + int lenA = strlen(strA); + int lenB = strlen(strB); + int lenSeparator = strlen(separator); + char* concatenatedStr = malloc(lenA + lenB + lenSeparator + 1); + memcpy(concatenatedStr, strA, lenA); + memcpy(concatenatedStr + lenA, separator, lenSeparator); + memcpy(concatenatedStr + lenA + lenSeparator, strB, lenB + 1); + return concatenatedStr; +} + +int +spawnParpgProcess(char* pythonExecutable, char* configDir, int argc, + char* argv[]) { + char* baseArgs[] = {pythonExecutable, "-m", "parpg.main", configDir}; + int nBaseArgs = ARRAY_LEN(baseArgs); + int nAdditionalArgs = argc - 1; + char* additionalArgs[nAdditionalArgs]; + int i; + for (i = 1; i < argc; i++) { + additionalArgs[i - 1] = argv[i]; + } + int nArgs = nBaseArgs + nAdditionalArgs; + char** args = NULL_TERMINATE(char*, CONCAT_ARRAYS(char*, baseArgs, + nBaseArgs, additionalArgs, nAdditionalArgs), nArgs); + char* oldPythonPath = getenv("PYTHONPATH"); + char* newPythonPath = joinStr(oldPythonPath, "@PY_PACKAGES_DIR@", ":"); + int exitStatus; + char* env[2] = {joinStr("PYTHONPATH=", newPythonPath, ""), (char*)(0)}; +#ifdef _MSC_VER + exitStatus = spawnve(P_WAIT, pythonExecutable, args, env); +#else + exitStatus = execve(pythonExecutable, args, env); +#endif + return exitStatus; +} + +int +main(int argc, char* argv[]) { + int exitStatus = spawnParpgProcess("@PYTHON@", "@SYS_CONF_DIR@", argc, + argv); + if (exitStatus) { + perror("parpg exited with error"); + } + return exitStatus; +} +