Update Copyright lines for the new year.
[scons.git] / Construct
1 #
2 # Construct file to build scons during development.
3 # (Kind of ironic that we're using the classic Perl Cons
4 # to build its Python child...)
5 #
6
7 #
8 # Copyright (c) 2001, 2002 Steven Knight
9 #
10 # Permission is hereby granted, free of charge, to any person obtaining
11 # a copy of this software and associated documentation files (the
12 # "Software"), to deal in the Software without restriction, including
13 # without limitation the rights to use, copy, modify, merge, publish,
14 # distribute, sublicense, and/or sell copies of the Software, and to
15 # permit persons to whom the Software is furnished to do so, subject to
16 # the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 #
29
30 $project = 'scons';
31
32 Default qw( . );
33
34 #
35 # An internal "whereis" routine to figure out if we have a
36 # given program available.  Put it in the "cons::" package
37 # so subsidiary Conscript files can get at it easily, too.
38 #
39 use Config;
40 sub cons::whereis {
41     my $file = shift;
42     foreach my $dir (split(/$Config{path_sep}/, $ENV{PATH})) {
43         $f = File::Spec->catfile($dir, $file);
44         return $f if -x $f && ! -d $f;
45     }
46     return undef
47 }
48
49 sub cons::read_file {
50     my $file = shift;
51     open(F, "<$file") || die "cannot open $file: $!";
52     my @lines = <F>;
53     close(F);
54     return wantarray ? @lines : join('', @lines);
55 }
56
57 #
58 # We let the presence or absence of various utilities determine
59 # whether or not we bother to build certain pieces of things.
60 # This will allow people to still do SCons work even if they
61 # don't have Aegis or RPM installed, for example.
62 #
63 $aegis = cons::whereis('aegis');
64 $aesub = cons::whereis('aesub');
65 $rpm = cons::whereis('rpm');
66 $dh_builddeb = cons::whereis('dh_builddeb');
67 $fakeroot = cons::whereis('fakeroot');
68
69 # My installation on Red Hat doesn't like any debhelper version
70 # beyond 2, so let's use 2 as the default on any non-Debian build.
71 $DH_COMPAT = (-f "/etc/debian_version") ? 3 : 2;
72
73 #
74 # Now grab the information that we "build" into the files (using sed).
75 #
76 chomp($date = $ARG{date});
77 if (! $date) {
78     ($sec,$min,$hour,$mday,$mon,$year) = localtime(time);
79     $year += 1900;
80     $date = sprintf("%4d/%02d/%02d %02d:%02d:%02d",
81                     $year, $mon, $mday, $hour, $min, $sec);
82 }
83
84 $developer = $ARG{developer} || $ENV{USERNAME} || $ENV{LOGNAME} || $ENV{USER};
85
86 $revision = $ARG{version};
87 chomp($revision = `$aesub '\$version' 2>/dev/null`) if $aesub && ! $revision;
88 $revision = '0.04' if ! $revision;
89
90 @arr = split(/\./, $revision);
91 @arr = ($arr[0], map {length($_) == 1 ? "0$_" : $_} @arr[1 .. $#arr]);
92 $revision = join('.', @arr);
93
94 # Here's how we'd turn the calculated $revision into our package $version.
95 # This makes it difficult to coordinate with other files (debian/changelog
96 # and rpm/scons.spec) that hard-code the version number, so just go with
97 # the flow for now.
98 #pop @arr if $#arr >= 2;
99 #map {s/^[CD]//, s/^0*(\d\d)$/$1/} @arr;
100 #$version = join('.', @arr);
101 $version = '0.04';
102
103 $change = $ARG{change};
104 chomp($change = `$aesub '\$change' 2>/dev/null`) if $aesub && ! $change;
105
106 chomp($python_ver = `python -c "import sys; print sys.version[0:3]"`);
107
108 chomp($platform = `python -c "from distutils.util import get_platform; print get_platform()"`);
109
110 if ($platform eq "win32") {
111     $archsuffix = "zip"
112 } else {
113     $archsuffix = "tar.gz"
114 }
115
116 use Cwd;
117 $test1_dir = File::Spec->catfile(cwd, "build", "test1");
118 $test2_dir = File::Spec->catfile(cwd, "build", "test2");
119
120 $lib_project = File::Spec->catfile("lib", "$project");
121
122 # Originally, we were going to package the build engine in a
123 # private SCons library that contained the version number, so
124 # we could easily have multiple side-by-side versions of SCons
125 # installed.  Keep this around in case we ever want to go back
126 # to that scheme.  Note that this also requires changes to
127 # runtest.py and src/setup.py.
128 #$lib_project = File::Spec->catfile("lib", "$project-$version");
129
130 $test1_lib_dir = File::Spec->catfile($test1_dir, $lib_project);
131
132 $test2_lib_dir = File::Spec->catfile($test2_dir,
133                                      "lib",
134                                      "python${python_ver}",
135                                      "site-packages");
136
137 $unpack_dir = File::Spec->catfile(cwd, "build", "unpack");
138
139 $env = new cons( ENV => {
140                           AEGIS_PROJECT => $ENV{AEGIS_PROJECT},
141                           PATH => $ENV{PATH},
142                         },
143
144                  TEST1_LIB_DIR  => $test1_lib_dir,
145                  TEST2_LIB_DIR  => $test2_lib_dir,
146
147                  DATE           => $date,
148                  DEVELOPER      => $developer,
149                  REVISION       => $revision,
150                  VERSION        => $version,
151
152                  SED            => 'sed',
153                                    # Use %(-%) around the date so date
154                                    # changes don't cause rebuilds.
155                  SEDFLAGS       => " %( -e 's+__DATE__+%DATE+' %)" .
156                                    " -e 's+__DEVELOPER__+%DEVELOPER+'" .
157                                    " -e 's+__FILE__+%<+'" .
158                                    " -e 's+__REVISION__+%REVISION+'" .
159                                    " -e 's+__VERSION__+%VERSION+'",
160                  SEDCOM         => "%SED %SEDFLAGS %< > %>",
161                 );
162
163 #
164 # Define SCons packages.
165 #
166 # In the original, more complicated packaging scheme, we were going
167 # to have separate packages for:
168 #
169 #       python-scons    only the build engine
170 #       scons-script    only the script
171 #       scons           the script plus the build engine
172 #
173 # We're now only delivering a single "scons" package, but this is still
174 # "built" as two sub-packages (the build engine and the script), so
175 # the definitions remain here, even though we're not using them for
176 # separate packages.
177 #
178
179 $python_scons = {
180         'pkg'           => "python-$project",
181         'src_subdir'    => 'engine',
182         'inst_subdir'   => File::Spec->catfile("lib",
183                                                "python1.5",
184                                                "site-packages"),
185         'prefix'        => $test2_dir,
186
187         'debian_deps'   => [ qw(debian/rules debian/control
188                                 debian/changelog debian/copyright
189                                 debian/python-scons.postinst
190                                 debian/python-scons.prerm) ],
191
192         'files'         => [ qw(LICENSE.txt README.txt setup.cfg setup.py) ],
193         'filemap'       => {
194                                 'LICENSE.txt'   => '../LICENSE.txt',
195         }
196 };
197
198 #
199 # The original packaging scheme would have have required us to push
200 # the Python version number into the package name (python1.5-scons,
201 # python2.0-scons, etc.), which would have required a definition
202 # like the following.  Leave this here in case we ever decide to do
203 # this in the future, but note that this would require some modification
204 # to src/engine/setup.py before it would really work.
205 #
206 #$python2_scons = {
207 #        'pkg'          => "python2-$project",
208 #        'src_subdir'   => 'engine',
209 #        'inst_subdir'  => File::Spec->catfile("lib",
210 #                                               "python2.1",
211 #                                               "site-packages"),
212 #        'prefix'       => $test2_dir,
213 #
214 #        'debian_deps'  => [ qw(debian/rules debian/control
215 #                                debian/changelog debian/copyright
216 #                                debian/python2-scons.postinst
217 #                                debian/python2-scons.prerm) ],
218 #
219 #        'files'                => [ qw(LICENSE.txt README.txt setup.cfg setup.py) ],
220 #        'filemap'      => {
221 #                                'LICENSE.txt'  => '../LICENSE.txt',
222 #        }
223 #};
224
225 $scons_script = {
226         'pkg'           => "$project-script",
227         'src_subdir'    => 'script',
228         'inst_subdir'   => 'bin',
229         'prefix'        => $test2_dir,
230
231         'debian_deps'   => [ qw(debian/rules debian/control
232                                 debian/changelog debian/copyright
233                                 debian/python-scons.postinst
234                                 debian/python-scons.prerm) ],
235
236         'files'         => [ qw(LICENSE.txt README.txt setup.cfg setup.py) ],
237         'filemap'               => {
238                                 'LICENSE.txt'   => '../LICENSE.txt',
239                                 'scons'         => 'scons.py',
240                         }
241 };
242
243 $scons = {
244         'pkg'           => $project,
245         'inst_subdir'   => undef,
246         'prefix'        => $test1_dir,
247
248         'debian_deps'   => [ qw(debian/rules debian/control
249                                 debian/changelog debian/copyright
250                                 debian/scons.postinst
251                                 debian/scons.prerm) ],
252
253         'files'         => [ qw(CHANGES.txt LICENSE.txt
254                                 README.txt RELEASE.txt
255                                 os_spawnv_fix.diff scons.1
256                                 script/scons.bat setup.cfg setup.py) ],
257         'filemap'               => {
258                                 'scons.1'       => '../doc/man/scons.1',
259                         },
260
261         'subpkgs'       => [ $python_scons, $scons_script ],
262         'subinst_dirs'  => { "python-$project" => $lib_project,
263                              "$project-script" => 'bin',
264                            },
265 };
266
267 my @src_deps;
268
269 for $p ($scons) {
270     #
271     # Initialize variables with the right directories for this package.
272     #
273     my $pkg = $p->{'pkg'};
274
275     my $src = 'src';
276     $src = File::Spec->catfile($src, $p->{'src_subdir'}) if $p->{'src_subdir'};
277
278     my $build = File::Spec->catfile('build', $pkg);
279
280     my $prefix = $p->{'prefix'};
281     my $install = $prefix;
282     if ($p->{'inst_subdir'}) {
283         $install = File::Spec->catfile($install, $p->{'inst_subdir'});
284     }
285
286     #
287     # Read up the list of source files from our MANIFEST.in.
288     # This list should *not* include LICENSE.txt, MANIFEST,
289     # README.txt, or setup.py.  Make a copy of the list for the
290     # destination files.
291     #
292     my @src_files = cons::read_file("$src/MANIFEST.in");
293     chomp(@src_files);
294     my @dst_files = map(File::Spec->catfile($install, $_), @src_files);
295
296     if ($p->{'subpkgs'}) {
297         #
298         # This package includes some sub-packages.  Read up their
299         # MANIFEST.in files, and add them to our source and destination
300         # file lists, modifying them as appropriate to add the
301         # specified subdirs.
302         #
303         foreach $sp (@{$p->{'subpkgs'}}) {
304             my $ssubdir = $sp->{'src_subdir'};
305             my $isubdir = $p->{'subinst_dirs'}->{$sp->{'pkg'}};
306             my $manifest = File::Spec->catfile($src, $ssubdir, 'MANIFEST.in');
307             my @f = cons::read_file($manifest);
308             chomp(@f);
309             push(@src_files, map(File::Spec->catfile($sp->{'src_subdir'}, $_),
310                                  @f));
311             push(@dst_files, map(File::Spec->catfile($install, $isubdir, $_),
312                                  @f));
313             my $k;
314             foreach $k (keys %{$sp->{'filemap'}}) {
315                 my $f = $sp->{'filemap'}->{$k};
316                 next if ! defined $f;
317                 $k = File::Spec->catfile($sp->{'src_subdir'}, $k); 
318                 $p->{'filemap'}->{$k} = File::Spec->catfile($sp->{'src_subdir'},
319                                                             $f); 
320             }
321         }
322     }
323
324     #
325     # Now that we have the "normal" source files, add those files
326     # that are standard for each distribution.  Note that we don't
327     # add these to dst_files, because they don't get installed.
328     # And we still have the MANIFEST to add.
329     #
330     push(@src_files, @{$p->{'files'}});
331
332     #
333     # Now run everything in src_file through the sed command we
334     # concocted to expand __FILE__, __VERSION__, etc.
335     #
336     foreach $b (@src_files) {
337         my $s = $p->{'filemap'}->{$b} || $b;
338         $env->Command("$build/$b", "$src/$s", "%SEDCOM");
339     }
340
341     #
342     # NOW, finally, we can create the MANIFEST, which we do
343     # by having Perl spit out the contents of the @src_files
344     # array we've carefully created.  After we've added
345     # MANIFEST itself to the array, of course.
346     #
347     push(@src_files, "MANIFEST");
348     $env->Command("$build/MANIFEST", "$src/MANIFEST.in",
349         qq([perl] open(F, ">%>"); print F join("\\n", sort qw(@src_files)), "\\n"; close(F)));
350
351     #
352     # Use the Python distutils to generate the packages.
353     #
354     my $archive = "$build/dist/$pkg-$version.$archsuffix";
355
356     push(@src_deps, $archive);
357
358     my @build_targets = (
359         "$build/dist/$pkg-$version.$platform.$archsuffix",
360         $archive,
361         "$build/dist/$pkg-$version.win32.exe",
362     );
363     my @install_targets = @build_targets;
364
365     # We can get away with calling setup.py using a directory path
366     # like this because we put a preamble in it that will chdir()
367     # to the directory in which setup.py exists.
368     my @bdist_dirs = ("$build/build/lib", "$build/build/scripts");
369     my $commands = qq(rm -rf @bdist_dirs && python $build/setup.py bdist
370                       python $build/setup.py sdist
371                       python $build/setup.py bdist_wininst);
372
373     if ($rpm) {
374         chomp($cwd = `pwd`);
375         $topdir = "$cwd/$build/build/bdist.$platform/rpm";
376
377         $BUILDdir = "$topdir/BUILD/$pkg-$version";
378         $RPMSdir = "$topdir/RPMS/noarch";
379         $SOURCESdir = "$topdir/SOURCES";
380         $SPECSdir = "$topdir/SPECS";
381         $SRPMSdir = "$topdir/SRPMS";
382
383         $specfile = "$SPECSdir/$pkg-$version-1.spec";
384         $sourcefile = "$SOURCESdir/$pkg-$version.$archsuffix";
385         $rpm = "$RPMSdir/$pkg-$version-1.noarch.rpm";
386         $src_rpm = "$SRPMSdir/$pkg-$version-1.src.rpm";
387
388         # We'd like to use the following here:
389         #
390         #       $env->InstallAs($specfile, "rpm/$pkg.spec");
391         #       $env->InstallAs($sourcefile, $archive);
392         #
393         # but it looks like InstallAs doesn't propogate the
394         # signatures correctly, which means that the RPM file
395         # wouldn't always get rebuilt when it should.  Work
396         # around it.
397         use File::Copy;
398         $env->Command($specfile, "rpm/$pkg.spec",
399                       "[perl] File::Copy::copy('%<', '%>')");
400         $env->Command($sourcefile, $archive,
401                       "[perl] File::Copy::copy('%<', '%>')");
402
403         if (! -d $BUILDdir) {
404             $cmd = "mkdir -p $BUILDdir; ";
405         }
406         my @targets = ( $rpm, $src_rpm );
407         $env->Command(\@targets, $specfile,
408                         "${cmd}rpm --define '_topdir $topdir' -ba %<");
409         $env->Depends(\@targets, $sourcefile);
410
411         push(@install_targets, @targets);
412     };
413
414     @build_src_files = map("$build/$_", @src_files);
415
416     if ($dh_builddeb && $fakeroot) {
417         # Debian builds directly into build/dist, so we don't
418         # need to add the .debs to the install_targets.
419         my $deb = "build/dist/${pkg}_$version-1_all.deb";
420         $env->Command($deb, @build_src_files, qq(
421                 fakeroot make -f debian/rules VERSION=%VERSION DH_COMPAT=$DH_COMPAT ENVOKED_BY_CONSTRUCT=1 binary-$pkg
422                 env DH_COMPAT=$DH_COMPAT dh_clean));
423         $env->Depends($deb, @{$p->{'debian_deps'}});
424     }
425
426     #
427     # Now set up creation and installation of the packages.
428     #
429     $env->Command([@build_targets], @build_src_files, $commands);
430
431     $env->Install("build/dist", @install_targets);
432
433     #
434     # Unpack the archive created by the distutils into build/unpack.
435     #
436     my @unpack_files = map("$unpack_dir/$pkg-$version/$_", @src_files);
437
438     # We'd like to replace the last three lines with the following:
439     #
440     #   tar zxf %< -C $unpack_dir
441     #
442     # but that gives heartburn to Cygwin's tar, so work around it
443     # with separate zcat-tar-rm commands.
444     Command $env [@unpack_files], $archive, qq(
445         rm -rf $unpack_dir/$pkg-$version
446         zcat %< > .temp
447         tar xf .temp -C $unpack_dir
448         rm -f .temp
449     );
450
451     #
452     # Run setup.py in the unpacked subdirectory to "install" everything
453     # into our build/test subdirectory.  Auxiliary modules that we need
454     # (TestCmd.py, TestSCons.py, unittest.py) will be copied in by
455     # etc/Conscript.  The runtest.py script will set PYTHONPATH so that
456     # the tests only look under build/test.  This makes sure that our
457     # tests pass with what we really packaged, not because of something
458     # hanging around in the development directory.
459     #
460     # We can get away with calling setup.py using a directory path
461     # like this because we put a preamble in it that will chdir()
462     # to the directory in which setup.py exists.
463     Command $env [@dst_files], @unpack_files, qq(
464         rm -rf $install
465         python $unpack_dir/$pkg-$version/setup.py install --prefix=$prefix
466     );
467 }
468
469 #
470 # Arrange for supporting packages to be installed in the test directories.
471 #
472 Export qw( env );
473
474 Build "etc/Conscript";
475
476 #
477 # Documentation.
478 #
479 Link 'build/doc' => 'doc';
480
481 Build 'build/doc/Conscript';
482
483
484 #
485 # If we're running in the actual Aegis project, pack up a complete
486 # source archive from the project files and files in the change,
487 # so we can share it with helpful developers who don't use Aegis.
488 #
489 # First, lie and say that we've seen any files removed by this
490 # change, so they don't get added to the source files list
491 # that goes into the archive.
492 #
493
494 if ($change) {
495     foreach (`aegis -list -unf -c $change cf 2>/dev/null`) {
496         $seen{"$1\n"}++ if /^(?:source|test) remove(?:\s.*)+\s(\S+)$/;
497     }
498
499     eval '@src_files = grep(! $seen{$_}++,
500                     `aegis -list -terse pf 2>/dev/null`,
501                     `aegis -list -terse cf 2>/dev/null`)';
502
503     @src_files = grep($_ !~ /(\.aeignore|\.consign)$/, @src_files);
504
505     if (@src_files) {
506         chomp(@src_files);
507
508         foreach $file (@src_files) {
509             $env->Command("build/$project-src/$file", 
510                           $file, 
511                           qq(%SEDCOM
512                              chmod --reference=%< %>)
513             );
514         }
515
516         $env->Command("build/dist/$project-src-$version.tar.gz",
517                       @src_deps,
518                       map("build/$project-src/$_", @src_files),
519                       qq(
520                 rm -rf build/$project-src-$version
521                 cp -rp build/$project-src build/$project-src-$version
522                 find build/$project-src-$version -name .consign -exec rm {} \\;
523                 tar zcf %> -C build $project-src-$version
524         ));
525     }
526 }