More doc infrastructure enhancements towards putting the function
[scons.git] / SConstruct
1 #
2 # SConstruct file to build scons packages during development.
3 #
4 # See the README file for an overview of how SCons is built and tested.
5 #
6
7 # When this gets changed, you must also change the copyright_years string
8 # in QMTest/TestSCons.py so the test scripts look for the right string.
9 copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010'
10
11 # This gets inserted into the man pages to reflect the month of release.
12 month_year = 'January 2010'
13
14 #
15 # __COPYRIGHT__
16 #
17 # Permission is hereby granted, free of charge, to any person obtaining
18 # a copy of this software and associated documentation files (the
19 # "Software"), to deal in the Software without restriction, including
20 # without limitation the rights to use, copy, modify, merge, publish,
21 # distribute, sublicense, and/or sell copies of the Software, and to
22 # permit persons to whom the Software is furnished to do so, subject to
23 # the following conditions:
24 #
25 # The above copyright notice and this permission notice shall be included
26 # in all copies or substantial portions of the Software.
27 #
28 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
29 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
30 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 #
36
37 import distutils.util
38 import fnmatch
39 import os
40 import os.path
41 import re
42 import stat
43 import string
44 import sys
45 import tempfile
46
47 project = 'scons'
48 default_version = '1.2.0'
49 copyright = "Copyright (c) %s The SCons Foundation" % copyright_years
50
51 platform = distutils.util.get_platform()
52
53 SConsignFile()
54
55 #
56 # An internal "whereis" routine to figure out if a given program
57 # is available on this system.
58 #
59 def whereis(file):
60     exts = ['']
61     if platform == "win32":
62         exts += ['.exe']
63     for dir in string.split(os.environ['PATH'], os.pathsep):
64         f = os.path.join(dir, file)
65         for ext in exts:
66             f_ext = f + ext
67             if os.path.isfile(f_ext):
68                 try:
69                     st = os.stat(f_ext)
70                 except:
71                     continue
72                 if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
73                     return f_ext
74     return None
75
76 #
77 # We let the presence or absence of various utilities determine whether
78 # or not we bother to build certain pieces of things.  This should allow
79 # people to still do SCons packaging work even if they don't have all
80 # of the utilities installed (e.g. RPM).
81 #
82 dh_builddeb = whereis('dh_builddeb')
83 fakeroot = whereis('fakeroot')
84 gzip = whereis('gzip')
85 rpmbuild = whereis('rpmbuild') or whereis('rpm')
86 hg = os.path.exists('.hg') and whereis('hg')
87 svn = os.path.exists('.svn') and whereis('svn')
88 unzip = whereis('unzip')
89 zip = whereis('zip')
90
91 #
92 # Now grab the information that we "build" into the files.
93 #
94 date = ARGUMENTS.get('DATE')
95 if not date:
96     import time
97     date = time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(time.time()))
98
99 developer = ARGUMENTS.get('DEVELOPER')
100 if not developer:
101     for variable in ['USERNAME', 'LOGNAME', 'USER']:
102         developer = os.environ.get(variable)
103         if developer:
104             break
105
106 build_system = ARGUMENTS.get('BUILD_SYSTEM')
107 if not build_system:
108     import socket
109     build_system = string.split(socket.gethostname(), '.')[0]
110
111 version = ARGUMENTS.get('VERSION', '')
112 if not version:
113     version = default_version
114
115 hg_status_lines = []
116 svn_status_lines = []
117
118 if hg:
119     cmd = "%s status --all 2> /dev/null" % hg
120     hg_status_lines = os.popen(cmd, "r").readlines()
121
122 if svn:
123     cmd = "%s status --verbose 2> /dev/null" % svn
124     svn_status_lines = os.popen(cmd, "r").readlines()
125
126 revision = ARGUMENTS.get('REVISION', '')
127 def generate_build_id(revision):
128     return revision
129
130 if not revision and hg:
131     hg_heads = os.popen("%s heads 2> /dev/null" % hg, "r").read()
132     cs = re.search('changeset:\s+(\S+)', hg_heads)
133     if cs:
134         revision = cs.group(1)
135         b = re.search('branch:\s+(\S+)', hg_heads)
136         if b:
137             revision = b.group(1) + ':' + revision
138         def generate_build_id(revision):
139             result = revision
140             if filter(lambda l: l[0] in 'AMR!', hg_status_lines):
141                 result = result + '[MODIFIED]'
142             return result
143
144 if not revision and svn:
145     svn_info = os.popen("%s info 2> /dev/null" % svn, "r").read()
146     m = re.search('Revision: (\d+)', svn_info)
147     if m:
148         revision = m.group(1)
149         def generate_build_id(revision):
150             result = 'r' + revision
151             if filter(lambda l: l[0] in 'ACDMR', svn_status_lines):
152                 result = result + '[MODIFIED]'
153             return result
154
155 checkpoint = ARGUMENTS.get('CHECKPOINT', '')
156 if checkpoint:
157     if checkpoint == 'd':
158         import time
159         checkpoint = time.strftime('d%Y%m%d', time.localtime(time.time()))
160     elif checkpoint == 'r':
161         checkpoint = 'r' + revision
162     version = version + '.' + checkpoint
163
164 build_id = ARGUMENTS.get('BUILD_ID')
165 if build_id is None:
166     if revision:
167         build_id = generate_build_id(revision)
168     else:
169         build_id = ''
170
171 python_ver = sys.version[0:3]
172
173 # Re-exporting LD_LIBRARY_PATH is necessary if the Python version was
174 # built with the --enable-shared option.
175
176 ENV = { 'PATH' : os.environ['PATH'] }
177 for key in ['LOGNAME', 'PYTHONPATH', 'LD_LIBRARY_PATH']:
178     if os.environ.has_key(key):
179         ENV[key] = os.environ[key]
180
181 build_dir = ARGUMENTS.get('BUILDDIR', 'build')
182 if not os.path.isabs(build_dir):
183     build_dir = os.path.normpath(os.path.join(os.getcwd(), build_dir))
184
185 command_line_variables = [
186     ("BUILDDIR=",       "The directory in which to build the packages.  " +
187                         "The default is the './build' subdirectory."),
188
189     ("BUILD_ID=",       "An identifier for the specific build." +
190                         "The default is the Subversion revision number."),
191
192     ("BUILD_SYSTEM=",   "The system on which the packages were built.  " +
193                         "The default is whatever hostname is returned " +
194                         "by socket.gethostname()."),
195
196     ("CHECKPOINT=",     "The specific checkpoint release being packaged, " +
197                         "which will be appended to the VERSION string.  " +
198                         "A value of CHECKPOINT=d will generate a string " +
199                         "of 'd' plus today's date in the format YYYMMDD.  " +
200                         "A value of CHECKPOINT=r will generate a " +
201                         "string of 'r' plus the Subversion revision " +
202                         "number.  Any other CHECKPOINT= string will be " +
203                         "used as is.  There is no default value."),
204
205     ("DATE=",           "The date string representing when the packaging " +
206                         "build occurred.  The default is the day and time " +
207                         "the SConstruct file was invoked, in the format " +
208                         "YYYY/MM/DD HH:MM:SS."),
209
210     ("DEVELOPER=",      "The developer who created the packages.  " +
211                         "The default is the first set environment " +
212                         "variable from the list $USERNAME, $LOGNAME, $USER."),
213
214     ("REVISION=",       "The revision number of the source being built.  " +
215                         "The default is the Subversion revision returned " +
216                         "'svn info', with an appended string of " +
217                         "'[MODIFIED]' if there are any changes in the " +
218                         "working copy."),
219
220     ("VERSION=",        "The SCons version being packaged.  The default " +
221                         "is the hard-coded value '%s' " % default_version +
222                         "from this SConstruct file."),
223 ]
224
225 Default('.', build_dir)
226
227 packaging_flavors = [
228     ('deb',             "A .deb package.  (This is currently not supported.)"),
229
230     ('rpm',             "A RedHat Package Manager file."),
231
232     ('tar-gz',          "The normal .tar.gz file for end-user installation."),
233
234     ('src-tar-gz',      "A .tar.gz file containing all the source " +
235                         "(including tests and documentation)."),
236
237     ('local-tar-gz',    "A .tar.gz file for dropping into other software " +
238                         "for local use."),
239
240     ('zip',             "The normal .zip file for end-user installation."),
241
242     ('src-zip',         "A .zip file containing all the source " +
243                         "(including tests and documentation)."),
244
245     ('local-zip',       "A .zip file for dropping into other software " +
246                         "for local use."),
247 ]
248
249 test_deb_dir          = os.path.join(build_dir, "test-deb")
250 test_rpm_dir          = os.path.join(build_dir, "test-rpm")
251 test_tar_gz_dir       = os.path.join(build_dir, "test-tar-gz")
252 test_src_tar_gz_dir   = os.path.join(build_dir, "test-src-tar-gz")
253 test_local_tar_gz_dir = os.path.join(build_dir, "test-local-tar-gz")
254 test_zip_dir          = os.path.join(build_dir, "test-zip")
255 test_src_zip_dir      = os.path.join(build_dir, "test-src-zip")
256 test_local_zip_dir    = os.path.join(build_dir, "test-local-zip")
257
258 unpack_tar_gz_dir     = os.path.join(build_dir, "unpack-tar-gz")
259 unpack_zip_dir        = os.path.join(build_dir, "unpack-zip")
260
261 if platform == "win32":
262     tar_hflag = ''
263     python_project_subinst_dir = os.path.join("Lib", "site-packages", project)
264     project_script_subinst_dir = 'Scripts'
265 else:
266     tar_hflag = 'h'
267     python_project_subinst_dir = os.path.join("lib", project)
268     project_script_subinst_dir = 'bin'
269
270
271
272 import textwrap
273
274 indent_fmt = '  %-26s  '
275
276 Help("""\
277 The following aliases build packages of various types, and unpack the
278 contents into build/test-$PACKAGE subdirectories, which can be used by the
279 runtest.py -p option to run tests against what's been actually packaged:
280
281 """)
282
283 aliases = packaging_flavors + [('doc', 'The SCons documentation.')]
284 aliases.sort()
285
286 for alias, help_text in aliases:
287     tw = textwrap.TextWrapper(
288         width = 78,
289         initial_indent = indent_fmt % alias,
290         subsequent_indent = indent_fmt % '' + '  ',
291     )
292     Help(tw.fill(help_text) + '\n')
293
294 Help("""
295 The following command-line variables can be set:
296
297 """)
298
299 for variable, help_text in command_line_variables:
300     tw = textwrap.TextWrapper(
301         width = 78,
302         initial_indent = indent_fmt % variable,
303         subsequent_indent = indent_fmt % '' + '  ',
304     )
305     Help(tw.fill(help_text) + '\n')
306
307
308
309 zcat = 'gzip -d -c'
310
311 #
312 # Figure out if we can handle .zip files.
313 #
314 zipit = None
315 unzipit = None
316 try:
317     import zipfile
318
319     def zipit(env, target, source):
320         print "Zipping %s:" % str(target[0])
321         def visit(arg, dirname, names):
322             for name in names:
323                 path = os.path.join(dirname, name)
324                 if os.path.isfile(path):
325                     arg.write(path)
326         zf = zipfile.ZipFile(str(target[0]), 'w')
327         olddir = os.getcwd()
328         os.chdir(env['CD'])
329         try: os.path.walk(env['PSV'], visit, zf)
330         finally: os.chdir(olddir)
331         zf.close()
332
333     def unzipit(env, target, source):
334         print "Unzipping %s:" % str(source[0])
335         zf = zipfile.ZipFile(str(source[0]), 'r')
336         for name in zf.namelist():
337             dest = os.path.join(env['UNPACK_ZIP_DIR'], name)
338             dir = os.path.dirname(dest)
339             try:
340                 os.makedirs(dir)
341             except:
342                 pass
343             print dest,name
344             # if the file exists, then delete it before writing
345             # to it so that we don't end up trying to write to a symlink:
346             if os.path.isfile(dest) or os.path.islink(dest):
347                 os.unlink(dest)
348             if not os.path.isdir(dest):
349                 open(dest, 'wb').write(zf.read(name))
350
351 except:
352     if unzip and zip:
353         zipit = "cd $CD && $ZIP $ZIPFLAGS $( ${TARGET.abspath} $) $PSV"
354         unzipit = "$UNZIP $UNZIPFLAGS $SOURCES"
355
356 def SCons_revision(target, source, env):
357     """Interpolate specific values from the environment into a file.
358
359     This is used to copy files into a tree that gets packaged up
360     into the source file package.
361     """
362     t = str(target[0])
363     s = source[0].rstr()
364     contents = open(s, 'rb').read()
365     # Note:  We construct the __*__ substitution strings here
366     # so that they don't get replaced when this file gets
367     # copied into the tree for packaging.
368     contents = string.replace(contents, '__BUILD'     + '__', env['BUILD'])
369     contents = string.replace(contents, '__BUILDSYS'  + '__', env['BUILDSYS'])
370     contents = string.replace(contents, '__COPYRIGHT' + '__', env['COPYRIGHT'])
371     contents = string.replace(contents, '__DATE'      + '__', env['DATE'])
372     contents = string.replace(contents, '__DEVELOPER' + '__', env['DEVELOPER'])
373     contents = string.replace(contents, '__FILE'      + '__', str(source[0]))
374     contents = string.replace(contents, '__MONTH_YEAR'+ '__', env['MONTH_YEAR'])
375     contents = string.replace(contents, '__REVISION'  + '__', env['REVISION'])
376     contents = string.replace(contents, '__VERSION'   + '__', env['VERSION'])
377     contents = string.replace(contents, '__NULL'      + '__', '')
378     open(t, 'wb').write(contents)
379     os.chmod(t, os.stat(s)[0])
380
381 revbuilder = Builder(action = Action(SCons_revision,
382                                      varlist=['COPYRIGHT', 'VERSION']))
383
384 def soelim(target, source, env):
385     """
386     Interpolate files included in [gnt]roff source files using the
387     .so directive.
388
389     This behaves somewhat like the soelim(1) wrapper around groff, but
390     makes us independent of whether the actual underlying implementation
391     includes an soelim() command or the corresponding command-line option
392     to groff(1).  The key behavioral difference is that this doesn't
393     recursively include .so files from the include file.  Not yet, anyway.
394     """
395     t = str(target[0])
396     s = str(source[0])
397     dir, f = os.path.split(s)
398     tfp = open(t, 'w')
399     sfp = open(s, 'r')
400     for line in sfp.readlines():
401         if line[:4] in ['.so ', "'so "]:
402             sofile = os.path.join(dir, line[4:-1])
403             tfp.write(open(sofile, 'r').read())
404         else:
405             tfp.write(line)
406     sfp.close()
407     tfp.close()
408
409 def soscan(node, env, path):
410     c = node.get_text_contents()
411     return re.compile(r"^[\.']so\s+(\S+)", re.M).findall(c)
412
413 soelimbuilder = Builder(action = Action(soelim),
414                         source_scanner = Scanner(soscan))
415
416 # When copying local files from a Repository (Aegis),
417 # just make copies, don't symlink them.
418 SetOption('duplicate', 'copy')
419
420 env = Environment(
421                    ENV                 = ENV,
422
423                    BUILD               = build_id,
424                    BUILDDIR            = build_dir,
425                    BUILDSYS            = build_system,
426                    COPYRIGHT           = copyright,
427                    DATE                = date,
428                    DEVELOPER           = developer,
429                    DISTDIR             = os.path.join(build_dir, 'dist'),
430                    MONTH_YEAR          = month_year,
431                    REVISION            = revision,
432                    VERSION             = version,
433                    DH_COMPAT           = 2,
434
435                    TAR_HFLAG           = tar_hflag,
436
437                    ZIP                 = zip,
438                    ZIPFLAGS            = '-r',
439                    UNZIP               = unzip,
440                    UNZIPFLAGS          = '-o -d $UNPACK_ZIP_DIR',
441
442                    ZCAT                = zcat,
443
444                    RPMBUILD            = rpmbuild,
445                    RPM2CPIO            = 'rpm2cpio',
446
447                    TEST_DEB_DIR        = test_deb_dir,
448                    TEST_RPM_DIR        = test_rpm_dir,
449                    TEST_SRC_TAR_GZ_DIR = test_src_tar_gz_dir,
450                    TEST_SRC_ZIP_DIR    = test_src_zip_dir,
451                    TEST_TAR_GZ_DIR     = test_tar_gz_dir,
452                    TEST_ZIP_DIR        = test_zip_dir,
453
454                    UNPACK_TAR_GZ_DIR   = unpack_tar_gz_dir,
455                    UNPACK_ZIP_DIR      = unpack_zip_dir,
456
457                    BUILDERS            = { 'SCons_revision' : revbuilder,
458                                            'SOElim' : soelimbuilder },
459
460                    PYTHON              = '"%s"' % sys.executable,
461                    PYTHONFLAGS         = '-tt',
462                  )
463
464 Version_values = [Value(version), Value(build_id)]
465
466 #
467 # Define SCons packages.
468 #
469 # In the original, more complicated packaging scheme, we were going
470 # to have separate packages for:
471 #
472 #       python-scons    only the build engine
473 #       scons-script    only the script
474 #       scons           the script plus the build engine
475 #
476 # We're now only delivering a single "scons" package, but this is still
477 # "built" as two sub-packages (the build engine and the script), so
478 # the definitions remain here, even though we're not using them for
479 # separate packages.
480 #
481
482 python_scons = {
483         'pkg'           : 'python-' + project,
484         'src_subdir'    : 'engine',
485         'inst_subdir'   : os.path.join('lib', 'python1.5', 'site-packages'),
486         'rpm_dir'       : '/usr/lib/scons',
487
488         'debian_deps'   : [
489                             'debian/changelog',
490                             'debian/control',
491                             'debian/copyright',
492                             'debian/dirs',
493                             'debian/docs',
494                             'debian/postinst',
495                             'debian/prerm',
496                             'debian/rules',
497                           ],
498
499         'files'         : [ 'LICENSE.txt',
500                             'README.txt',
501                             'setup.cfg',
502                             'setup.py',
503                           ],
504
505         'filemap'       : {
506                             'LICENSE.txt' : '../LICENSE.txt'
507                           },
508
509         'buildermap'    : {},
510
511         'extra_rpm_files' : [],
512
513         'explicit_deps' : {
514                             'SCons/__init__.py' : Version_values,
515                           },
516 }
517
518 # Figure out the name of a .egg-info file that might be generated
519 # as part of the RPM package.  There are two complicating factors.
520 #
521 # First, the RPM spec file we generate will just execute "python", not
522 # necessarily the one in sys.executable.  If *that* version of python has
523 # a distutils that knows about Python eggs, then setup.py will generate a
524 # .egg-info file, so we have to execute any distutils logic in a subshell.
525 #
526 # Second, we can't just have the subshell check for the existence of the
527 # distutils.command.install_egg_info module and generate the expected
528 # file name by hand, the way we used to, because different systems can
529 # have slightly different .egg-info naming conventions.  (Specifically,
530 # Ubuntu overrides the default behavior to remove the Python version
531 # string from the .egg-info file name.)  The right way to do this is to
532 # actually call into the install_egg_info() class to have it generate
533 # the expected name for us.
534 #
535 # This is all complicated enough that we do it by writing an in-line
536 # script to a temporary file and then feeding it to a separate invocation
537 # of "python" to tell us the actual name of the generated .egg-info file.
538
539 print_egg_info_name = """
540 try:
541     from distutils.dist import Distribution
542     from distutils.command.install_egg_info import install_egg_info
543 except ImportError:
544     pass
545 else:
546     dist = Distribution({'name' : "scons", 'version' : '%s'})
547     i = install_egg_info(dist)
548     i.finalize_options()
549     import os.path
550     print os.path.split(i.outputs[0])[1]
551 """ % version
552
553 try:
554     fd, tfname = tempfile.mkstemp()
555     tfp = os.fdopen(fd, "w")
556     tfp.write(print_egg_info_name)
557     tfp.close()
558     egg_info_file = os.popen("python %s" % tfname).read()[:-1]
559     if egg_info_file:
560         python_scons['extra_rpm_files'].append(egg_info_file)
561 finally:
562     try:
563         os.unlink(tfname)
564     except EnvironmentError:
565         pass
566
567 #
568 # The original packaging scheme would have have required us to push
569 # the Python version number into the package name (python1.5-scons,
570 # python2.0-scons, etc.), which would have required a definition
571 # like the following.  Leave this here in case we ever decide to do
572 # this in the future, but note that this would require some modification
573 # to src/engine/setup.py before it would really work.
574 #
575 #python2_scons = {
576 #        'pkg'          : 'python2-' + project,
577 #        'src_subdir'   : 'engine',
578 #        'inst_subdir'  : os.path.join('lib', 'python2.2', 'site-packages'),
579 #
580 #        'debian_deps'  : [
581 #                            'debian/changelog',
582 #                            'debian/control',
583 #                            'debian/copyright',
584 #                            'debian/dirs',
585 #                            'debian/docs',
586 #                            'debian/postinst',
587 #                            'debian/prerm',
588 #                            'debian/rules',
589 #                          ],
590 #
591 #        'files'        : [
592 #                            'LICENSE.txt',
593 #                            'README.txt',
594 #                            'setup.cfg',
595 #                            'setup.py',
596 #                          ],
597 #        'filemap'      : {
598 #                            'LICENSE.txt' : '../LICENSE.txt',
599 #                          },
600 #        'buildermap'    : {},
601 #}
602 #
603
604 scons_script = {
605         'pkg'           : project + '-script',
606         'src_subdir'    : 'script',
607         'inst_subdir'   : 'bin',
608         'rpm_dir'       : '/usr/bin',
609
610         'debian_deps'   : [
611                             'debian/changelog',
612                             'debian/control',
613                             'debian/copyright',
614                             'debian/dirs',
615                             'debian/docs',
616                             'debian/postinst',
617                             'debian/prerm',
618                             'debian/rules',
619                           ],
620
621         'files'         : [
622                             'LICENSE.txt',
623                             'README.txt',
624                             'setup.cfg',
625                             'setup.py',
626                           ],
627
628         'filemap'       : {
629                             'LICENSE.txt'       : '../LICENSE.txt',
630                             'scons'             : 'scons.py',
631                             'sconsign'          : 'sconsign.py',
632                             'scons-time'        : 'scons-time.py',
633                            },
634
635         'buildermap'    : {},
636
637         'extra_rpm_files' : [
638                             'scons-' + version,
639                             'sconsign-' + version,
640                             'scons-time-' + version,
641                           ],
642
643         'explicit_deps' : {
644                             'scons'       : Version_values,
645                             'sconsign'    : Version_values,
646                           },
647 }
648
649 scons = {
650         'pkg'           : project,
651
652         'debian_deps'   : [
653                             'debian/changelog',
654                             'debian/control',
655                             'debian/copyright',
656                             'debian/dirs',
657                             'debian/docs',
658                             'debian/postinst',
659                             'debian/prerm',
660                             'debian/rules',
661                           ],
662
663         'files'         : [
664                             'CHANGES.txt',
665                             'LICENSE.txt',
666                             'README.txt',
667                             'RELEASE.txt',
668                             'os_spawnv_fix.diff',
669                             'scons.1',
670                             'sconsign.1',
671                             'scons-time.1',
672                             'script/scons.bat',
673                             #'script/scons-post-install.py',
674                             'setup.cfg',
675                             'setup.py',
676                           ],
677
678         'filemap'       : {
679                             'scons.1' : '$BUILDDIR/doc/man/scons.1',
680                             'sconsign.1' : '$BUILDDIR/doc/man/sconsign.1',
681                             'scons-time.1' : '$BUILDDIR/doc/man/scons-time.1',
682                           },
683
684         'buildermap'    : {
685                             'scons.1' : env.SOElim,
686                             'sconsign.1' : env.SOElim,
687                             'scons-time.1' : env.SOElim,
688                           },
689
690         'subpkgs'       : [ python_scons, scons_script ],
691
692         'subinst_dirs'  : {
693                              'python-' + project : python_project_subinst_dir,
694                              project + '-script' : project_script_subinst_dir,
695                            },
696 }
697
698 scripts = ['scons', 'sconsign', 'scons-time']
699
700 src_deps = []
701 src_files = []
702
703 for p in [ scons ]:
704     #
705     # Initialize variables with the right directories for this package.
706     #
707     pkg = p['pkg']
708     pkg_version = "%s-%s" % (pkg, version)
709
710     src = 'src'
711     if p.has_key('src_subdir'):
712         src = os.path.join(src, p['src_subdir'])
713
714     build = os.path.join(build_dir, pkg)
715
716     tar_gz = os.path.join(build, 'dist', "%s.tar.gz" % pkg_version)
717     platform_tar_gz = os.path.join(build,
718                                    'dist',
719                                    "%s.%s.tar.gz" % (pkg_version, platform))
720     zip = os.path.join(build, 'dist', "%s.zip" % pkg_version)
721     platform_zip = os.path.join(build,
722                                 'dist',
723                                 "%s.%s.zip" % (pkg_version, platform))
724     if platform == "win-amd64":
725         win32_exe = os.path.join(build, 'dist', "%s.win-amd64.exe" % pkg_version)
726     else:
727         win32_exe = os.path.join(build, 'dist', "%s.win32.exe" % pkg_version)
728
729     #
730     # Update the environment with the relevant information
731     # for this package.
732     #
733     # We can get away with calling setup.py using a directory path
734     # like this because we put a preamble in it that will chdir()
735     # to the directory in which setup.py exists.
736     #
737     setup_py = os.path.join(build, 'setup.py')
738     env.Replace(PKG = pkg,
739                 PKG_VERSION = pkg_version,
740                 SETUP_PY = '"%s"' % setup_py)
741     Local(setup_py)
742
743     #
744     # Read up the list of source files from our MANIFEST.in.
745     # This list should *not* include LICENSE.txt, MANIFEST,
746     # README.txt, or setup.py.  Make a copy of the list for the
747     # destination files.
748     #
749     manifest_in = File(os.path.join(src, 'MANIFEST.in')).rstr()
750     src_files = map(lambda x: x[:-1],
751                     open(manifest_in).readlines())
752     raw_files = src_files[:]
753     dst_files = src_files[:]
754     rpm_files = []
755
756     MANIFEST_in_list = []
757
758     if p.has_key('subpkgs'):
759         #
760         # This package includes some sub-packages.  Read up their
761         # MANIFEST.in files, and add them to our source and destination
762         # file lists, modifying them as appropriate to add the
763         # specified subdirs.
764         #
765         for sp in p['subpkgs']:
766             ssubdir = sp['src_subdir']
767             isubdir = p['subinst_dirs'][sp['pkg']]
768             MANIFEST_in = File(os.path.join(src, ssubdir, 'MANIFEST.in')).rstr()
769             MANIFEST_in_list.append(MANIFEST_in)
770             files = map(lambda x: x[:-1], open(MANIFEST_in).readlines())
771             raw_files.extend(files)
772             src_files.extend(map(lambda x, s=ssubdir: os.path.join(s, x), files))
773             for f in files:
774                 r = os.path.join(sp['rpm_dir'], f)
775                 rpm_files.append(r)
776                 if f[-3:] == ".py":
777                     rpm_files.append(r + 'c')
778             for f in sp.get('extra_rpm_files', []):
779                 r = os.path.join(sp['rpm_dir'], f)
780                 rpm_files.append(r)
781             files = map(lambda x, i=isubdir: os.path.join(i, x), files)
782             dst_files.extend(files)
783             for k, f in sp['filemap'].items():
784                 if f:
785                     k = os.path.join(ssubdir, k)
786                     p['filemap'][k] = os.path.join(ssubdir, f)
787             for f, deps in sp['explicit_deps'].items():
788                 f = os.path.join(build, ssubdir, f)
789                 env.Depends(f, deps)
790
791     #
792     # Now that we have the "normal" source files, add those files
793     # that are standard for each distribution.  Note that we don't
794     # add these to dst_files, because they don't get installed.
795     # And we still have the MANIFEST to add.
796     #
797     src_files.extend(p['files'])
798
799     #
800     # Now run everything in src_file through the sed command we
801     # concocted to expand __FILE__, __VERSION__, etc.
802     #
803     for b in src_files:
804         s = p['filemap'].get(b, b)
805         if not s[0] == '$' and not os.path.isabs(s):
806             s = os.path.join(src, s)
807         builder = p['buildermap'].get(b, env.SCons_revision)
808         x = builder(os.path.join(build, b), s)
809         Local(x)
810
811     #
812     # NOW, finally, we can create the MANIFEST, which we do
813     # by having Python spit out the contents of the src_files
814     # array we've carefully created.  After we've added
815     # MANIFEST itself to the array, of course.
816     #
817     src_files.append("MANIFEST")
818     MANIFEST_in_list.append(os.path.join(src, 'MANIFEST.in'))
819
820     def write_src_files(target, source, **kw):
821         global src_files
822         src_files.sort()
823         f = open(str(target[0]), 'wb')
824         for file in src_files:
825             f.write(file + "\n")
826         f.close()
827         return 0
828     env.Command(os.path.join(build, 'MANIFEST'),
829                 MANIFEST_in_list,
830                 write_src_files)
831
832     #
833     # Now go through and arrange to create whatever packages we can.
834     #
835     build_src_files = map(lambda x, b=build: os.path.join(b, x), src_files)
836     apply(Local, build_src_files, {})
837
838     distutils_formats = []
839
840     distutils_targets = [ win32_exe ]
841
842     dist_distutils_targets = env.Install('$DISTDIR', distutils_targets)
843     Local(dist_distutils_targets)
844     AddPostAction(dist_distutils_targets, Chmod(dist_distutils_targets, 0644))
845
846     if not gzip:
847         print "gzip not found in %s; skipping .tar.gz package for %s." % (os.environ['PATH'], pkg)
848     else:
849
850         distutils_formats.append('gztar')
851
852         src_deps.append(tar_gz)
853
854         distutils_targets.extend([ tar_gz, platform_tar_gz ])
855
856         dist_tar_gz             = env.Install('$DISTDIR', tar_gz)
857         dist_platform_tar_gz    = env.Install('$DISTDIR', platform_tar_gz)
858         Local(dist_tar_gz, dist_platform_tar_gz)
859         AddPostAction(dist_tar_gz, Chmod(dist_tar_gz, 0644))
860         AddPostAction(dist_platform_tar_gz, Chmod(dist_platform_tar_gz, 0644))
861
862         #
863         # Unpack the tar.gz archive created by the distutils into
864         # build/unpack-tar-gz/scons-{version}.
865         #
866         # We'd like to replace the last three lines with the following:
867         #
868         #       tar zxf $SOURCES -C $UNPACK_TAR_GZ_DIR
869         #
870         # but that gives heartburn to Cygwin's tar, so work around it
871         # with separate zcat-tar-rm commands.
872         #
873         unpack_tar_gz_files = map(lambda x, u=unpack_tar_gz_dir, pv=pkg_version:
874                                          os.path.join(u, pv, x),
875                                   src_files)
876         env.Command(unpack_tar_gz_files, dist_tar_gz, [
877                     Delete(os.path.join(unpack_tar_gz_dir, pkg_version)),
878                     "$ZCAT $SOURCES > .temp",
879                     "tar xf .temp -C $UNPACK_TAR_GZ_DIR",
880                     Delete(".temp"),
881         ])
882
883         #
884         # Run setup.py in the unpacked subdirectory to "install" everything
885         # into our build/test subdirectory.  The runtest.py script will set
886         # PYTHONPATH so that the tests only look under build/test-{package},
887         # and under etc (for the testing modules TestCmd.py, TestSCons.py,
888         # and unittest.py).  This makes sure that our tests pass with what
889         # we really packaged, not because of something hanging around in
890         # the development directory.
891         #
892         # We can get away with calling setup.py using a directory path
893         # like this because we put a preamble in it that will chdir()
894         # to the directory in which setup.py exists.
895         #
896         dfiles = map(lambda x, d=test_tar_gz_dir: os.path.join(d, x), dst_files)
897         env.Command(dfiles, unpack_tar_gz_files, [
898             Delete(os.path.join(unpack_tar_gz_dir, pkg_version, 'build')),
899             Delete("$TEST_TAR_GZ_DIR"),
900             '$PYTHON $PYTHONFLAGS "%s" install "--prefix=$TEST_TAR_GZ_DIR" --standalone-lib' % \
901                 os.path.join(unpack_tar_gz_dir, pkg_version, 'setup.py'),
902         ])
903
904         #
905         # Generate portage files for submission to Gentoo Linux.
906         #
907         gentoo = os.path.join(build, 'gentoo')
908         ebuild = os.path.join(gentoo, 'scons-%s.ebuild' % version)
909         digest = os.path.join(gentoo, 'files', 'digest-scons-%s' % version)
910         env.Command(ebuild, os.path.join('gentoo', 'scons.ebuild.in'), SCons_revision)
911         def Digestify(target, source, env):
912             import md5
913             def hexdigest(s):
914                 """Return a signature as a string of hex characters.
915                 """
916                 # NOTE:  This routine is a method in the Python 2.0 interface
917                 # of the native md5 module, but we want SCons to operate all
918                 # the way back to at least Python 1.5.2, which doesn't have it.
919                 h = string.hexdigits
920                 r = ''
921                 for c in s:
922                     i = ord(c)
923                     r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
924                 return r
925             src = source[0].rfile()
926             contents = open(str(src)).read()
927             sig = hexdigest(md5.new(contents).digest())
928             bytes = os.stat(str(src))[6]
929             open(str(target[0]), 'w').write("MD5 %s %s %d\n" % (sig,
930                                                                 src.name,
931                                                                 bytes))
932         env.Command(digest, tar_gz, Digestify)
933
934     if not zipit:
935         print "zip not found; skipping .zip package for %s." % pkg
936     else:
937
938         distutils_formats.append('zip')
939
940         src_deps.append(zip)
941
942         distutils_targets.extend([ zip, platform_zip ])
943
944         dist_zip            = env.Install('$DISTDIR', zip)
945         dist_platform_zip   = env.Install('$DISTDIR', platform_zip)
946         Local(dist_zip, dist_platform_zip)
947         AddPostAction(dist_zip, Chmod(dist_zip, 0644))
948         AddPostAction(dist_platform_zip, Chmod(dist_platform_zip, 0644))
949
950         #
951         # Unpack the zip archive created by the distutils into
952         # build/unpack-zip/scons-{version}.
953         #
954         unpack_zip_files = map(lambda x, u=unpack_zip_dir, pv=pkg_version:
955                                       os.path.join(u, pv, x),
956                                src_files)
957
958         env.Command(unpack_zip_files, dist_zip, [
959             Delete(os.path.join(unpack_zip_dir, pkg_version)),
960             unzipit,
961         ])
962
963         #
964         # Run setup.py in the unpacked subdirectory to "install" everything
965         # into our build/test subdirectory.  The runtest.py script will set
966         # PYTHONPATH so that the tests only look under build/test-{package},
967         # and under etc (for the testing modules TestCmd.py, TestSCons.py,
968         # and unittest.py).  This makes sure that our tests pass with what
969         # we really packaged, not because of something hanging around in
970         # the development directory.
971         #
972         # We can get away with calling setup.py using a directory path
973         # like this because we put a preamble in it that will chdir()
974         # to the directory in which setup.py exists.
975         #
976         dfiles = map(lambda x, d=test_zip_dir: os.path.join(d, x), dst_files)
977         env.Command(dfiles, unpack_zip_files, [
978             Delete(os.path.join(unpack_zip_dir, pkg_version, 'build')),
979             Delete("$TEST_ZIP_DIR"),
980             '$PYTHON $PYTHONFLAGS "%s" install "--prefix=$TEST_ZIP_DIR" --standalone-lib' % \
981                 os.path.join(unpack_zip_dir, pkg_version, 'setup.py'),
982         ])
983
984     if not rpmbuild:
985         msg = "@echo \"Warning:  Can not build 'rpm':  no rpmbuild utility found\""
986         AlwaysBuild(Alias('rpm', [], msg))
987     else:
988         topdir = os.path.join(build, 'build',
989                               'bdist.' + platform, 'rpm')
990
991         buildroot = os.path.join(build_dir, 'rpm-buildroot')
992
993         BUILDdir = os.path.join(topdir, 'BUILD', pkg + '-' + version)
994         RPMSdir = os.path.join(topdir, 'RPMS', 'noarch')
995         SOURCESdir = os.path.join(topdir, 'SOURCES')
996         SPECSdir = os.path.join(topdir, 'SPECS')
997         SRPMSdir = os.path.join(topdir, 'SRPMS')
998
999         specfile_in = os.path.join('rpm', "%s.spec.in" % pkg)
1000         specfile = os.path.join(SPECSdir, "%s-1.spec" % pkg_version)
1001         sourcefile = os.path.join(SOURCESdir, "%s.tar.gz" % pkg_version);
1002         noarch_rpm = os.path.join(RPMSdir, "%s-1.noarch.rpm" % pkg_version)
1003         src_rpm = os.path.join(SRPMSdir, "%s-1.src.rpm" % pkg_version)
1004
1005         def spec_function(target, source, env):
1006             """Generate the RPM .spec file from the template file.
1007
1008             This fills in the %files portion of the .spec file with a
1009             list generated from our MANIFEST(s), so we don't have to
1010             maintain multiple lists.
1011             """
1012             c = open(str(source[0]), 'rb').read()
1013             c = string.replace(c, '__VERSION' + '__', env['VERSION'])
1014             c = string.replace(c, '__RPM_FILES' + '__', env['RPM_FILES'])
1015             open(str(target[0]), 'wb').write(c)
1016
1017         rpm_files.sort()
1018         rpm_files_str = string.join(rpm_files, "\n") + "\n"
1019         rpm_spec_env = env.Clone(RPM_FILES = rpm_files_str)
1020         rpm_spec_action = Action(spec_function, varlist=['RPM_FILES'])
1021         rpm_spec_env.Command(specfile, specfile_in, rpm_spec_action)
1022
1023         env.InstallAs(sourcefile, tar_gz)
1024         Local(sourcefile)
1025
1026         targets = [ noarch_rpm, src_rpm ]
1027         cmd = "$RPMBUILD --define '_topdir $(%s$)' --buildroot %s -ba $SOURCES" % (topdir, buildroot)
1028         if not os.path.isdir(BUILDdir):
1029             cmd = ("$( mkdir -p %s; $)" % BUILDdir) + cmd
1030         t = env.Command(targets, specfile, cmd)
1031         env.Depends(t, sourcefile)
1032
1033         dist_noarch_rpm = env.Install('$DISTDIR', noarch_rpm)
1034         dist_src_rpm    = env.Install('$DISTDIR', src_rpm)
1035         Local(dist_noarch_rpm, dist_src_rpm)
1036         AddPostAction(dist_noarch_rpm, Chmod(dist_noarch_rpm, 0644))
1037         AddPostAction(dist_src_rpm, Chmod(dist_src_rpm, 0644))
1038
1039         dfiles = map(lambda x, d=test_rpm_dir: os.path.join(d, 'usr', x),
1040                      dst_files)
1041         env.Command(dfiles,
1042                     dist_noarch_rpm,
1043                     "$RPM2CPIO $SOURCES | (cd $TEST_RPM_DIR && cpio -id)")
1044
1045     if dh_builddeb and fakeroot:
1046         # Our Debian packaging builds directly into build/dist,
1047         # so we don't need to Install() the .debs.
1048         deb = os.path.join(build_dir, 'dist', "%s_%s-1_all.deb" % (pkg, version))
1049         for d in p['debian_deps']:
1050             b = env.SCons_revision(os.path.join(build, d), d)
1051             env.Depends(deb, b)
1052             Local(b)
1053         env.Command(deb, build_src_files, [
1054             "cd %s && fakeroot make -f debian/rules PYTHON=$PYTHON BUILDDEB_OPTIONS=--destdir=../../build/dist binary" % build,
1055                     ])
1056
1057         old = os.path.join('lib', 'scons', '')
1058         new = os.path.join('lib', 'python' + python_ver, 'site-packages', '')
1059         def xxx(s, old=old, new=new):
1060             if s[:len(old)] == old:
1061                 s = new + s[len(old):]
1062             return os.path.join('usr', s)
1063         dfiles = map(lambda x, t=test_deb_dir: os.path.join(t, x),
1064                      map(xxx, dst_files))
1065         env.Command(dfiles,
1066                     deb,
1067                     "dpkg --fsys-tarfile $SOURCES | (cd $TEST_DEB_DIR && tar -xf -)")
1068
1069
1070     #
1071     # Use the Python distutils to generate the appropriate packages.
1072     #
1073     commands = [
1074         Delete(os.path.join(build, 'build', 'lib')),
1075         Delete(os.path.join(build, 'build', 'scripts')),
1076     ]
1077
1078     if distutils_formats:
1079         commands.append(Delete(os.path.join(build,
1080                                             'build',
1081                                             'bdist.' + platform,
1082                                             'dumb')))
1083         for format in distutils_formats:
1084             commands.append("$PYTHON $PYTHONFLAGS $SETUP_PY bdist_dumb -f %s" % format)
1085
1086         commands.append("$PYTHON $PYTHONFLAGS $SETUP_PY sdist --formats=%s" %  \
1087                             string.join(distutils_formats, ','))
1088
1089     commands.append("$PYTHON $PYTHONFLAGS $SETUP_PY bdist_wininst")
1090
1091     env.Command(distutils_targets, build_src_files, commands)
1092
1093     #
1094     # Now create local packages for people who want to let people
1095     # build their SCons-buildable packages without having to
1096     # install SCons.
1097     #
1098     s_l_v = '%s-local-%s' % (pkg, version)
1099
1100     local = pkg + '-local'
1101     build_dir_local = os.path.join(build_dir, local)
1102     build_dir_local_slv = os.path.join(build_dir, local, s_l_v)
1103
1104     dist_local_tar_gz = os.path.join("$DISTDIR/%s.tar.gz" % s_l_v)
1105     dist_local_zip = os.path.join("$DISTDIR/%s.zip" % s_l_v)
1106     AddPostAction(dist_local_tar_gz, Chmod(dist_local_tar_gz, 0644))
1107     AddPostAction(dist_local_zip, Chmod(dist_local_zip, 0644))
1108
1109     commands = [
1110         Delete(build_dir_local),
1111         '$PYTHON $PYTHONFLAGS $SETUP_PY install "--install-script=%s" "--install-lib=%s" --no-install-man --no-compile --standalone-lib --no-version-script' % \
1112                                                 (build_dir_local, build_dir_local_slv),
1113     ]
1114
1115     for script in scripts:
1116         #commands.append("mv %s/%s %s/%s.py" % (local, script, local, script))
1117         local_script = os.path.join(build_dir_local, script)
1118         commands.append(Move(local_script + '.py', local_script))
1119
1120     rf = filter(lambda x: not x in scripts, raw_files)
1121     rf = map(lambda x, slv=s_l_v: os.path.join(slv, x), rf)
1122     for script in scripts:
1123         rf.append("%s.py" % script)
1124     local_targets = map(lambda x, s=build_dir_local: os.path.join(s, x), rf)
1125
1126     env.Command(local_targets, build_src_files, commands)
1127
1128     scons_LICENSE = os.path.join(build_dir_local, 'scons-LICENSE')
1129     l = env.SCons_revision(scons_LICENSE, 'LICENSE-local')
1130     local_targets.append(l)
1131     Local(l)
1132
1133     scons_README = os.path.join(build_dir_local, 'scons-README')
1134     l = env.SCons_revision(scons_README, 'README-local')
1135     local_targets.append(l)
1136     Local(l)
1137
1138     if gzip:
1139         env.Command(dist_local_tar_gz,
1140                     local_targets,
1141                     "cd %s && tar czf $( ${TARGET.abspath} $) *" % build_dir_local)
1142
1143         unpack_targets = map(lambda x, d=test_local_tar_gz_dir:
1144                                     os.path.join(d, x),
1145                              rf)
1146         commands = [Delete(test_local_tar_gz_dir),
1147                     Mkdir(test_local_tar_gz_dir),
1148                     "cd %s && tar xzf $( ${SOURCE.abspath} $)" % test_local_tar_gz_dir]
1149
1150         env.Command(unpack_targets, dist_local_tar_gz, commands)
1151
1152     if zipit:
1153         env.Command(dist_local_zip, local_targets, zipit,
1154                     CD = build_dir_local, PSV = '.')
1155
1156         unpack_targets = map(lambda x, d=test_local_zip_dir:
1157                                     os.path.join(d, x),
1158                              rf)
1159         commands = [Delete(test_local_zip_dir),
1160                     Mkdir(test_local_zip_dir),
1161                     unzipit]
1162
1163         env.Command(unpack_targets, dist_local_zip, unzipit,
1164                     UNPACK_ZIP_DIR = test_local_zip_dir)
1165
1166 #
1167 #
1168 #
1169 Export('build_dir', 'env')
1170
1171 SConscript('QMTest/SConscript')
1172
1173 #
1174 #
1175 #
1176 files = [
1177     'runtest.py',
1178 ]
1179
1180 def copy(target, source, env):
1181     t = str(target[0])
1182     s = str(source[0])
1183     open(t, 'wb').write(open(s, 'rb').read())
1184
1185 for file in files:
1186     # Guarantee that real copies of these files always exist in
1187     # build/.  If there's a symlink there, then this is an Aegis
1188     # build and we blow them away now so that they'll get "built" later.
1189     p = os.path.join(build_dir, file)
1190     if os.path.islink(p):
1191         os.unlink(p)
1192     if not os.path.isabs(p):
1193         p = '#' + p
1194     sp = env.Command(p, file, copy)
1195     Local(sp)
1196
1197 #
1198 # Documentation.
1199 #
1200 Export('build_dir', 'env', 'whereis')
1201
1202 SConscript('doc/SConscript')
1203
1204 #
1205 # If we're running in a Subversion working directory, pack up a complete
1206 # source archive from the project files and files in the change.
1207 #
1208
1209 sfiles = None
1210 if hg_status_lines:
1211     slines = filter(lambda l: l[0] in 'ACM', hg_status_lines)
1212     sfiles = map(lambda l: l.split()[-1], slines)
1213 elif svn_status_lines:
1214     slines = filter(lambda l: l[0] in ' MA', svn_status_lines)
1215     sentries = map(lambda l: l.split()[-1], slines)
1216     sfiles = filter(os.path.isfile, sentries)
1217 else:
1218    "Not building in a Mercurial or Subversion tree; skipping building src package."
1219
1220 if sfiles:
1221     remove_patterns = [
1222         '.hgt/*',
1223         '.svnt/*',
1224         '*.aeignore',
1225         '*.cvsignore',
1226         '*.hgignore',
1227         'www/*',
1228     ]
1229
1230     for p in remove_patterns:
1231         sfiles = filter(lambda s, p=p: not fnmatch.fnmatch(s, p), sfiles)
1232
1233     if sfiles:
1234         ps = "%s-src" % project
1235         psv = "%s-%s" % (ps, version)
1236         b_ps = os.path.join(build_dir, ps)
1237         b_psv = os.path.join(build_dir, psv)
1238         b_psv_stamp = b_psv + '-stamp'
1239
1240         src_tar_gz = os.path.join(build_dir, 'dist', '%s.tar.gz' % psv)
1241         src_zip = os.path.join(build_dir, 'dist', '%s.zip' % psv)
1242
1243         Local(src_tar_gz, src_zip)
1244
1245         for file in sfiles:
1246             env.SCons_revision(os.path.join(b_ps, file), file)
1247
1248         b_ps_files = map(lambda x, d=b_ps: os.path.join(d, x), sfiles)
1249         cmds = [
1250             Delete(b_psv),
1251             Copy(b_psv, b_ps),
1252             Touch("$TARGET"),
1253         ]
1254
1255         env.Command(b_psv_stamp, src_deps + b_ps_files, cmds)
1256
1257         apply(Local, b_ps_files, {})
1258
1259         if gzip:
1260
1261             env.Command(src_tar_gz, b_psv_stamp,
1262                         "tar cz${TAR_HFLAG} -f $TARGET -C build %s" % psv)
1263
1264             #
1265             # Unpack the archive into build/unpack/scons-{version}.
1266             #
1267             unpack_tar_gz_files = map(lambda x, u=unpack_tar_gz_dir, psv=psv:
1268                                              os.path.join(u, psv, x),
1269                                       sfiles)
1270
1271             #
1272             # We'd like to replace the last three lines with the following:
1273             #
1274             #   tar zxf $SOURCES -C $UNPACK_TAR_GZ_DIR
1275             #
1276             # but that gives heartburn to Cygwin's tar, so work around it
1277             # with separate zcat-tar-rm commands.
1278             env.Command(unpack_tar_gz_files, src_tar_gz, [
1279                 Delete(os.path.join(unpack_tar_gz_dir, psv)),
1280                 "$ZCAT $SOURCES > .temp",
1281                 "tar xf .temp -C $UNPACK_TAR_GZ_DIR",
1282                 Delete(".temp"),
1283             ])
1284
1285             #
1286             # Run setup.py in the unpacked subdirectory to "install" everything
1287             # into our build/test subdirectory.  The runtest.py script will set
1288             # PYTHONPATH so that the tests only look under build/test-{package},
1289             # and under etc (for the testing modules TestCmd.py, TestSCons.py,
1290             # and unittest.py).  This makes sure that our tests pass with what
1291             # we really packaged, not because of something hanging around in
1292             # the development directory.
1293             #
1294             # We can get away with calling setup.py using a directory path
1295             # like this because we put a preamble in it that will chdir()
1296             # to the directory in which setup.py exists.
1297             #
1298             dfiles = map(lambda x, d=test_src_tar_gz_dir: os.path.join(d, x),
1299                             dst_files)
1300             scons_lib_dir = os.path.join(unpack_tar_gz_dir, psv, 'src', 'engine')
1301             ENV = env.Dictionary('ENV').copy()
1302             ENV['SCONS_LIB_DIR'] = scons_lib_dir
1303             ENV['USERNAME'] = developer
1304             env.Command(dfiles, unpack_tar_gz_files,
1305                 [
1306                 Delete(os.path.join(unpack_tar_gz_dir,
1307                                     psv,
1308                                     'build',
1309                                     'scons',
1310                                     'build')),
1311                 Delete("$TEST_SRC_TAR_GZ_DIR"),
1312                 'cd "%s" && $PYTHON $PYTHONFLAGS "%s" "%s" VERSION="$VERSION"' % \
1313                     (os.path.join(unpack_tar_gz_dir, psv),
1314                      os.path.join('src', 'script', 'scons.py'),
1315                      os.path.join('build', 'scons')),
1316                 '$PYTHON $PYTHONFLAGS "%s" install "--prefix=$TEST_SRC_TAR_GZ_DIR" --standalone-lib' % \
1317                     os.path.join(unpack_tar_gz_dir,
1318                                  psv,
1319                                  'build',
1320                                  'scons',
1321                                  'setup.py'),
1322                 ],
1323                 ENV = ENV)
1324
1325         if zipit:
1326
1327             env.Command(src_zip, b_psv_stamp, zipit, CD = 'build', PSV = psv)
1328
1329             #
1330             # Unpack the archive into build/unpack/scons-{version}.
1331             #
1332             unpack_zip_files = map(lambda x, u=unpack_zip_dir, psv=psv:
1333                                              os.path.join(u, psv, x),
1334                                       sfiles)
1335
1336             env.Command(unpack_zip_files, src_zip, [
1337                 Delete(os.path.join(unpack_zip_dir, psv)),
1338                 unzipit
1339             ])
1340
1341             #
1342             # Run setup.py in the unpacked subdirectory to "install" everything
1343             # into our build/test subdirectory.  The runtest.py script will set
1344             # PYTHONPATH so that the tests only look under build/test-{package},
1345             # and under etc (for the testing modules TestCmd.py, TestSCons.py,
1346             # and unittest.py).  This makes sure that our tests pass with what
1347             # we really packaged, not because of something hanging around in
1348             # the development directory.
1349             #
1350             # We can get away with calling setup.py using a directory path
1351             # like this because we put a preamble in it that will chdir()
1352             # to the directory in which setup.py exists.
1353             #
1354             dfiles = map(lambda x, d=test_src_zip_dir: os.path.join(d, x),
1355                             dst_files)
1356             scons_lib_dir = os.path.join(unpack_zip_dir, psv, 'src', 'engine')
1357             ENV = env.Dictionary('ENV').copy()
1358             ENV['SCONS_LIB_DIR'] = scons_lib_dir
1359             ENV['USERNAME'] = developer
1360             env.Command(dfiles, unpack_zip_files,
1361                 [
1362                 Delete(os.path.join(unpack_zip_dir,
1363                                     psv,
1364                                     'build',
1365                                     'scons',
1366                                     'build')),
1367                 Delete("$TEST_SRC_ZIP_DIR"),
1368                 'cd "%s" && $PYTHON $PYTHONFLAGS "%s" "%s" VERSION="$VERSION"' % \
1369                     (os.path.join(unpack_zip_dir, psv),
1370                      os.path.join('src', 'script', 'scons.py'),
1371                      os.path.join('build', 'scons')),
1372                 '$PYTHON $PYTHONFLAGS "%s" install "--prefix=$TEST_SRC_ZIP_DIR" --standalone-lib' % \
1373                     os.path.join(unpack_zip_dir,
1374                                  psv,
1375                                  'build',
1376                                  'scons',
1377                                  'setup.py'),
1378                 ],
1379                 ENV = ENV)
1380
1381 for pf, help_text in packaging_flavors:
1382     Alias(pf, [
1383         os.path.join(build_dir, 'test-'+pf),
1384         os.path.join(build_dir, 'QMTest'),
1385         os.path.join(build_dir, 'runtest.py'),
1386     ])