Subversion Repositories gelsvn

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
218 bj 1
eval '(exit $?0)' && eval 'exec perl -w -S $0 ${1+"$@"}'
2
    & eval 'exec perl -w -S $0 $argv:q'
3
    if 0;
4
 
5
# ******************************************************************
6
#      Author: Chad Elliott
7
#        Date: 4/8/2004
8
#         $Id: clone_build_tree.pl,v 1.13 2006/02/02 13:04:19 elliott_c Exp $
9
# Description: Clone a build tree into an alternate location.
10
#              This script is a rewrite of create_ace_build.pl and
11
#              does not restrict the user to place the build
12
#              in any particular location or that it be used with
13
#              ACE_wrappers.  Some of the functions were barrowed
14
#              from create_ace_build.pl, but were modified quite a
15
#              bit.
16
# ******************************************************************
17
 
18
# ******************************************************************
19
# Pragma Section
20
# ******************************************************************
21
 
22
use strict;
23
use Cwd;
24
use FileHandle;
25
use File::Copy;
26
use File::Find;
27
use File::Path;
28
use File::stat;
29
use File::Basename;
30
 
31
# ******************************************************************
32
# Data Section
33
# ******************************************************************
34
 
35
my($exclude)    = undef;
36
my($verbose)    = 0;
37
my($lbuildf)    = 0;
38
my(@foundFiles) = ();
39
my($version)    = '$Id: clone_build_tree.pl,v 1.13 2006/02/02 13:04:19 elliott_c Exp $';
40
$version =~ s/.*\s+(\d+[\.\d]+)\s+.*/$1/;
41
 
42
eval 'symlink("", "");';
43
my($hasSymlink) = ($@ eq '');
44
 
45
# ******************************************************************
46
# Subroutine Section
47
# ******************************************************************
48
 
