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