changeset 6:dd4ed4945411

Ported binary launcher to Windows. * Reorganized the parpg executable source so that it now compiles with the MSVC compiler (tested with MSVC Express 2010). * Cleaned up the launcher source by adding a bunch of const statements and removing the superfluous NULL_TERMINATE macro. * Fixed a few memory leaks in the launcher source by freeing malloced/calloced pointers; * Added a new SubstfileEscape builder to the SConstruct script used to escape certain sequences when substituting a template file. This is used to escape backslashes ("\") in c strings in the bin/parpg.c.in template for Windows paths. * Fixed the PY_LIB_DIR_DEFAULT for Windows so that it now correctly points to the default Python installation path. * Modified the SConstruct script to support compiling the launcher executable with debugging symbols in Windows with the DEBUG flag.
author M. George Hansen
date Sun, 22 May 2011 00:53:59 -0700
parents 33684971cdb1
children af8e90c22758
files SConstruct bin/parpg.c.in
diffstat 2 files changed, 106 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/SConstruct	Tue May 17 14:18:25 2011 -0700
+++ b/SConstruct	Sun May 22 00:53:59 2011 -0700
@@ -7,6 +7,8 @@
 from types import StringType
 from multiprocessing import cpu_count
 
+from SCons.Util import is_Sequence, is_Dict
+
 def InstallChmod(env, dest, source, mode):
     targets = env.Install(dest, source)
     for target in targets:
@@ -62,10 +64,31 @@
     targets = env.InstallReadOnly(dest, source)
     return targets
 
+def SubstfileEscape(env, *args, **kwargs):
+    subst_dict = kwargs.get('SUBST_DICT') or env.get('SUBST_DICT')
+    escape_sequences = kwargs.get('ESCAPE_SEQUENCES') or env.get('ESCAPE_SEQUENCES')
+    if subst_dict is not None and escape_sequences is not None:
+        if not is_Dict(subst_dict) and is_Sequence(subst_dict):
+            subst_dict = dict(subst_dict)
+        else:
+            error_message = 'SUBST_DICT must be dict or sequence'
+            raise SCons.Errors.UserError(error_message)
+        escaped_subst_dict = {}
+        for key, value in subst_dict.items():
+            escaped_value = value
+            for seq, escaped_seq in escape_sequences.items():
+                escaped_value = escaped_value.replace(seq, escaped_seq)
+            escaped_subst_dict[key] = escaped_value
+        kwargs['SUBST_DICT'] = escaped_subst_dict
+    
+    target = env.Substfile(*args, **kwargs)
+    return target
+
 AddMethod(Environment, InstallChmod)
 AddMethod(Environment, InstallExecutable)
 AddMethod(Environment, InstallReadOnly)
 AddMethod(Environment, InstallPyPackages)
+AddMethod(Environment, SubstfileEscape)
 
 EnsurePythonVersion(2, 6)
 
@@ -139,7 +162,6 @@
     INCLUDE_DIR_DEFAULT = '$PREFIX/include'
     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
@@ -149,13 +171,7 @@
         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_DIR_DEFAULT = os.path.join(python_prefix, 'libs')
     PY_LIB_NAME = 'python$PY_VERSION_MAJOR$PY_VERSION_MINOR'
 
 # Platform-independant variables:
@@ -250,11 +266,10 @@
     ),
 )
 
-platform_name = platform.system()
 python_version_tuple = platform.python_version_tuple()
 
 environment = Environment(
-    tools=['default', 'cc', 'textfile', 'packaging'],
+    tools=['default', 'textfile', 'packaging'],
     variables=variables,
     PROJECT_NAME='parpg',
     PROJECT_VERSION_MAJOR=0,
@@ -277,7 +292,11 @@
     EXECUTABLES=[],
 )
 if environment['DEBUG']:
-    environment.AppendUnique(CCFLAGS=['-gdwarf-2', '-g3'])
+    if platform_name == 'Windows':
+        environment['CCPDBFLAGS'] = '/Z7 /Od'
+        environment.AppendUnique(LINKFLAGS='/DEBUG')
+    else:
+        environment.AppendUnique(CCFLAGS=['-gdwarf-2', '-g3'])
 
 Help(variables.GenerateHelpText(environment))
 
@@ -307,9 +326,12 @@
     Glob('data/*'),
 )
 
