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;
+}
+
--- a/bin/unix/parpg.in	Sat May 21 09:13:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-#!/bin/sh
-
-export PYTHONPATH="$PYTHONPATH:@PY_LIB_DIR@"
-@PYTHON@ -m "parpg.main" "@SYS_CONF_DIR@"
-