comparison SConstruct @ 11:4706e0194af3

Various improvements to the build process including support for self-contained builds. * Note that despite all of these changes PARPG still does not run because asset paths are not standardized, * Modified the SCons script so that by default running `scons` with no arguments creates a self-contained "build" under a build subdirectory to make in-source testing easier. To install PARPG, use `scons install` instead. * Got rid of the binary launcher and replaced it with a shell script for unix and a batch script for Windows (batch script is untested). The binary turned out to be too much trouble to maintain. * Modified the parpg.settings module and parpg.main entry script so that PARPG searches through several default search paths for configuration file(s). PARPG thus no longer crashes if it can't find a configuration file in any particular search path, but will crash it if can't find any configuration files. * Paths supplied to parpg.main are now appended as search paths for the configuration file(s). * Changed the default configuration file name to "parpg.cfg" to simplify searches. * Created the site_scons directory tree where SCons extensions and tools should be placed. * Created a new SCons builder, CopyRecurse, which can copy only certain files and folders from a directory tree using filters (files and folders that start with a leading dot "." e.g. ".svn" are ignored by default). * Added the CPython SCons tool (stands for Compile-Python - I didn't name it!), which provides the InstallPython builder for pre-compiling python sources before they are installed. However, it is currently broken and only installs the python sources.
author M. George Hansen <technopolitica@gmail.com>
date Tue, 31 May 2011 02:46:20 -0700
parents dd4ed4945411
children d60f1dab8469
comparison
equal deleted inserted replaced
10:5deaf494934a 11:4706e0194af3
1 import sys 1 import sys
2 import os 2 import os
3 import platform 3 import platform
4 import compileall 4 import compileall
5 import fnmatch 5 import fnmatch
6 from copy import copy
6 from collections import Sequence 7 from collections import Sequence
7 from types import StringType 8 from types import StringType
8 from multiprocessing import cpu_count 9 from multiprocessing import cpu_count
9 10
10 from SCons.Util import is_Sequence, is_Dict 11 from SCons.Util import is_Sequence, is_Dict
12 from SCons.Tool.install import copyFunc
13
14 #def recursive_glob(topdir_path, include_pattern='*', exclude_pattern=''):
15 # """
16 # Locate all files matching supplied filename pattern in and below
17 # supplied root directory.
18 # """
19 # for dir_path, subdir_names, file_names in \
20 # os.walk(os.path.abspath(topdir_path)):
21 # for file_name in fnmatch.filter(file_names, include_pattern):
22 # if not fnmatch.fnmatch(file_name, exclude_pattern):
23 # file_path = os.path.join(dir_path, file_name)
24 # yield File(file_path)
25 # for subdir_name in copy(subdir_names):
26 # if not fnmatch.fnmatch(subdir_name, include_pattern) or \
27 # fnmatch.fnmatch(subdir_name, exclude_pattern):
28 # subdir_names.remove(subdir_name)
29 # else:
30 # subdir_path = os.path.join(dir_path, subdir_name)
11 31
12 def InstallChmod(env, dest, source, mode): 32 def InstallChmod(env, dest, source, mode):
13 targets = env.Install(dest, source) 33 targets = env.Install(dest, source)
14 for target in targets: 34 for target in targets:
15 env.AddPostAction(target, Chmod(target, mode)) 35 env.AddPostAction(target, Chmod(target, mode))
33 # File... (Note: Yes this can happen!) 53 # File... (Note: Yes this can happen!)
34 error_message = \ 54 error_message = \
35 'expected entry to be a Dir or a File, but got {0!r}' 55 'expected entry to be a Dir or a File, but got {0!r}'
36 raise ValueError(error_message.format(entry)) 56 raise ValueError(error_message.format(entry))
37 targets.append(target) 57 targets.append(target)
38 return targets
39
40 def InstallPyPackages(env, dest, source, compile=True):
41 # Remove all existing *.pyc and *.pyo files for a sanitary install
42 # environment.
43 def _remove_compiled_modules(path):
44 for dir_path, dir_names, file_names in os.walk(path):
45 for file_name in fnmatch.filter(file_names, '*.py[co]'):
46 file_path = os.path.join(dir_path, file_name)
47 os.remove(file_path)
48
49 def _add_targets(path):
50 for dir_path, dir_names, file_names in os.walk(path):
51 for file_name in fnmatch.filter(file_names, '*.py[co]'):
52 file_path = os.path.join(dir_path, file_name)
53 env.Clean(path, file_path)
54 source_file_path = file_path.rstrip('oc')
55 env.Depends(file_path, source_file_path)
56
57 if not isinstance(source, Sequence) or isinstance(source, StringType):
58 source = [source]
59 for dir in source:
60 _remove_compiled_modules(str(dir))
61 if compile:
62 compileall.compile_dir(str(dir), ddir=str(dest), force=True)
63 _add_targets(str(dir))
64 targets = env.InstallReadOnly(dest, source)
65 return targets 58 return targets
66 59
67 def SubstfileEscape(env, *args, **kwargs): 60 def SubstfileEscape(env, *args, **kwargs):
68 subst_dict = kwargs.get('SUBST_DICT') or env.get('SUBST_DICT') 61 subst_dict = kwargs.get('SUBST_DICT') or env.get('SUBST_DICT')
69 escape_sequences = kwargs.get('ESCAPE_SEQUENCES') or env.get('ESCAPE_SEQUENCES') 62 escape_sequences = kwargs.get('ESCAPE_SEQUENCES') or env.get('ESCAPE_SEQUENCES')
85 return target 78 return target
86 79
87 AddMethod(Environment, InstallChmod) 80 AddMethod(Environment, InstallChmod)
88 AddMethod(Environment, InstallExecutable) 81 AddMethod(Environment, InstallExecutable)
89 AddMethod(Environment, InstallReadOnly) 82 AddMethod(Environment, InstallReadOnly)
90 AddMethod(Environment, InstallPyPackages)
91 AddMethod(Environment, SubstfileEscape) 83 AddMethod(Environment, SubstfileEscape)
92 84
93 EnsurePythonVersion(2, 6) 85 EnsurePythonVersion(2, 6)
94 86
95 AddOption( 87 AddOption(
267 ) 259 )
268 260
269 python_version_tuple = platform.python_version_tuple() 261 python_version_tuple = platform.python_version_tuple()
270 262
271 environment = Environment( 263 environment = Environment(
272 tools=['default', 'textfile', 'packaging'], 264 tools=['default', 'cpython', 'copyrecurse', 'textfile', 'packaging'],
273 variables=variables, 265 variables=variables,
274 PROJECT_NAME='parpg', 266 PROJECT_NAME='parpg',
275 PROJECT_VERSION_MAJOR=0, 267 PROJECT_VERSION_MAJOR=0,
276 PROJECT_VERSION_MINOR=2, 268 PROJECT_VERSION_MINOR=2,
277 PROJECT_VERSION_PATCH=0, 269 PROJECT_VERSION_PATCH=0,
284 PY_VERSION_PATCH=python_version_tuple[2], 276 PY_VERSION_PATCH=python_version_tuple[2],
285 PY_VERSION_SHORT='${PY_VERSION_MAJOR}.${PY_VERSION_MINOR}', 277 PY_VERSION_SHORT='${PY_VERSION_MAJOR}.${PY_VERSION_MINOR}',
286 PY_VERSION_LONG='${PY_VERSION_MAJOR}.${PY_VERSION_MINOR}.' 278 PY_VERSION_LONG='${PY_VERSION_MAJOR}.${PY_VERSION_MINOR}.'
287 '${PY_VERSION_PATCH}', 279 '${PY_VERSION_PATCH}',
288 PY_LIB_NAME=PY_LIB_NAME, 280 PY_LIB_NAME=PY_LIB_NAME,
289 PY_PACKAGES=[], 281 BUILD_DIR='build',
290 CONFIG_FILES=[],
291 DATA_FILES=[],
292 EXECUTABLES=[],
293 ) 282 )
294 if environment['DEBUG']: 283 if environment['DEBUG']:
295 if platform_name == 'Windows': 284 if platform_name == 'Windows':
296 environment['CCPDBFLAGS'] = '/Z7 /Od' 285 environment['CCPDBFLAGS'] = '/Z7 /Od'
297 environment.AppendUnique(LINKFLAGS='/DEBUG') 286 environment.AppendUnique(LINKFLAGS='/DEBUG')
302 291
303 config_dict = [('@{0}@'.format(key), environment.Dictionary()[key]) for key in 292 config_dict = [('@{0}@'.format(key), environment.Dictionary()[key]) for key in
304 ('PREFIX', 'LIB_DIR', 'PY_PACKAGES_DIR', 'BIN_DIR', 293 ('PREFIX', 'LIB_DIR', 'PY_PACKAGES_DIR', 'BIN_DIR',
305 'SYS_CONF_DIR', 'DATA_DIR', 'PYTHON')] 294 'SYS_CONF_DIR', 'DATA_DIR', 'PYTHON')]
306 295
307 install_py_packages = environment.InstallPyPackages( 296 copy_py_packages = environment.Install(
297 '$BUILD_DIR',
298 'src/parpg',
299 )
300 install_py_packages = environment.InstallPython(
308 '$PY_PACKAGES_DIR', 301 '$PY_PACKAGES_DIR',
309 'src/parpg', 302 copy_py_packages,
310 compile=GetOption('compile'), 303 PYTHON_COMPILE=GetOption('compile'),
304 )
305
306 copy_data = environment.CopyRecurse(
307 '$BUILD_DIR',
308 'data',
311 ) 309 )
312 310
313 subst_config_file = environment.Substfile( 311 subst_config_file = environment.Substfile(
314 'system.cfg.in', 312 '$BUILD_DIR/parpg.cfg',
315 SUBST_DICT=config_dict 313 'parpg.cfg.in',
314 SUBST_DICT=config_dict,
316 ) 315 )
317 install_config_files = environment.InstallChmod( 316 install_config_files = environment.InstallChmod(
318 '$SYS_CONF_DIR', 317 '$SYS_CONF_DIR',
319 subst_config_file, 318 subst_config_file,
320 mode=0755 319 mode=0755,
321 ) 320 )
322 Requires(install_config_files, subst_config_file) 321 Requires(install_config_files, subst_config_file)
323 322
324 install_data = environment.InstallReadOnly( 323 if platform_name == 'Windows':
325 '$DATA_DIR', 324 launcher_name = 'parpg.bat'
326 Glob('data/*'), 325 else:
327 ) 326 launcher_name = 'parpg.sh'
328 327 # FIXME M. George Hansen 2011-05-20: Do any other sequences need to be escaped?
329 # FIXME M. George Hansen 2011-05-20: Do any other sequences need to be escaped 328 launcher = environment.SubstfileEscape(
330 # in a C string? 329 'build/$LAUNCHER_NAME',
331 executable_source = environment.SubstfileEscape( 330 'bin/${LAUNCHER_NAME}.in',
332 'bin/parpg.c.in', 331 LAUNCHER_NAME=launcher_name,
333 SUBST_DICT=config_dict, 332 SUBST_DICT=config_dict,
334 ESCAPE_SEQUENCES={'\\': r'\\\\', '"': r'\\"'}, 333 ESCAPE_SEQUENCES={'\\': r'\\\\', '"': r'\\"'},
335 ) 334 )
336 build_executable = environment.Program(
337 executable_source,
338 LIBS=['$PY_LIB_NAME'],
339 LIBPATH='$PY_LIB_DIR',
340 CPPPATH=['$PY_HEADERS_DIR'],
341 )
342 # Clean up any files created by the MSVC compiler on Windows.
343 if platform_name == 'Windows': 335 if platform_name == 'Windows':
344 environment.Clean(build_executable, 'bin/parpg.ilk') 336 install_launcher = environment.InstallExecutable(
345 environment.Clean(build_executable, 'bin/parpg.pdb') 337 '$BIN_DIR',
346 install_executable = environment.InstallExecutable( 338 launcher,
347 '$BIN_DIR', 339 )
348 build_executable, 340 else:
349 ) 341 # Remove the .sh suffix, since it isn't needed on unix platforms.
342 install_launcher = environment.InstallAs(
343 '$BIN_DIR/parpg',
344 launcher,
345 )
350 346
351 # TODO M. George Hansen 2011-05-12: Implement package builder. 347 # TODO M. George Hansen 2011-05-12: Implement package builder.
352 #package = environment.Package( 348 #package = environment.Package(
353 # NAME='parpg', 349 # NAME='parpg',
354 # VERSION='0.2.0', 350 # VERSION='0.2.0',
357 # SUMMARY='', 353 # SUMMARY='',
358 # DESCRIPTION='', 354 # DESCRIPTION='',
359 # X_RPM_GROUP='Application/parpg', 355 # X_RPM_GROUP='Application/parpg',
360 #) 356 #)
361 357
362 build = Alias('build', [build_executable]) 358 build = Alias('build', [launcher, subst_config_file, copy_py_packages,
363 install = Alias('install', [build, install_executable, install_py_packages, 359 copy_data])
364 install_config_files, install_data]) 360 install = Alias('install', [build, install_launcher, install_py_packages,
365 361 install_config_files])
366 Default(install) 362
363 Default(build)