-executable_source = environment.Substfile(
+# FIXME M. George Hansen 2011-05-20: Do any other sequences need to be escaped
+#     in a C string?
+executable_source = environment.SubstfileEscape(
     'bin/parpg.c.in',
     SUBST_DICT=config_dict,
+    ESCAPE_SEQUENCES={'\\': r'\\\\', '"': r'\\"'},
 )
 build_executable = environment.Program(
     executable_source,
@@ -317,11 +339,13 @@
     LIBPATH='$PY_LIB_DIR',
     CPPPATH=['$PY_HEADERS_DIR'],
 )
-# FIXME M. George Hansen 2011-05-17: Should the executable target be hard-coded
-#     like this?
+# Clean up any files created by the MSVC compiler on Windows.
+if platform_name == 'Windows':
+    environment.Clean(build_executable, 'bin/parpg.ilk')
+    environment.Clean(build_executable, 'bin/parpg.pdb')
 install_executable = environment.InstallExecutable(
     '$BIN_DIR',
-    'bin/parpg',
+    build_executable,
 )
 
 # TODO M. George Hansen 2011-05-12: Implement package builder.
--- a/bin/parpg.c.in	Tue May 17 14:18:25 2011 -0700
+++ b/bin/parpg.c.in	Sun May 22 00:53:59 2011 -0700
@@ -1,37 +1,36 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
-#ifdef _MSC_VER
+#ifdef _WIN32
 #  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))
 
+#define CONCAT_ARRAYS(TYPE, ARRAY_A, ARRAY_B) \
+  (TYPE*)concatArrays((const void** const)(ARRAY_A), \
+  ARRAY_LEN(ARRAY_A) * sizeof(TYPE), (const void** const)(ARRAY_B), \
+  ARRAY_LEN(ARRAY_B) * sizeof(TYPE), sizeof(TYPE))
+
 void*
-concatArrays(const void* arrayA, size_t sizeA,
-  const void* arrayB, size_t sizeB, size_t sizeType) {
-    char* concatenatedArray = malloc(sizeType * (sizeA + sizeB));
+concatArrays(const void* const arrayA, const size_t sizeA,
+  const void* const arrayB, const size_t sizeB, const size_t sizeType) {
+    char* const 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);
+joinStr(const char* const strA, const char* const strB,
+  const char* const separator) {
+    const int lenA = strlen(strA);
+    const int lenB = strlen(strB);
+    const int lenSeparator = strlen(separator);
+    char* const concatenatedStr = malloc(lenA + lenB + lenSeparator + 1);
     memcpy(concatenatedStr, strA, lenA);
     memcpy(concatenatedStr + lenA, separator, lenSeparator);
     memcpy(concatenatedStr + lenA + lenSeparator, strB, lenB + 1);
@@ -39,38 +38,69 @@
 }
 
 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];
+spawnParpgProcess(char* const pythonExecutable, char* const configDir,
+  const int argc, char* const argv[]) {
+    const int sizeCharPtr = sizeof(char*);
+    const int nBaseArgs = 4;
+    char** const baseArgs = malloc(nBaseArgs * sizeCharPtr);
+    const int nAdditionalArgs = argc - 1;
+    char** const additionalArgs =
+      (char**)malloc(nAdditionalArgs * sizeCharPtr);
+    const int nArgs = nBaseArgs + nAdditionalArgs;
+    char* const oldPythonPath = getenv("PYTHONPATH");
+    char* newPythonPath;
+    // Use calloc so that the last element is NULL.
+    char** const env = calloc(2, sizeCharPtr);
+    int exitStatus;
+    // Use calloc so that the last element is NULL.
+    char** const args = (char**)calloc(nArgs + 1,  sizeCharPtr);
     int i;
-    for (i = 1; i < argc; i++) {
-        additionalArgs[i - 1] = argv[i];
+    
+    baseArgs[0] = pythonExecutable;
+    baseArgs[1] = "-m";
+    baseArgs[2] = "parpg.main";
+    baseArgs[3] = configDir;
+    if (nAdditionalArgs > 0) {
+        for (i = 1; i < argc; i++) {
+            additionalArgs[i - 1] = argv[i];
+        }
+        memcpy(args, CONCAT_ARRAYS(char*, baseArgs, additionalArgs),
+          nArgs * sizeCharPtr);
+    } else {
+        // Use memcpy so that the last NULL element in args is preserved.
+        memcpy(args, baseArgs, nArgs * sizeCharPtr);
     }
-    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
+    if (oldPythonPath != 0) {
+        newPythonPath = joinStr(oldPythonPath, "@PY_PACKAGES_DIR@", ":");
+    } else {
+        newPythonPath = "@PY_PACKAGES_DIR@";
+    }
+    env[0] = joinStr("PYTHONPATH=", newPythonPath, "");
+#ifdef _WIN32
+#  ifdef _MSC_VER // MSVC deprecated POSIX spawn functions.
+    exitStatus = _spawnve(_P_WAIT, pythonExecutable, args, env);
+#  else
     exitStatus = spawnve(P_WAIT, pythonExecutable, args, env);
+#  endif
 #else
     exitStatus = execve(pythonExecutable, args, env);
 #endif
+    free(baseArgs);
+    free(additionalArgs);
+    free(env);
+    free(args);
     return exitStatus;
 }
 
 int
 main(int argc, char* argv[]) {
-    int exitStatus = spawnParpgProcess("@PYTHON@", "@SYS_CONF_DIR@", argc,
-      argv);
-    if (exitStatus) {
-        perror("parpg exited with error");
+    const int exitStatus = spawnParpgProcess("@PYTHON@", "@SYS_CONF_DIR@",
+      argc, argv);
+    if (exitStatus < 0) {
+        perror("failed to execute subprocess");
+        exit(EXIT_FAILURE);
+    } else {
+        exit(EXIT_SUCCESS);
     }
-    return exitStatus;
 }