changeset 20:07ff8cf8a0f1

Fixed WAF install paths issue on Windows. * Reorganized the waf_paths.py WAF tool so that install paths are correctly set on Windows. * Added ordereddict.py for use by the waf_paths.py WAF tool on python versions less than 2.7. * Renamed the waf script to waf.py so that Windows users get the benefits of the .py file extension. * Fixed a bug where the FifePath entry in parpg.cfg was not getting set to the default python site-package path. * Fixed a bug in the Windows parpg.bat launcher where quotation marks (") were screwing up the PYTHONPATH variable.
author M. George Hansen <technopolitica@gmail.com>
date Wed, 15 Jun 2011 13:21:25 -1000
parents e97972cc7110
children feceb6130570
files bin/parpg.bat.in ordereddict.py waf waf.py waf_paths.py wscript
diffstat 6 files changed, 268 insertions(+), 92 deletions(-) [+]
line wrap: on
line diff
--- a/bin/parpg.bat.in	Fri Jun 10 11:57:39 2011 -1000
+++ b/bin/parpg.bat.in	Wed Jun 15 13:21:25 2011 -1000
@@ -1,5 +1,5 @@
 @ECHO OFF
 
-SET PYTHONPATH=%PYTHONPATH%;"@PYTHONDIR@"
+SET PYTHONPATH=%PYTHONPATH%;@PYTHONDIR@
 CD %~dp0
-"@PYTHON@" -m parpg.main "@SYSCONFDIR@" %*
+@PYTHON@ -m parpg.main "@SYSCONFDIR@" %*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ordereddict.py	Wed Jun 15 13:21:25 2011 -1000
@@ -0,0 +1,127 @@
+# Copyright (c) 2009 Raymond Hettinger
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+#     The above copyright notice and this permission notice shall be
+#     included in all copies or substantial portions of the Software.
+#
+#     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+#     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+#     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+#     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+#     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+#     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+#     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+#     OTHER DEALINGS IN THE SOFTWARE.
+
+from UserDict import DictMixin
+
+class OrderedDict(dict, DictMixin):
+
+    def __init__(self, *args, **kwds):
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__end
+        except AttributeError:
+            self.clear()
+        self.update(*args, **kwds)
+
+    def clear(self):
+        self.__end = end = []
+        end += [None, end, end]         # sentinel node for doubly linked list
+        self.__map = {}                 # key --> [key, prev, next]
+        dict.clear(self)
+
+    def __setitem__(self, key, value):
+        if key not in self:
+            end = self.__end
+            curr = end[1]
+            curr[2] = end[1] = self.__map[key] = [key, curr, end]
+        dict.__setitem__(self, key, value)
+
+    def __delitem__(self, key):
+        dict.__delitem__(self, key)
+        key, prev, next = self.__map.pop(key)
+        prev[2] = next
+        next[1] = prev
+
+    def __iter__(self):
+        end = self.__end
+        curr = end[2]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[2]
+
+    def __reversed__(self):
+        end = self.__end
+        curr = end[1]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[1]
+
+    def popitem(self, last=True):
+        if not self:
+            raise KeyError('dictionary is empty')
+        if last:
+            key = reversed(self).next()
+        else:
+            key = iter(self).next()
+        value = self.pop(key)
+        return key, value
+
+    def __reduce__(self):
+        items = [[k, self[k]] for k in self]
+        tmp = self.__map, self.__end
+        del self.__map, self.__end
+        inst_dict = vars(self).copy()
+        self.__map, self.__end = tmp
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def keys(self):
+        return list(self)
+
+    setdefault = DictMixin.setdefault
+    update = DictMixin.update
+    pop = DictMixin.pop
+    values = DictMixin.values
+    items = DictMixin.items
+    iterkeys = DictMixin.iterkeys
+    itervalues = DictMixin.itervalues
+    iteritems = DictMixin.iteritems
+
+    def __repr__(self):
+        if not self:
+            return '%s()' % (self.__class__.__name__,)
+        return '%s(%r)' % (self.__class__.__name__, self.items())
+
+    def copy(self):
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        d = cls()
+        for key in iterable:
+            d[key] = value
+        return d
+
+    def __eq__(self, other):
+        if isinstance(other, OrderedDict):
+            if len(self) != len(other):
+                return False
+            for p, q in  zip(self.items(), other.items()):
+                if p != q:
+                    return False
+            return True
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self == other
Binary file waf has changed
Binary file waf.py has changed
--- a/waf_paths.py	Fri Jun 10 11:57:39 2011 -1000
+++ b/waf_paths.py	Wed Jun 15 13:21:25 2011 -1000
@@ -1,73 +1,67 @@
 #! /usr/bin/env python
 # encoding: utf-8
-import sys
+import os
+import platform
+import re
 
-from waflib import Utils, Options, Context
+from waflib import Utils, Options, Context, Logs, Errors
 
-option_values = {
-    'bindir'        : ('user executables', '${EXEC_PREFIX}/bin'),
-    'sbindir'       : ('system admin executables', '${EXEC_PREFIX}/sbin'),
-    'libexecdir'    : ('program executables', '${EXEC_PREFIX}/libexec'),
-    'sharedstatedir': ('modifiable architecture-independent data',
-                       '${PREFIX}/com'),
-    'localstatedir' : ('modifiable single-machine data', '${PREFIX}/var'),
-    'libdir'        : ('object code libraries', '${EXEC_PREFIX}/lib'),
-    'includedir'    : ('C header files', '${PREFIX}/include'),
-    'oldincludedir' : ('C header files for non-gcc', '/usr/include'),
-    'datarootdir'   : ('read-only arch.-independent data root',
-                       '${PREFIX}/share'),
-    # datadir and sysconfdir definitions deviate from GNU standards by
-    # appending the ${APPNAME}, but it makes the install script simpler since
-    # we don't have to redefine install paths on Windows.
-    'datadir'       : ('read-only architecture-independent data',
-                       '${DATAROOTDIR}/${APPNAME}'),
-    'sysconfdir'    : ('read-only single-machine data',
-                       '${PREFIX}/etc/${APPNAME}'),
-    'infodir'       : ('info documentation', '${DATAROOTDIR}/info'),
-    'localedir'     : ('locale-dependent data', '${DATAROOTDIR}/locale'),
-    'mandir'        : ('man documentation', '${DATAROOTDIR}/man'),
-    'docdir'        : ('documentation root', '${DATAROOTDIR}/doc/${APPNAME}'),
-    'htmldir'       : ('html documentation', '${DOCDIR}'),
-    'dvidir'        : ('dvi documentation', '${DOCDIR}'),
-    'pdfdir'        : ('pdf documentation', '${DOCDIR}'),
-    'psdir'         : ('ps documentation', '${DOCDIR}'),
-}
+try:
+    from collections import OrderedDict
+except ImportError:
+    # Python 2.6 doesn't have an OrderedDict, so we'll provide one.
+    from ordereddict import OrderedDict
+
+prefix_values = OrderedDict(
+    [
+        ('prefix', ['installation prefix', '/usr/local']),
+        ('exec_prefix',
+         ['installation prefix for platform-dependent binary files',
+          '${PREFIX}'])
+    ]
+)
 
-if sys.platform == 'Windows':
-    option_values['PREFIX'][1] = '${PROGRAM_FILES}/${APPNAME}'
-    option_values['BINDIR'][1] = '${EXEC_PREFIX}'
-    option_values['SYSCONFDIR'][1] = '${PREFIX}'
-    option_values['DATAROOTDIR'][1] = '${PREFIX}'
-    option_values['DATADIR'][1] = '${DATAROOTDIR}/data'
-    option_values['DOCDIR'][1] = '${DATAROOTDIR}/doc'
-
-def get_param(varname, default):
-        return getattr(Options.options, varname, '') or default
+option_values = OrderedDict(
+    [
+        ('bindir', ['user executables', '${EXEC_PREFIX}/bin']),
+        ('sbindir', ['system admin executables', '${EXEC_PREFIX}/sbin']),
+        ('libexecdir', ['program executables', '${EXEC_PREFIX}/libexec']),
+        ('sharedstatedir', ['modifiable architecture-independent data',
+                            '${PREFIX}/com']),
+        ('localstatedir', ['modifiable single-machine data', '${PREFIX}/var']),
+        ('libdir', ['object code libraries', '${EXEC_PREFIX}/lib']),
+        ('includedir', ['C header files', '${PREFIX}/include']),
+        ('oldincludedir', ['C header files for non-gcc', '/usr/include']),
+        ('datarootdir', ['read-only arch.-independent data root',
+                         '${PREFIX}/share']),
+        # datadir and sysconfdir definitions deviate from GNU standards by
+        # appending the ${APPNAME}, but it makes the install script simpler
+        # since we don't have to redefine install paths on Windows.
+        ('datadir', ['read-only architecture-independent data',
+                     '${DATAROOTDIR}/${APPNAME}']),
+        ('sysconfdir', ['read-only single-machine data',
+                        '${PREFIX}/etc/${APPNAME}']),
+        ('infodir', ['info documentation', '${DATAROOTDIR}/info']),
+        ('localedir', ['locale-dependent data', '${DATAROOTDIR}/locale']),
+        ('mandir', ['man documentation', '${DATAROOTDIR}/man']),
+        ('docdir', ['documentation root', '${DATAROOTDIR}/doc/${APPNAME}']),
+        ('htmldir', ['html documentation', '${DOCDIR}']),
+        ('dvidir', ['dvi documentation', '${DOCDIR}']),
+        ('pdfdir', ['pdf documentation', '${DOCDIR}']),
+        ('psdir', ['ps documentation', '${DOCDIR}']),
+    ]
+)
 
-def configure(conf):
-    env = conf.env
-    env['LIBDIR'] = []
-    env['BINDIR'] = []
-    env['EXEC_PREFIX'] = get_param('EXEC_PREFIX', env['PREFIX'])
-    env['APPNAME'] = getattr(Context.g_module, 'APPNAME')
-    env['INSTALL_PATHS'] = ['PREFIX', 'EXEC_PREFIX']
-    complete = False
-    iter = 0
-    while not complete and iter < len(option_values) + 1:
-        iter += 1
-        complete = True
-        for name in option_values:
-            help, default = option_values[name]
-            name = name.upper()
-            if not env[name]:
-                try:
-                    env[name] = Utils.subst_vars(get_param(name, default), env)
-                except TypeError:
-                    complete = False
-            env['INSTALL_PATHS'].append(name)
-    if not complete:
-        lst = [name for name in option_values if not env[name.upper()]]
-        raise Errors.WafError('Variable substitution failure %r' % lst)
+if platform.system() == 'Windows':
+    prefix_values['prefix'][1] = '${PROGRAMFILES}/${APPNAME}'
+    
+    option_values['bindir'][1] = '${EXEC_PREFIX}'
+    option_values['sysconfdir'][1] = '${PREFIX}'
+    option_values['datarootdir'][1] = '${PREFIX}'
+    option_values['datadir'][1] = '${DATAROOTDIR}/data'
+    option_values['docdir'][1] = '${DATAROOTDIR}/doc'
+    del option_values['oldincludedir']
+    del option_values['mandir']
 
 def options(opt):
     inst_dir = opt.add_option_group(
@@ -77,24 +71,75 @@
         'be specified using the "--prefix" option, for example '
         '"--prefix=$HOME"'
     )
-    for k in ('--prefix', '--destdir'):
-        option = opt.parser.get_option(k)
-        if option:
-            opt.parser.remove_option(k)
-            inst_dir.add_option(option)
-    inst_dir.add_option(
-        '--exec-prefix',
-        help='installation prefix [Default: ${PREFIX}]',
-        default=inst_dir.defaults['prefix'],
-        dest='EXEC_PREFIX',
-    )
-    dirs_options = opt.add_option_group(
+    help_template = '{0} [Default: %default]'
+    for prefix_name in prefix_values.keys():
+        # Remove any of the core WAF options if they conflict with our options.
+        if opt.parser.get_option(prefix_name):
+            opt.parser.remove_option(prefix_name)
+        help, default = prefix_values[prefix_name]
+        option_name = '--' + prefix_name
+        inst_dir.add_option(option_name, help=help_template.format(help),
+                            default=default, dest=prefix_name)
+    
+    # Move the --destdir option to the inst_dir group for consistency.
+    destdir_option = opt.parser.get_option('destdir')
+    if destdir_option:
+        opt.parser.remove_option('destdir')
+        inst_dir.add_option(destdir_option)
+    
+    predef_dirs = opt.add_option_group(
         'Pre-defined installation directories',
         ''
     )
     for name in option_values:
         help, default = option_values[name]
         option_name = '--' + name
-        str_help = '{0} [Default: %default]'.format(help)
-        dirs_options.add_option(option_name, help=str_help, default=default,
-                                dest=name.upper())
+        predef_dirs.add_option(option_name, help=help_template.format(help),
+                               default=default, dest=name)
+
+def configure(conf):
+    def set_env_paths(path_values):
+        errors_raised = False
+        index = 0
+        print(path_values.keys(), path_values)
+        for option_name in path_values.keys():
+            help, default = path_values[option_name]
+            upper_option_name = option_name.upper()
+            try:
+                env[upper_option_name] = Utils.subst_vars(
+                    getattr(conf.options, option_name),
+                    env,
+                )
+            except TypeError:
+                errors_raised = True
+            env['INSTALL_PATHS'].append(upper_option_name)
+        if errors_raised:
+            failed_path_names = [name for name in option_values if
+                                 upper_option_name not in env]
+        else:
+            failed_path_names = []
+        return failed_path_names
+    
+    env = conf.env
+    if platform.system() == 'Windows':
+        try:
+            env['PROGRAMFILES'] = os.environ['PROGRAMFILES']
+        except KeyError:
+            Logs.warn('PROGRAMFILES environmental variable is not set')
+            if re.search(r'\$\{PROGRAMFILES\}', conf.options.prefix):
+                Logs.error(
+                    'unable to find path to Program Files direcotry, please '
+                    'set the PROGRAMFILES environmental variable or '
+                    'override installation prefix with --prefix'
+                )
+    if 'APPNAME' not in env:
+        env['APPNAME'] = Context.g_module.APPNAME
+    env['INSTALL_PATHS'] = []
+    failed_path_names = []
+    failed_path_names.extend(set_env_paths(prefix_values))
+    failed_path_names.extend(set_env_paths(option_values))
+    if failed_path_names:
+        error_message = 'failed to expand install paths {0!r}'
+        raise Errors.ConfigurationError(
+            error_message.format(failed_path_names)
+        )
--- a/wscript	Fri Jun 10 11:57:39 2011 -1000
+++ b/wscript	Wed Jun 15 13:21:25 2011 -1000
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # encoding: utf-8
-import sys
 import os
+import platform
 
 APPNAME = 'parpg'
 VERSION = '0.2.0'
@@ -28,19 +28,22 @@
         min_python_version = (2, 6)
     cnf.check_python_version(min_python_version)
     
-    cnf.env['FIFEPATH'] = \
-        os.path.abspath(os.path.expanduser(cnf.options.fifepath)) or \
-        cnf.env['PYTHONDIR']
+    if not cnf.options.fifepath:
+        cnf.env['FIFEPATH'] = os.path.abspath(
+            os.path.expanduser(cnf.options.fifepath)
+        )
+    else:
+        cnf.env['FIFEPATH'] = cnf.env['PYTHONDIR']
 
 def build(bld):
     subst_vars = _get_subst_vars(bld)
     
-    if sys.platform == 'Windows':
-        launcher_template = 'bin/parpg.bat.in'
-        launcher = 'parpg.bat'
+    if platform.system() == 'Windows':
+        launcher_template = bld.path.find_node('bin/parpg.bat.in')
+        launcher = bld.path.find_or_declare('parpg.bat')
     else:
-        launcher_template = 'bin/parpg.sh.in'
-        launcher = 'parpg'
+        launcher_template = bld.path.find_node('bin/parpg.sh.in')
+        launcher = bld.path.find_or_declare('parpg')
     args = dict(
         features='subst',
         source=launcher_template,
@@ -59,8 +62,8 @@
     
     args = dict(
         features='subst',
-        source='parpg.cfg.in',
-        target='parpg.cfg',
+        source=bld.path.find_node('parpg.cfg.in'),
+        target=bld.path.find_or_declare('parpg.cfg'),
         install_path='${SYSCONFDIR}',
         chmod=0644,
     )
@@ -71,6 +74,7 @@
         files=bld.path.find_node('data').ant_glob('**/*'),
         dest='${DATADIR}',
         relative_trick=True,
+        cwd=bld.path.find_node('data'),
         chmod=0644,
     )