Add a script to print .sconsign file contents.
[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 copyright_years = '2001, 2002, 2003'
8
9 #
10 # __COPYRIGHT__
11 #
12 # Permission is hereby granted, free of charge, to any person obtaining
13 # a copy of this software and associated documentation files (the
14 # "Software"), to deal in the Software without restriction, including
15 # without limitation the rights to use, copy, modify, merge, publish,
16 # distribute, sublicense, and/or sell copies of the Software, and to
17 # permit persons to whom the Software is furnished to do so, subject to
18 # the following conditions:
19 #
20 # The above copyright notice and this permission notice shall be included
21 # in all copies or substantial portions of the Software.
22 #
23 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
24 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
25 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 #
31
32 import distutils.util
33 import os
34 import os.path
35 import socket
36 import stat
37 import string
38 import sys
39 import time
40
41 project = 'scons'
42 default_version = '0.90'
43 copyright = "Copyright (c) %s Steven Knight" % copyright_years
44
45 Default('.')
46
47 #
48 # An internal "whereis" routine to figure out if a given program
49 # is available on this system.
50 #
51 def whereis(file):
52     for dir in string.split(os.environ['PATH'], os.pathsep):
53         f = os.path.join(dir, file)
54         if os.path.isfile(f):
55             try:
56                 st = os.stat(f)
57             except:
58                 continue
59             if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
60                 return f
61     return None
62
63 #
64 # We let the presence or absence of various utilities determine
65 # whether or not we bother to build certain pieces of things.
66 # This should allow people to still do SCons work even if they
67 # don't have Aegis or RPM installed, for example.
68 #
69 aegis = whereis('aegis')
70 aesub = whereis('aesub')
71 dh_builddeb = whereis('dh_builddeb')
72 fakeroot = whereis('fakeroot')
73 gzip = whereis('gzip')
74 rpmbuild = whereis('rpmbuild') or whereis('rpm')
75 unzip = whereis('unzip')
76 zip = whereis('zip')
77
78 #
79 # Now grab the information that we "build" into the files.
80 #
81 try:
82     date = ARGUMENTS['date']
83 except:
84     date = time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(time.time()))
85
86 if ARGUMENTS.has_key('developer'):
87     developer = ARGUMENTS['developer']
88 elif os.environ.has_key('USERNAME'):
89     developer = os.environ['USERNAME']
90 elif os.environ.has_key('LOGNAME'):
91     developer = os.environ['LOGNAME']
92 elif os.environ.has_key('USER'):
93     developer = os.environ['USER']
94
95 if ARGUMENTS.has_key('build_system'):
96     build_system = ARGUMENTS['build_system']
97 else:
98     build_system = string.split(socket.gethostname(), '.')[0]
99
100 if ARGUMENTS.has_key('version'):
101     revision = ARGUMENTS['version']
102 elif aesub:
103     revision = os.popen(aesub + " \\$version", "r").read()[:-1]
104 else:
105     revision = default_version
106
107 a = string.split(revision, '.')
108 arr = [a[0]]
109 for s in a[1:]:
110     if len(s) == 1:
111         s = '0' + s
112     arr.append(s)
113 revision = string.join(arr, '.')
114
115 # Here's how we'd turn the calculated $revision into our package $version.
116 # This makes it difficult to coordinate with other files (debian/changelog
117 # and rpm/scons.spec) that hard-code the version number, so just go with
118 # the flow for now and hard code it here, too.
119 #if len(arr) >= 2:
120 #    arr = arr[:-1]
121 #def xxx(str):
122 #    if str[0] == 'C' or str[0] == 'D':
123 #        str = str[1:]
124 #    while len(str) > 2 and str[0] == '0':
125 #        str = str[1:]
126 #    return str
127 #arr = map(lambda x, xxx=xxx: xxx(x), arr)
128 #version = string.join(arr, '.')
129 version = default_version
130
131 build_id = string.replace(revision, version + '.', '')
132
133 if ARGUMENTS.has_key('change'):
134     change = ARGUMENTS['change']
135 elif aesub:
136     change = os.popen(aesub + " \\$change", "r").read()[:-1]
137 else:
138     change = default_version
139
140 python_ver = sys.version[0:3]
141
142 platform = distutils.util.get_platform()
143
144 ENV = { 'PATH' : os.environ['PATH'] }
145 for key in ['AEGIS_PROJECT', 'PYTHONPATH']:
146     if os.environ.has_key(key):
147         ENV[key] = os.environ[key]
148
149 lib_project = os.path.join("lib", project)
150
151 cwd_build = os.path.join(os.getcwd(), "build")
152
153 test_deb_dir          = os.path.join(cwd_build, "test-deb")
154 test_rpm_dir          = os.path.join(cwd_build, "test-rpm")
155 test_tar_gz_dir       = os.path.join(cwd_build, "test-tar-gz")
156 test_src_tar_gz_dir   = os.path.join(cwd_build, "test-src-tar-gz")
157 test_local_tar_gz_dir = os.path.join(cwd_build, "test-local-tar-gz")
158 test_zip_dir          = os.path.join(cwd_build, "test-zip")
159 test_src_zip_dir      = os.path.join(cwd_build, "test-src-zip")
160 test_local_zip_dir    = os.path.join(cwd_build, "test-local-zip")
161
162 unpack_tar_gz_dir     = os.path.join(cwd_build, "unpack-tar-gz")
163 unpack_zip_dir        = os.path.join(cwd_build, "unpack-zip")
164
165 if platform == "win32":
166     tar_hflag = ''
167     python_project_subinst_dir = None
168     project_script_subinst_dir = 'Scripts'
169 else:
170     tar_hflag = 'h'
171     python_project_subinst_dir = lib_project
172     project_script_subinst_dir = 'bin'
173
174
175 zcat = 'gzip -d -c'
176
177 #
178 # Figure out if we can handle .zip files.
179 #
180 zipit = None
181 unzipit = None
182 try:
183     import zipfile
184
185     def zipit(env, target, source):
186         print "Zipping %s:" % str(target[0])
187         def visit(arg, dirname, names):
188             for name in names:
189                 path = os.path.join(dirname, name)
190                 if os.path.isfile(path):
191                     arg.write(path)
192         zf = zipfile.ZipFile(str(target[0]), 'w')
193         olddir = os.getcwd()
194         os.chdir(env['CD'])
195         try: os.path.walk(env['PSV'], visit, zf)
196         finally: os.chdir(olddir)
197         zf.close()
198
199     def unzipit(env, target, source):
200         print "Unzipping %s:" % str(source[0])
201         zf = zipfile.ZipFile(str(source[0]), 'r')
202         for name in zf.namelist():
203             dest = os.path.join(env['UNPACK_ZIP_DIR'], name)
204             dir = os.path.dirname(dest)
205             try:
206                 os.makedirs(dir)
207             except:
208                 pass
209             print dest,name
210             # if the file exists, then delete it before writing
211             # to it so that we don't end up trying to write to a symlink:
212             if os.path.isfile(dest) or os.path.islink(dest):
213                 os.unlink(dest)
214             if not os.path.isdir(dest):
215                 open(dest, 'w').write(zf.read(name))
216
217 except:
218     if unzip and zip:
219         zipit = "cd $CD && $ZIP $ZIPFLAGS $( ${TARGET.abspath} $) $PSV"
220         unzipit = "$UNZIP $UNZIPFLAGS $SOURCES"
221
222 def SCons_revision(target, source, env):
223     """Interpolate specific values from the environment into a file.
224
225     This is used to copy files into a tree that gets packaged up
226     into the source file package.
227     """
228     t = str(target[0])
229     s = source[0].rstr()
230     inf = open(s, 'rb')
231     outf = open(t, 'wb')
232     for line in inf.readlines():
233         # Note:  We construct the __*__ substitution strings here
234         # so that they don't get replaced when this file gets
235         # copied into the tree for packaging.
236         line = string.replace(line, '__BUILD'     + '__', env['BUILD'])
237         line = string.replace(line, '__BUILDSYS'  + '__', env['BUILDSYS'])
238         line = string.replace(line, '__COPYRIGHT' + '__', env['COPYRIGHT'])
239         line = string.replace(line, '__DATE'      + '__', env['DATE'])
240         line = string.replace(line, '__DEVELOPER' + '__', env['DEVELOPER'])
241         line = string.replace(line, '__FILE'      + '__', str(source[0]))
242         line = string.replace(line, '__REVISION'  + '__', env['REVISION'])
243         line = string.replace(line, '__VERSION'   + '__', env['VERSION'])
244         line = string.replace(line, '__NULL'      + '__', '')
245         outf.write(line)
246     inf.close()
247     outf.close()
248     os.chmod(t, os.stat(s)[0])
249
250 revbuilder = Builder(action = Action(SCons_revision, varlist=['VERSION']))
251
252 env = Environment(
253                    ENV                 = ENV,
254
255                    BUILD               = build_id,
256                    BUILDSYS            = build_system,
257                    COPYRIGHT           = copyright,
258                    DATE                = date,
259                    DEVELOPER           = developer,
260                    REVISION            = revision,
261                    VERSION             = version,
262                    DH_COMPAT           = 2,
263
264                    TAR_HFLAG           = tar_hflag,
265
266                    ZIP                 = zip,
267                    ZIPFLAGS            = '-r',
268                    UNZIP               = unzip,
269                    UNZIPFLAGS          = '-o -d $UNPACK_ZIP_DIR',
270
271                    ZCAT                = zcat,
272
273                    RPMBUILD            = rpmbuild,
274                    RPM2CPIO            = 'rpm2cpio',
275
276                    TEST_DEB_DIR        = test_deb_dir,
277                    TEST_RPM_DIR        = test_rpm_dir,
278                    TEST_SRC_TAR_GZ_DIR = test_src_tar_gz_dir,
279                    TEST_SRC_ZIP_DIR    = test_src_zip_dir,
280                    TEST_TAR_GZ_DIR     = test_tar_gz_dir,
281                    TEST_ZIP_DIR        = test_zip_dir,
282
283                    UNPACK_TAR_GZ_DIR   = unpack_tar_gz_dir,
284                    UNPACK_ZIP_DIR      = unpack_zip_dir,
285
286                    BUILDERS            = { 'SCons_revision' : revbuilder },
287
288                    PYTHON              = sys.executable
289                  )
290
291 #
292 # Define SCons packages.
293 #
294 # In the original, more complicated packaging scheme, we were going
295 # to have separate packages for:
296 #
297 #       python-scons    only the build engine
298 #       scons-script    only the script
299 #       scons           the script plus the build engine
300 #
301 # We're now only delivering a single "scons" package, but this is still
302 # "built" as two sub-packages (the build engine and the script), so
303 # the definitions remain here, even though we're not using them for
304 # separate packages.
305 #
306
307 python_scons = {
308         'pkg'           : 'python-' + project,
309         'src_subdir'    : 'engine',
310         'inst_subdir'   : os.path.join('lib', 'python1.5', 'site-packages'),
311         'rpm_dir'       : '/usr/lib/scons',
312
313         'debian_deps'   : [
314                             'debian/changelog',
315                             'debian/control',
316                             'debian/copyright',
317                             'debian/dirs',
318                             'debian/docs',
319                             'debian/postinst',
320                             'debian/prerm',
321                             'debian/rules',
322                           ],
323
324         'files'         : [ 'LICENSE.txt',
325                             'README.txt',
326                             'setup.cfg',
327                             'setup.py',
328                           ],
329
330         'filemap'       : {
331                             'LICENSE.txt' : '../LICENSE.txt'
332                           },
333 }
334
335 #
336 # The original packaging scheme would have have required us to push
337 # the Python version number into the package name (python1.5-scons,
338 # python2.0-scons, etc.), which would have required a definition
339 # like the following.  Leave this here in case we ever decide to do
340 # this in the future, but note that this would require some modification
341 # to src/engine/setup.py before it would really work.
342 #
343 #python2_scons = {
344 #        'pkg'          : 'python2-' + project,
345 #        'src_subdir'   : 'engine',
346 #        'inst_subdir'  : os.path.join('lib', 'python2.1', 'site-packages'),
347 #
348 #        'debian_deps'  : [
349 #                            'debian/changelog',
350 #                            'debian/control',
351 #                            'debian/copyright',
352 #                            'debian/dirs',
353 #                            'debian/docs',
354 #                            'debian/postinst',
355 #                            'debian/prerm',
356 #                            'debian/rules',
357 #                          ],
358 #
359 #        'files'        : [
360 #                            'LICENSE.txt',
361 #                            'README.txt',
362 #                            'setup.cfg',
363 #                            'setup.py',
364 #                          ],
365 #        'filemap'      : {
366 #                            'LICENSE.txt' : '../LICENSE.txt',
367 #                          },
368 #}
369 #
370
371 scons_script = {
372         'pkg'           : project + '-script',
373         'src_subdir'    : 'script',
374         'inst_subdir'   : 'bin',
375         'rpm_dir'       : '/usr/bin',
376
377         'debian_deps'   : [
378                             'debian/changelog',
379                             'debian/control',
380                             'debian/copyright',
381                             'debian/dirs',
382                             'debian/docs',
383                             'debian/postinst',
384                             'debian/prerm',
385                             'debian/rules',
386                           ],
387
388         'files'         : [
389                             'LICENSE.txt',
390                             'README.txt',
391                             'setup.cfg',
392                             'setup.py',
393                           ],
394
395         'filemap'       : {
396                             'LICENSE.txt' : '../LICENSE.txt',
397                             'scons'       : 'scons.py',
398                             'sconsign'    : 'sconsign.py',
399                            }
400 }
401
402 scons = {
403         'pkg'           : project,
404
405         'debian_deps'   : [
406                             'debian/changelog',
407                             'debian/control',
408                             'debian/copyright',
409                             'debian/dirs',
410                             'debian/docs',
411                             'debian/postinst',
412                             'debian/prerm',
413                             'debian/rules',
414                           ],
415
416         'files'         : [
417                             'CHANGES.txt',
418                             'LICENSE.txt',
419                             'README.txt',
420                             'RELEASE.txt',
421                             'os_spawnv_fix.diff',
422                             'scons.1',
423                             'sconsign.1',
424                             'script/scons.bat',
425                             'setup.cfg',
426                             'setup.py',
427                           ],
428
429         'filemap'       : {
430                             'scons.1' : '../doc/man/scons.1',
431                             'sconsign.1' : '../doc/man/sconsign.1',
432                           },
433
434         'subpkgs'       : [ python_scons, scons_script ],
435
436         'subinst_dirs'  : {
437                              'python-' + project : python_project_subinst_dir,
438                              project + '-script' : project_script_subinst_dir,
439                            },
440 }
441
442 scripts = ['scons', 'sconsign']
443
444 src_deps = []
445 src_files = []
446
447 for p in [ scons ]:
448     #
449     # Initialize variables with the right directories for this package.
450     #
451     pkg = p['pkg']
452     pkg_version = "%s-%s" % (pkg, version)
453
454     src = 'src'
455     if p.has_key('src_subdir'):
456         src = os.path.join(src, p['src_subdir'])
457
458     build = os.path.join('build', pkg)
459
460     tar_gz = os.path.join(build, 'dist', "%s.tar.gz" % pkg_version)
461     platform_tar_gz = os.path.join(build,
462                                    'dist',
463                                    "%s.%s.tar.gz" % (pkg_version, platform))
464     zip = os.path.join(build, 'dist', "%s.zip" % pkg_version)
465     platform_zip = os.path.join(build,
466                                 'dist',
467                                 "%s.%s.zip" % (pkg_version, platform))
468     win32_exe = os.path.join(build, 'dist', "%s.win32.exe" % pkg_version)
469
470     #
471     # Update the environment with the relevant information
472     # for this package.
473     #
474     # We can get away with calling setup.py using a directory path
475     # like this because we put a preamble in it that will chdir()
476     # to the directory in which setup.py exists.
477     #
478     setup_py = os.path.join(build, 'setup.py')
479     env.Replace(PKG = pkg,
480                 PKG_VERSION = pkg_version,
481                 SETUP_PY = setup_py)
482     Local(setup_py)
483
484     #
485     # Read up the list of source files from our MANIFEST.in.
486     # This list should *not* include LICENSE.txt, MANIFEST,
487     # README.txt, or setup.py.  Make a copy of the list for the
488     # destination files.
489     #
490     manifest_in = File(os.path.join(src, 'MANIFEST.in')).rstr()
491     src_files = map(lambda x: x[:-1],
492                     open(manifest_in).readlines())
493     raw_files = src_files[:]
494     dst_files = src_files[:]
495     rpm_files = []
496
497     MANIFEST_in_list = []
498
499     if p.has_key('subpkgs'):
500         #
501         # This package includes some sub-packages.  Read up their
502         # MANIFEST.in files, and add them to our source and destination
503         # file lists, modifying them as appropriate to add the
504         # specified subdirs.
505         #
506         for sp in p['subpkgs']:
507             ssubdir = sp['src_subdir']
508             isubdir = p['subinst_dirs'][sp['pkg']]
509             MANIFEST_in = File(os.path.join(src, ssubdir, 'MANIFEST.in')).rstr()
510             MANIFEST_in_list.append(MANIFEST_in)
511             files = map(lambda x: x[:-1], open(MANIFEST_in).readlines())
512             raw_files.extend(files)
513             src_files.extend(map(lambda x, s=ssubdir: os.path.join(s, x), files))
514             for f in files:
515                 r = os.path.join(sp['rpm_dir'], f)
516                 rpm_files.append(r)
517                 if f[-3:] == ".py":
518                     rpm_files.append(r + 'c')
519             if isubdir:
520                 files = map(lambda x, i=isubdir: os.path.join(i, x), files)
521             dst_files.extend(files)
522             for k in sp['filemap'].keys():
523                 f = sp['filemap'][k]
524                 if f:
525                     k = os.path.join(sp['src_subdir'], k)
526                     p['filemap'][k] = os.path.join(sp['src_subdir'], f)
527
528     #
529     # Now that we have the "normal" source files, add those files
530     # that are standard for each distribution.  Note that we don't
531     # add these to dst_files, because they don't get installed.
532     # And we still have the MANIFEST to add.
533     #
534     src_files.extend(p['files'])
535
536     #
537     # Now run everything in src_file through the sed command we
538     # concocted to expand __FILE__, __VERSION__, etc.
539     #
540     for b in src_files:
541         s = p['filemap'].get(b, b)
542         env.SCons_revision(os.path.join(build, b), os.path.join(src, s))
543
544     #
545     # NOW, finally, we can create the MANIFEST, which we do
546     # by having Python spit out the contents of the src_files
547     # array we've carefully created.  After we've added
548     # MANIFEST itself to the array, of course.
549     #
550     src_files.append("MANIFEST")
551     MANIFEST_in_list.append(os.path.join(src, 'MANIFEST.in'))
552
553     def write_src_files(target, source, **kw):
554         global src_files
555         src_files.sort()
556         f = open(str(target[0]), 'wb')
557         for file in src_files:
558             f.write(file + "\n")
559         f.close()
560         return 0
561     env.Command(os.path.join(build, 'MANIFEST'),
562                 MANIFEST_in_list,
563                 write_src_files)
564
565     #
566     # Now go through and arrange to create whatever packages we can.
567     #
568     build_src_files = map(lambda x, b=build: os.path.join(b, x), src_files)
569     apply(Local, build_src_files, {})
570
571     distutils_formats = []
572
573     distutils_targets = [ win32_exe ]
574
575     install_targets = distutils_targets[:]
576
577     if gzip:
578
579         distutils_formats.append('gztar')
580
581         src_deps.append(tar_gz)
582
583         distutils_targets.extend([ tar_gz, platform_tar_gz ])
584         install_targets.extend([ tar_gz, platform_tar_gz ])
585
586         #
587         # Unpack the tar.gz archive created by the distutils into
588         # build/unpack-tar-gz/scons-{version}.
589         #
590         # We'd like to replace the last three lines with the following:
591         #
592         #       tar zxf $SOURCES -C $UNPACK_TAR_GZ_DIR
593         #
594         # but that gives heartburn to Cygwin's tar, so work around it
595         # with separate zcat-tar-rm commands.
596         #
597         unpack_tar_gz_files = map(lambda x, u=unpack_tar_gz_dir, pv=pkg_version:
598                                          os.path.join(u, pv, x),
599                                   src_files)
600         env.Command(unpack_tar_gz_files, tar_gz, [
601                     "rm -rf %s" % os.path.join(unpack_tar_gz_dir, pkg_version),
602                     "$ZCAT $SOURCES > .temp",
603                     "tar xf .temp -C $UNPACK_TAR_GZ_DIR",
604                     "rm -f .temp",
605         ])
606
607         #
608         # Run setup.py in the unpacked subdirectory to "install" everything
609         # into our build/test subdirectory.  The runtest.py script will set
610         # PYTHONPATH so that the tests only look under build/test-{package},
611         # and under etc (for the testing modules TestCmd.py, TestSCons.py,
612         # and unittest.py).  This makes sure that our tests pass with what
613         # we really packaged, not because of something hanging around in
614         # the development directory.
615         #
616         # We can get away with calling setup.py using a directory path
617         # like this because we put a preamble in it that will chdir()
618         # to the directory in which setup.py exists.
619         #
620         dfiles = map(lambda x, d=test_tar_gz_dir: os.path.join(d, x), dst_files)
621         env.Command(dfiles, unpack_tar_gz_files, [
622             "rm -rf %s" % os.path.join(unpack_tar_gz_dir, pkg_version, 'build'),
623             "rm -rf $TEST_TAR_GZ_DIR",
624             "$PYTHON %s install --prefix=$TEST_TAR_GZ_DIR" % \
625                 os.path.join(unpack_tar_gz_dir, pkg_version, 'setup.py'),
626         ])
627
628     if zipit:
629
630         distutils_formats.append('zip')
631
632         src_deps.append(zip)
633
634         distutils_targets.extend([ zip, platform_zip ])
635         install_targets.extend([ zip, platform_zip ])
636
637         #
638         # Unpack the zip archive created by the distutils into
639         # build/unpack-zip/scons-{version}.
640         #
641         unpack_zip_files = map(lambda x, u=unpack_zip_dir, pv=pkg_version:
642                                       os.path.join(u, pv, x),
643                                src_files)
644
645         env.Command(unpack_zip_files, zip, unzipit)
646
647         #
648         # Run setup.py in the unpacked subdirectory to "install" everything
649         # into our build/test subdirectory.  The runtest.py script will set
650         # PYTHONPATH so that the tests only look under build/test-{package},
651         # and under etc (for the testing modules TestCmd.py, TestSCons.py,
652         # and unittest.py).  This makes sure that our tests pass with what
653         # we really packaged, not because of something hanging around in
654         # the development directory.
655         #
656         # We can get away with calling setup.py using a directory path
657         # like this because we put a preamble in it that will chdir()
658         # to the directory in which setup.py exists.
659         #
660         dfiles = map(lambda x, d=test_zip_dir: os.path.join(d, x), dst_files)
661         env.Command(dfiles, unpack_zip_files, [
662             "rm -rf %s" % os.path.join(unpack_zip_dir, pkg_version, 'build'),
663             "rm -rf $TEST_ZIP_DIR",
664             "$PYTHON %s install --prefix=$TEST_ZIP_DIR" % \
665                 os.path.join(unpack_zip_dir, pkg_version, 'setup.py'),
666         ])
667
668     if rpmbuild:
669         topdir = os.path.join(os.getcwd(), build, 'build',
670                               'bdist.' + platform, 'rpm')
671
672         BUILDdir = os.path.join(topdir, 'BUILD', pkg + '-' + version)
673         RPMSdir = os.path.join(topdir, 'RPMS', 'noarch')
674         SOURCESdir = os.path.join(topdir, 'SOURCES')
675         SPECSdir = os.path.join(topdir, 'SPECS')
676         SRPMSdir = os.path.join(topdir, 'SRPMS')
677
678         specfile_in = os.path.join('rpm', "%s.spec.in" % pkg)
679         specfile = os.path.join(SPECSdir, "%s-1.spec" % pkg_version)
680         sourcefile = os.path.join(SOURCESdir, "%s.tar.gz" % pkg_version);
681         noarch_rpm = os.path.join(RPMSdir, "%s-1.noarch.rpm" % pkg_version)
682         src_rpm = os.path.join(SRPMSdir, "%s-1.src.rpm" % pkg_version)
683
684         def spec_function(target, source, env):
685             """Generate the RPM .spec file from the template file.
686
687             This fills in the %files portion of the .spec file with a
688             list generated from our MANIFEST(s), so we don't have to
689             maintain multiple lists.
690             """
691             c = open(str(source[0]), 'rb').read()
692             c = string.replace(c, '__RPM_FILES__', env['RPM_FILES'])
693             open(str(target[0]), 'wb').write(c)
694
695         rpm_files.sort()
696         rpm_files_str = string.join(rpm_files, "\n") + "\n"
697         rpm_spec_env = env.Copy(RPM_FILES = rpm_files_str)
698         rpm_spec_action = Action(spec_function, varlist=['RPM_FILES'])
699         rpm_spec_env.Command(specfile, specfile_in, rpm_spec_action)
700
701         env.InstallAs(sourcefile, tar_gz)
702
703         targets = [ noarch_rpm, src_rpm ]
704         cmd = "$RPMBUILD --define '_topdir $(%s$)' -ba $SOURCES" % topdir
705         if not os.path.isdir(BUILDdir):
706             cmd = ("$( mkdir -p %s; $)" % BUILDdir) + cmd
707         env.Command(targets, specfile, cmd)
708         env.Depends(targets, sourcefile)
709
710         install_targets.extend(targets)
711
712         dfiles = map(lambda x, d=test_rpm_dir: os.path.join(d, 'usr', x),
713                      dst_files)
714         env.Command(dfiles,
715                     noarch_rpm,
716                     "$RPM2CPIO $SOURCES | (cd $TEST_RPM_DIR && cpio -id)")
717
718     if dh_builddeb and fakeroot:
719         # Our Debian packaging builds directly into build/dist,
720         # so we don't need to add the .debs to install_targets.
721         deb = os.path.join('build', 'dist', "%s_%s-1_all.deb" % (pkg, version))
722         for d in p['debian_deps']:
723             b = env.SCons_revision(os.path.join(build, d), d)
724             env.Depends(deb, b)
725             Local(b)
726         env.Command(deb, build_src_files, [
727             "cd %s && fakeroot make -f debian/rules PYTHON=$PYTHON BUILDDEB_OPTIONS=--destdir=../../build/dist binary" % build,
728                     ])
729
730         old = os.path.join('lib', 'scons', '')
731         new = os.path.join('lib', 'python2.1', 'site-packages', '')
732         def xxx(s, old=old, new=new):
733             if s[:len(old)] == old:
734                 s = new + s[len(old):]
735             return os.path.join('usr', s)
736         dfiles = map(lambda x, t=test_deb_dir: os.path.join(t, x),
737                      map(xxx, dst_files))
738         env.Command(dfiles,
739                     deb,
740                     "dpkg --fsys-tarfile $SOURCES | (cd $TEST_DEB_DIR && tar -xf -)")
741
742
743     #
744     # Generate portage files for submission to Gentoo Linux.
745     #
746     gentoo = os.path.join('build', 'gentoo')
747     ebuild = os.path.join(gentoo, 'scons-%s.ebuild' % version)
748     digest = os.path.join(gentoo, 'files', 'digest-scons-%s' % version)
749     env.Command(ebuild, os.path.join('gentoo', 'scons.ebuild.in'), SCons_revision)
750     def Digestify(target, source, env):
751         import md5
752         def hexdigest(s):
753             """Return a signature as a string of hex characters.
754             """
755             # NOTE:  This routine is a method in the Python 2.0 interface
756             # of the native md5 module, but we want SCons to operate all
757             # the way back to at least Python 1.5.2, which doesn't have it.
758             h = string.hexdigits
759             r = ''
760             for c in s:
761                 i = ord(c)
762                 r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
763             return r
764         src = source[0].rfile()
765         contents = open(str(src)).read()
766         sig = hexdigest(md5.new(contents).digest())
767         bytes = os.stat(str(src))[6]
768         open(str(target[0]), 'w').write("MD5 %s %s %d\n" % (sig,
769                                                             src.name,
770                                                             bytes))
771     env.Command(digest, tar_gz, Digestify)
772
773     #
774     # Use the Python distutils to generate the appropriate packages.
775     #
776     commands = [
777         "rm -rf %s" % os.path.join(build, 'build', 'lib'),
778         "rm -rf %s" % os.path.join(build, 'build', 'scripts'),
779     ]
780
781     if distutils_formats:
782         commands.append("rm -rf %s" % os.path.join(build,
783                                                    'build',
784                                                    'bdist.' + platform,
785                                                    'dumb'))
786         for format in distutils_formats:
787             commands.append("$PYTHON $SETUP_PY bdist_dumb -f %s" % format)
788
789         commands.append("$PYTHON $SETUP_PY sdist --formats=%s" %  \
790                             string.join(distutils_formats, ','))
791
792     commands.append("$PYTHON $SETUP_PY bdist_wininst")
793
794     env.Command(distutils_targets, build_src_files, commands)
795
796     #
797     # Now create local packages for people who want to let people
798     # build their SCons-buildable packages without having to
799     # install SCons.
800     #
801     s_l_v = '%s-local-%s' % (pkg, version)
802
803     local = os.path.join('build', pkg + '-local')
804     cwd_local = os.path.join(os.getcwd(), local)
805     cwd_local_slv = os.path.join(os.getcwd(), local, s_l_v)
806
807     local_tar_gz = os.path.join('build', 'dist', "%s.tar.gz" % s_l_v)
808     local_zip = os.path.join('build', 'dist', "%s.zip" % s_l_v)
809
810     commands = [
811         "rm -rf %s" % local,
812         "$PYTHON $SETUP_PY install --install-script=%s --install-lib=%s --no-compile" % \
813                                                 (cwd_local, cwd_local_slv),
814     ]
815
816     for script in scripts:
817         commands.append("mv %s/%s %s/%s.py" % (local, script, local, script))
818
819     rf = filter(lambda x: not x in scripts, raw_files)
820     rf = map(lambda x, slv=s_l_v: os.path.join(slv, x), rf)
821     for script in scripts:
822         rf.append("%s.py" % script)
823     local_targets = map(lambda x, s=local: os.path.join(s, x), rf)
824
825     env.Command(local_targets, build_src_files, commands)
826
827     scons_LICENSE = os.path.join(local, 'scons-LICENSE')
828     env.SCons_revision(scons_LICENSE, 'LICENSE-local')
829     local_targets.append(scons_LICENSE)
830
831     scons_README = os.path.join(local, 'scons-README')
832     env.SCons_revision(scons_README, 'README-local')
833     local_targets.append(scons_README)
834
835     if gzip:
836         env.Command(local_tar_gz,
837                     local_targets,
838                     "cd %s && tar czf $( ${TARGET.abspath} $) *" % local)
839
840         unpack_targets = map(lambda x, d=test_local_tar_gz_dir:
841                                     os.path.join(d, x),
842                              rf)
843         commands = ["rm -rf %s" % test_local_tar_gz_dir,
844                     "mkdir %s" % test_local_tar_gz_dir,
845                     "cd %s && tar xzf $( ${SOURCE.abspath} $)" % test_local_tar_gz_dir]
846
847         env.Command(unpack_targets, local_tar_gz, commands)
848
849     if zipit:
850         zipenv = env.Copy(CD = local, PSV = '.')
851         zipenv.Command(local_zip, local_targets, zipit)
852
853         unpack_targets = map(lambda x, d=test_local_zip_dir:
854                                     os.path.join(d, x),
855                              rf)
856         commands = ["rm -rf %s" % test_local_zip_dir,
857                     "mkdir %s" % test_local_zip_dir,
858                     unzipit]
859
860         zipenv = env.Copy(UNPACK_ZIP_DIR = test_local_zip_dir)
861         zipenv.Command(unpack_targets, local_zip, unzipit)
862
863     #
864     # And, lastly, install the appropriate packages in the
865     # appropriate subdirectory.
866     #
867     b_d_files = env.Install(os.path.join('build', 'dist'), install_targets)
868     Local(b_d_files)
869
870 #
871 #
872 #
873 Export('env')
874
875 SConscript('etc/SConscript')
876
877 #
878 # Documentation.
879 #
880 BuildDir('build/doc', 'doc')
881
882 Export('env', 'whereis')
883
884 SConscript('build/doc/SConscript')
885
886 #
887 # If we're running in the actual Aegis project, pack up a complete
888 # source archive from the project files and files in the change,
889 # so we can share it with helpful developers who don't use Aegis.
890 #
891
892 if change:
893     df = []
894     cmd = "aegis -list -unf -c %s cf 2>/dev/null" % change
895     for line in map(lambda x: x[:-1], os.popen(cmd, "r").readlines()):
896         a = string.split(line)
897         if a[1] == "remove":
898             df.append(a[-1])
899
900     cmd = "aegis -list -terse pf 2>/dev/null"
901     pf = map(lambda x: x[:-1], os.popen(cmd, "r").readlines())
902     cmd = "aegis -list -terse cf 2>/dev/null"
903     cf = map(lambda x: x[:-1], os.popen(cmd, "r").readlines())
904     u = {}
905     for f in pf + cf:
906         u[f] = 1
907     for f in df:
908         try:
909             del u[f]
910         except KeyError:
911             pass
912     sfiles = filter(lambda x: x[-9:] != '.aeignore' and x[-9:] != '.sconsign',
913                     u.keys())
914
915     if sfiles:
916         ps = "%s-src" % project
917         psv = "%s-%s" % (ps, version)
918         b_ps = os.path.join('build', ps)
919         b_psv = os.path.join('build', psv)
920         b_psv_stamp = b_psv + '-stamp'
921
922         src_tar_gz = os.path.join('build', 'dist', '%s.tar.gz' % psv)
923         src_zip = os.path.join('build', 'dist', '%s.zip' % psv)
924
925         Local(src_tar_gz, src_zip)
926
927         for file in sfiles:
928             env.SCons_revision(os.path.join(b_ps, file), file)
929
930         b_ps_files = map(lambda x, d=b_ps: os.path.join(d, x), sfiles)
931         cmds = [
932             "rm -rf %s" % b_psv,
933             "cp -rp %s %s" % (b_ps, b_psv),
934             "find %s -name .sconsign -exec rm {} \\;" % b_psv,
935             "touch $TARGET",
936         ]
937
938         env.Command(b_psv_stamp, src_deps + b_ps_files, cmds)
939
940         apply(Local, b_ps_files, {})
941
942         if gzip:
943
944             env.Command(src_tar_gz, b_psv_stamp,
945                         "tar cz${TAR_HFLAG} -f $TARGET -C build %s" % psv)
946
947             #
948             # Unpack the archive into build/unpack/scons-{version}.
949             #
950             unpack_tar_gz_files = map(lambda x, u=unpack_tar_gz_dir, psv=psv:
951                                              os.path.join(u, psv, x),
952                                       sfiles)
953
954             #
955             # We'd like to replace the last three lines with the following:
956             #
957             #   tar zxf $SOURCES -C $UNPACK_TAR_GZ_DIR
958             #
959             # but that gives heartburn to Cygwin's tar, so work around it
960             # with separate zcat-tar-rm commands.
961             env.Command(unpack_tar_gz_files, src_tar_gz, [
962                 "rm -rf %s" % os.path.join(unpack_tar_gz_dir, psv),
963                 "$ZCAT $SOURCES > .temp",
964                 "tar xf .temp -C $UNPACK_TAR_GZ_DIR",
965                 "rm -f .temp",
966             ])
967
968             #
969             # Run setup.py in the unpacked subdirectory to "install" everything
970             # into our build/test subdirectory.  The runtest.py script will set
971             # PYTHONPATH so that the tests only look under build/test-{package},
972             # and under etc (for the testing modules TestCmd.py, TestSCons.py,
973             # and unittest.py).  This makes sure that our tests pass with what
974             # we really packaged, not because of something hanging around in
975             # the development directory.
976             #
977             # We can get away with calling setup.py using a directory path
978             # like this because we put a preamble in it that will chdir()
979             # to the directory in which setup.py exists.
980             #
981             dfiles = map(lambda x, d=test_src_tar_gz_dir: os.path.join(d, x),
982                             dst_files)
983             ENV = env.Dictionary('ENV')
984             ENV['SCONS_LIB_DIR'] = os.path.join(unpack_tar_gz_dir, psv, 'src', 'engine')
985             ENV['USERNAME'] = developer
986             env.Copy(ENV = ENV).Command(dfiles, unpack_tar_gz_files, [
987                 "rm -rf %s" % os.path.join(unpack_tar_gz_dir,
988                                            psv,
989                                            'build',
990                                            'scons',
991                                            'build'),
992                 "rm -rf $TEST_SRC_TAR_GZ_DIR",
993                 "cd %s && $PYTHON %s %s" % \
994                     (os.path.join(unpack_tar_gz_dir, psv),
995                      os.path.join('src', 'script', 'scons.py'),
996                      os.path.join('build', 'scons')),
997                 "$PYTHON %s install --prefix=$TEST_SRC_TAR_GZ_DIR" % \
998                     os.path.join(unpack_tar_gz_dir,
999                                  psv,
1000                                  'build',
1001                                  'scons',
1002                                  'setup.py'),
1003             ])
1004
1005         if zipit:
1006
1007             zipenv = env.Copy(CD = 'build', PSV = psv)
1008             zipenv.Command(src_zip, b_psv_stamp, zipit)
1009
1010             #
1011             # Unpack the archive into build/unpack/scons-{version}.
1012             #
1013             unpack_zip_files = map(lambda x, u=unpack_zip_dir, psv=psv:
1014                                              os.path.join(u, psv, x),
1015                                       sfiles)
1016
1017             env.Command(unpack_zip_files, src_zip, unzipit)
1018
1019             #
1020             # Run setup.py in the unpacked subdirectory to "install" everything
1021             # into our build/test subdirectory.  The runtest.py script will set
1022             # PYTHONPATH so that the tests only look under build/test-{package},
1023             # and under etc (for the testing modules TestCmd.py, TestSCons.py,
1024             # and unittest.py).  This makes sure that our tests pass with what
1025             # we really packaged, not because of something hanging around in
1026             # the development directory.
1027             #
1028             # We can get away with calling setup.py using a directory path
1029             # like this because we put a preamble in it that will chdir()
1030             # to the directory in which setup.py exists.
1031             #
1032             dfiles = map(lambda x, d=test_src_zip_dir: os.path.join(d, x),
1033                             dst_files)
1034             ENV = env.Dictionary('ENV')
1035             ENV['SCONS_LIB_DIR'] = os.path.join(unpack_zip_dir, psv, 'src', 'engine')
1036             ENV['USERNAME'] = developer
1037             env.Copy(ENV = ENV).Command(dfiles, unpack_zip_files, [
1038                 "rm -rf %s" % os.path.join(unpack_zip_dir,
1039                                            psv,
1040                                            'build',
1041                                            'scons',
1042                                            'build'),
1043                 "rm -rf $TEST_SRC_ZIP_DIR",
1044                 "cd %s && $PYTHON %s %s" % \
1045                     (os.path.join(unpack_zip_dir, psv),
1046                      os.path.join('src', 'script', 'scons.py'),
1047                      os.path.join('build', 'scons')),
1048                 "$PYTHON %s install --prefix=$TEST_SRC_ZIP_DIR" % \
1049                     os.path.join(unpack_zip_dir,
1050                                  psv,
1051                                  'build',
1052                                  'scons',
1053                                  'setup.py'),
1054             ])