49
sub findCallback {
50
  my($matches) = !(/^CVS\z/s && ($File::Find::prune = 1)            ||
51
                   /^\.svn\z/s && ($File::Find::prune = 1)          ||
52
                   defined $exclude &&
53
                   /^$exclude\z/s && ($File::Find::prune = 1)       ||
54
                   /^\.cvsignore\z/s && ($File::Find::prune = 1)    ||
55
                   /^build\z/s && ($File::Find::prune = 1)          ||
56
                   /^\..*obj\z/s && ($File::Find::prune = 1)        ||
57
                   /^Templates\.DB\z/s && ($File::Find::prune = 1)  ||
58
                   /^Debug\z/s && ($File::Find::prune = 1)          ||
59
                   /^Release\z/s && ($File::Find::prune = 1)        ||
60
                   /^Static_Debug\z/s && ($File::Find::prune = 1)   ||
61
                   /^Static_Release\z/s && ($File::Find::prune = 1)
62
                  );
63
 
64
  if ($matches) {
65
    $matches &&= (! -l $_           &&
66
                  ! /^core\z/s      &&
67
                  ! /^.*\.rej\z/s   &&
68
                  ! /^.*\.state\z/s &&
69
                  ! /^.*\.so\z/s    &&
70
                  ! /^.*\.[oa]\z/s  &&
71
                  ! /^.*\.dll\z/s   &&
72
                  ! /^.*\.lib\z/s   &&
73
                  ! /^.*\.obj\z/s   &&
74
                  ! /^.*~\z/s       &&
75
                  ! /^\.\z/s        &&
76
                  ! /^\.#.*\z/s     &&
77
                  ! /^.*\.ncb\z/s   &&
78
                  ! /^.*\.opt\z/s   &&
79
                  ! /^.*\.bak\z/s   &&
80
                  ! /^.*\.suo\z/s   &&
81
                  ! /^.*\.ilk\z/s   &&
82
                  ! /^.*\.pdb\z/s   &&
83
                  ! /^.*\.pch\z/s   &&
84
                  ! /^.*\.log\z/s
85
                 );
86
 
87
    if ($matches) {
88
      if (!$lbuildf) {
89
        $matches = (! /^.*\.dsp\z/s       &&
90
                    ! /^.*\.dsw\z/s       &&
91
                    ! /^.*\.vcproj\z/s    &&
92
                    ! /^.*\.sln\z/s       &&
93
                    ! /^Makefile.*\z/s    &&
94
                    ! /^GNUmakefile.*\z/s &&
95
                    ! /^.*\.am\z/s        &&
96
                    ! /^\.depend\..*\z/s  &&
97
                    ! /^.*\.vcn\z/s       &&
98
                    ! /^.*\.vcp\z/s       &&
99
                    ! /^.*\.vcw\z/s       &&
100
                    ! /^.*\.vpj\z/s       &&
101
                    ! /^.*\.vpw\z/s       &&
102
                    ! /^.*\.cbx\z/s       &&
103
                    ! /^.*\.bpgr\z/s      &&
104
                    ! /^.*\.bmak\z/s      &&
105
                    ! /^.*\.bmake\z/s     &&
106
                    ! /^.*\.mak\z/s       &&
107
                    ! /^.*\.nmake\z/s     &&
108
                    ! /^.*\.bld\z/s       &&
109
                    ! /^.*\.icc\z/s       &&
110
                    ! /^.*\.icp\z/s
111
                  );
112
      }
113
 
114
      if ($matches) {
115
        ## Remove the beginning dot slash as we save the file
116
        push(@foundFiles, $File::Find::name);
117
        $foundFiles[$#foundFiles] =~ s/^\.[\\\/]+//;
118
      }
119
    }
120
  }
121
}
122
 
123
 
124
sub getFileList {
125
  File::Find::find({wanted => \&findCallback}, '.');
126
  return \@foundFiles;
127
}
128
 
129
 
130
sub backupAndMoveModified {
131
  my($realpath) = shift;
132
  my($linkpath) = shift;
133
  my($mltime)   = -M $linkpath;
134
  my($mrtime)   = -M $realpath;
135
  my($status)   = 1;
136
 
137
  ## -M returns the number of days since modification.  Therefore,
138
  ## a smaller time means that it has been modified more recently.
139
  ## This is different than what stat() returns.
140
  if ($mltime < $mrtime) {
141
    $status = 0;
142
 
143
    ## Move the real file to a backup
144
    unlink("$realpath.bak");
145
    if (rename($realpath, "$realpath.bak")) {
146
      ## Move the linked file to the real file name
147
      if (move($linkpath, $realpath)) {
148
        $status = 1;
149
      }
150
      else {
151
        ## The move failed, so we will attempt to put
152
        ## the original file back.
153
        unlink($realpath);
154
        rename("$realpath.bak", $realpath);
155
      }
156
    }
157
  }
158
  elsif ($mltime != $mrtime) {
159
    $status = 0;
160
  }
161
  elsif (-s $linkpath != -s $realpath) {
162
    $status = 0;
163
  }
164
 
165
  if (!$status) {
166
    ## We were not able to properly deal with this file.  We will
167
    ## attempt to preserve the modified file.
168
    unlink("$linkpath.bak");
169
    rename($linkpath, "$linkpath.bak");
170
  }
171
}
172
 
173
 
174
sub hardlink {
175
  my($realpath) = shift;
176
  my($linkpath) = shift;
177
 
178
  if ($^O eq 'MSWin32' && ! -e $realpath) {
179
    ## If the real file "doesn't exist", then we need to
180
    ## look up the short file name.
181
    my($short) = Win32::GetShortPathName($realpath);
182
 
183
    ## If we were able to find the short file name, then we need to
184
    ## try again.
185
    if (defined $short) {
186
      $realpath = $short;
187
    }
188
    else {
189
      ## This should never happen, but there appears to be a bug
190
      ## with the underlying Win32 APIs on Windows Server 2003.
191
      ## Long paths will cause an error which perl will ignore.
192
      ## Unicode versions of the APIs seem to work fine.
193
      ## To experiment try Win32 _fullpath() and CreateHardLink with
194
      ## long paths.
195
      print "WARNING: Skipping $realpath.\n";
196
      return 1;
197
    }
198
  }
199
 
200
  return link($realpath, $linkpath);
201
}
202
 
203
 
204
sub symlinkFiles {
205
  my($files)     = shift;
206
  my($fullbuild) = shift;
207
  my($dmode)     = shift;
208
  my($startdir)  = shift;
209
  my($absolute)  = shift;
210
  my($sdlength)  = length($startdir) + 1;
211
  my($partial)   = ($absolute ? undef :
212
                                substr($fullbuild, $sdlength,
213
                                       length($fullbuild) - $sdlength));
214
 
215
  foreach my $file (@$files) {
216
    my($fullpath) = "$fullbuild/$file";
217
    if (-e $fullpath) {
218
      ## We need to make sure that we're not attempting to mix hardlinks
219
      ## and softlinks.
220
      if (! -d $fullpath && ! -l $fullpath) {
221
        my($stat) = stat($fullpath);
222
        if ($stat->nlink() > 1) {
223
          print STDERR "ERROR: Attempting to mix softlinks ",
224
                       "with a hardlink build.\n",
225
                       "$fullpath has ", $stat->nlink(), " links.\n";
226
          return 1;
227
        }
228
      }
229
    }
230
    else {
231
      if (-d $file) {
232
        if ($verbose) {
233
          print "Creating $fullpath\n";
234
        }
235
        if (!mkpath($fullpath, 0, $dmode)) {
236
          return 1;
237
        }
238
      }
239
      else {
240
        if ($absolute) {
241
          if ($verbose) {
242
            print "symlink $startdir/$file $fullpath\n";
243
          }
244
          if (!symlink("$startdir/$file", $fullpath)) {
245
            return 1;
246
          }
247
        }
248
        else {
249
          my($buildfile) = "$partial/$file";
250
          my($slashcount) = ($buildfile =~ tr/\///);
251
          my($real) = ($slashcount == 0 ? './' : ('../' x $slashcount)) .
252
                      $file;
253
          if ($verbose) {
254
            print "symlink $real $fullpath\n";
255
          }
256
          if (!symlink($real, $fullpath)) {
257
            return 1;
258
          }
259
        }
260
      }
261
    }
262
  }
263
 
264
  ## Remove links that point to non-existant files
265
  sub lcheck {
266
    if (-l $_ && ! -e $_) {
267
      unlink($_);
268
      if ($verbose) {
269
        print "Removing $File::Find::dir/$_\n";
270
      }
271
    }
272
  }
273
  File::Find::find({wanted => \&lcheck}, $fullbuild);
274
 
275
  return 0;
276
}
277
 
278
 
279
sub hardlinkFiles {
280
  my($files)     = shift;
281
  my($fullbuild) = shift;
282
  my($dmode)     = shift;
283
  my($startdir)  = shift;
284
  my(@hardlinks) = ();
285
 
286
  foreach my $file (@$files) {
287
    my($fullpath) = "$fullbuild/$file";
288
    if (-d $file) {
289
      if (! -e $fullpath) {
290
        if ($verbose) {
291
          print "Creating $fullpath\n";
292
        }
293
        if (!mkpath($fullpath, 0, $dmode)) {
294
          return 1;
295
        }
296
      }
297
    }
298
    else {
299
      if (-e $fullpath) {
300
        ## We need to make sure that we're not attempting to mix hardlinks
301
        ## and softlinks.
302
        if (-l $fullpath) {
303
          print STDERR "ERROR: Attempting to mix hardlinks ",
304
                       "with a softlink build.\n",
305
                       "$fullpath is a softlink.\n";
306
          return 1;
307
        }
308
        backupAndMoveModified($file, $fullpath);
309
      }
310
      if (! -e $fullpath) {
311
        if ($verbose) {
312
          print "hardlink $file $fullpath\n";
313
        }
314
        if (!hardlink($file, $fullpath)) {
315
          return 1;
316
        }
317
      }
318
 
319
      ## If we successfully linked the file or it already exists,
320
      ## we need to keep track of it.
321
      push(@hardlinks, $file);
322
    }
323
  }
324
 
325
  ## Remove links that point to non-existant files
326
  my($lfh) = new FileHandle();
327
  my($txt) = "$fullbuild/clone_build_tree.links";
328
  if (open($lfh, $txt)) {
329
    while(<$lfh>) {
330
      my($line) = $_;
331
      $line =~ s/\s+$//;
332
      if (! -e $line) {
333
        unlink("$fullbuild/$line");
334
        if ($verbose) {
335
          print "Removing $fullbuild/$line\n";
336
        }
337
      }
338
    }
339
    close($lfh);
340
  }
341
 
342
  ## Rewrite the link file.
343
  unlink($txt);
344
  if (open($lfh, ">$txt")) {
345
    foreach my $file (@hardlinks) {
346
      print $lfh "$file\n";
347
    }
348
    close($lfh);
349
  }
350
 
351
  return 0;
352
}
353
 
354
 
355
sub linkFiles {
356
  my($absolute)  = shift;
357
  my($dmode)     = shift;
358
  my($hardlink)  = shift;
359
  my($builddir)  = shift;
360
  my($builds)    = shift;
361
  my($status)    = 0;
362
  my($starttime) = time();
363
  my($startdir)  = getcwd();
364
 
365
  ## Ensure that the build directory exists and is writable
366
  mkpath($builddir, 0, $dmode);
367
  if (! -d $builddir || ! -w $builddir) {
368
    return 1;
369
  }
370
 
371
  ## Search for the clonable files
372
  print "Searching $startdir for files...\n";
373
  my($files) = getFileList();
374
  my($findtime) = time() - $starttime;
375
  print 'Found ', scalar(@$files), ' files and directories in ',
376
        $findtime, ' second', ($findtime == 1 ? '' : 's'), ".\n";
377
 
378
  foreach my $build (@$builds) {
379
    my($fullbuild) = "$builddir/$build";
380
 
381
    ## Create all of the links for this build
382
    if (-d $fullbuild) {
383
      print "Updating $fullbuild\n";
384
    }
385
    else {
386
      print "Creating $fullbuild\n";
387
      mkpath($fullbuild, 0, $dmode);
388
    }
389
 
390
    if ($hardlink) {
391
      $status += hardlinkFiles($files, $fullbuild, $dmode, $startdir);
392
    }
393
    else {
394
      $status += symlinkFiles($files, $fullbuild,
395
                              $dmode, $startdir, $absolute);
396
    }
397
    print "Finished in $fullbuild\n";
398
  }
399
 
400
  if ($status == 0) {
401
    print 'Total time: ', time() - $starttime, " seconds.\n";
402
  }
403
 
404
  return $status;
405
}
406
 
407
 
408
sub usageAndExit {
409
  my($msg) = shift;
410
  if (defined $msg) {
411
    print STDERR "$msg\n";
412
  }
413
  my($base) = basename($0);
414
  my($spc)  = ' ' x (length($base) + 8);
415
 
416
  print STDERR "$base v$version\n\n",
417
               "Create a tree identical in layout to the current directory\n",
418
               "with the use of ", ($hasSymlink ? "symbolic links or " : ''),
419
               "hard links.\n\n",
420
               "Usage: $base [-b <builddir>] [-d <dmode>] [-f] ",
421
               ($hasSymlink ? "[-a] [-l] " : ''),
422
               "[-v]\n",
423
               $spc, "[build names...]\n\n",
424
               ($hasSymlink ?
425
               "-a  Use absolute paths when creating soft links.\n" .
426
               "-l  Use hard links instead of soft links.\n" : ''),
427
               "-b  Set the build directory. It defaults to the ",
428
               "<current directory>/build.\n",
429
               "-d  Set the directory permissions mode.\n",
430
               "-f  Link build files (Makefile, .dsw, .sln, .etc).\n",
431
               "-v  Enable verbose mode.\n";
432
 
433
  exit(0);
434
}
435
 
436
 
437
# ******************************************************************
438
# Main Section
439
# ******************************************************************
440
 
441
my($dmode)    = 0777;
442
my($absolute) = 0;
443
my($hardlink) = !$hasSymlink;
444
my($builddir) = getcwd() . '/build';
445
my(@builds)   = ();
446
 
447
for(my $i = 0; $i <= $#ARGV; ++$i) {
448
  if ($ARGV[$i] eq '-a') {
449
    $absolute = 1;
450
  }
451
  elsif ($ARGV[$i] eq '-b') {
452
    ++$i;
453
    if (defined $ARGV[$i]) {
454
      $builddir = $ARGV[$i];
455
 
456
      ## Convert backslashes to slashes
457
      $builddir =~ s/\\/\//g;
458
 
459
      ## Remove trailing slashes
460
      $builddir =~ s/\/+$//;
461
 
462
      ## Remove duplicate slashes
463
      while($builddir =~ s/\/\//\//g) {
464
      }
465
    }
466
    else {
467
      usageAndExit('-b requires an argument');
468
    }
469
  }
470
  elsif ($ARGV[$i] eq '-d') {
471
    ++$i;
472
    if (defined $ARGV[$i]) {
473
      $dmode = $ARGV[$i];
474
    }
475
    else {
476
      usageAndExit('-d requires an argument');
477
    }
478
  }
479
  elsif ($ARGV[$i] eq '-f') {
480
    $lbuildf = 1;
481
  }
482
  elsif ($ARGV[$i] eq '-l') {
483
    $hardlink = 1;
484
  }
485
  elsif ($ARGV[$i] eq '-v') {
486
    $verbose = 1;
487
  }
488
  elsif ($ARGV[$i] =~ /^-/) {
489
    usageAndExit('Unknown option: ' . $ARGV[$i]);
490
  }
491
  else {
492
    push(@builds, $ARGV[$i]);
493
  }
494
}
495
 
496
if (index($builddir, getcwd()) == 0) {
497
  $exclude = substr($builddir, length(getcwd()) + 1);
498
  $exclude =~ s/([\+\-\\\$\[\]\(\)\.])/\\$1/g;
499
}
500
else {
501
  $absolute = 1;
502
}
503
 
504
if (!defined $builds[0]) {
505
  my($cwd) = getcwd();
506
  if (chdir($builddir)) {
507
    @builds = glob('*');
508
    chdir($cwd);
509
  }
510
  else {
511
    usageAndExit('There are no builds to update.');
512
  }
513
}
514
 
515
exit(linkFiles($absolute, $dmode, $hardlink, $builddir, \@builds));