ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/SCRAM/src/Cache/Cache.pm
Revision: 1.7.2.2
Committed: Thu Nov 8 15:25:28 2007 UTC (17 years, 6 months ago) by muzaffar
Content type: text/plain
Branch: v103_with_xml
CVS Tags: forV1_1_0
Changes since 1.7.2.1: +197 -183 lines
Log Message:
updated the new scram in the v103_with_xml branch

File Contents

# Content
1 #____________________________________________________________________
2 # File: Cache.pm
3 #____________________________________________________________________
4 #
5 # Author: Shaun Ashby <Shaun.Ashby@cern.ch>
6 # (with contribution from Lassi.Tuura@cern.ch)
7 # Update: 2003-11-27 16:45:18+0100
8 # Revision: $Id: Cache.pm,v 1.9 2007/11/06 14:13:51 muzaffar Exp $
9 #
10 # Copyright: 2003 (C) Shaun Ashby
11 #
12 #--------------------------------------------------------------------
13
14 =head1 NAME
15
16 Cache::Cache - A generic directory cache object.
17
18 =head1 SYNOPSIS
19
20 my $cacheobject=Cache::Cache->new();
21
22 =head1 DESCRIPTION
23
24 A package to provide caching of directory information. Directory timestamps
25 are tracked on further reading of an existing cache and lists of modified
26 directories and BuildFiles can be obtained.
27
28 =head1 METHODS
29
30 =over
31
32 =cut
33
34 package Cache::Cache;
35 require 5.004;
36
37 use Exporter;
38 use Utilities::AddDir;
39 @ISA=qw(Exporter);
40 #
41
42 =item C<new()>
43
44 Create a new Cache::Cache object. The name of the cache is B<DirCache.db> by default.
45
46 =cut
47
48 sub new()
49 ###############################################################
50 # new #
51 ###############################################################
52 # modified : Thu Nov 27 16:45:27 2003 / SFA #
53 # params : #
54 # : #
55 # function : #
56 # : #
57 ###############################################################
58 {
59 my $proto=shift;
60 my $class=ref($proto) || $proto;
61 my $self=
62 {
63 CACHENAME => "DirCache.db", # Name of global file/dir cache;
64 BFCACHE => {}, # BuildFile cache;
65 DIRCACHE => {}, # Source code cache;
66 EXTRASUFFIX => {}, # path with extra suffix;
67 STATUS => 0, # Status of cache: 1 => something changed. If so, force save;
68 VERBOSE => 0 # Verbose mode (0/1);
69 };
70
71 bless $self,$class;
72 return $self;
73 }
74
75 =item C<getdir($path)>
76
77 Return a list of directories starting from $path.
78
79 =cut
80
81 sub getdir()
82 {
83 my $self=shift;
84 my $path=shift;
85
86 opendir (DIR, $path) || die "$path: cannot read: $!\n";
87 # Skip .admin and CVS subdirectories too.
88 # Also skip files that look like backup files or files being modified with emacs:
89 my @items = map { "$path/$_" } grep ((-d "$path/$_") && ($_!~/^(CVS|\..*)$/),readdir(DIR));
90 closedir (DIR);
91 return @items;
92 }
93
94 =item C<prune($path)>
95
96 Recursively remove directories from the cache starting at $path.
97
98 =cut
99
100 sub prune()
101 {
102 my $self=shift;
103 my $path = shift;
104 my $skipparent = shift || 0;
105 my $suffix = shift || "";
106 $self->extra_suffix($path,$suffix) if ($suffix);
107 if (!$skipparent)
108 {
109 my $parent = $path;
110 $parent =~ s|(.*)/[^/]+$|$1|;
111 if ($parent ne $path && exists $self->{DIRCACHE}{$parent})
112 {
113 my ($time, @subs) = @{$self->{DIRCACHE}{$parent}};
114 $self->{DIRCACHE}{$parent} = [ $time, grep ($_ ne $path, @subs) ];
115 $self->{ADDEDDIR}{$parent}=1;
116 $self->cachestatus(1);
117 }
118 }
119 if (exists $self->{ADDEDDIR}{$path}){delete $self->{ADDEDDIR}{$path};}
120 return if ! exists $self->{DIRCACHE}{$path};
121 $self->cachestatus(1);
122 foreach my $bf ("$ENV{SCRAM_BUILDFILE}.xml","$ENV{SCRAM_BUILDFILE}")
123 {
124 if (exists $self->{BFCACHE}{"${path}/${bf}"})
125 {
126 if (!-f "${path}/${bf}") {$self->{REMOVEDBF}{"${path}/${bf}"}=1;}
127 delete $self->{BFCACHE}{"${path}/${bf}"};
128 if (exists $self->{ADDEDBF}{"${path}/${bf}"}){delete $self->{ADDEDBF}{"${path}/${bf}"};}
129 last;
130 }
131 }
132 if (!-d $path) {$self->{REMOVEDDIR}{$path}=1;}
133 my (undef, @subs) = @{$self->{DIRCACHE}{$path}};
134 delete $self->{DIRCACHE}{$path};
135 foreach my $sub (@subs)
136 {
137 $self->prune($sub,1);
138 }
139 }
140
141 =item C<checktree($path, $required, $dofiles)>
142
143 A timestamp checking routine. Starting from $path, check all timestamps of
144 directories and their files. Skip all files unless $dofiles is 1.
145
146 =cut
147
148 sub checktree()
149 {
150 my ($self, $path, $required) = @_;
151 # Check if this path needs to be checked. If it exists, has the same mode
152 # and the same time stamp, it's up to date and doesn't need to be checked.
153 # Otherwise if it is a directory whose time-stamp has changed, rescan it.
154 # If the path has be removed, prune it from the cache. Note that we skip
155 # non-directories unless $dofiles is set. Considering only directories is
156 # dramatically faster.
157
158 # NB: We stat each path only once ever. The special "_" file handle uses
159 # the results from the last stat we've made. See man perlfunc/stat.
160 if (! stat($path))
161 {
162 die "$path: $!\n" if $required;
163 $self->prune($path);
164 return;
165 }
166
167 # If the entry in the cache is not the same mode or time, force an update.
168 # Otherwise use the cache as the list of items we need to change.
169 my $cached = $self->{DIRCACHE}{$path};
170 my @items = ();
171
172 if (! -d _)
173 {
174 $self->prune($path);
175 return;
176 }
177 elsif (! $cached)
178 {
179 # When a directory is added, this block is activated
180 $self->{ADDEDDIR}{$path}=1;
181 $self->{DIRCACHE}{$path} = [ (stat(_))[9], @items = $self->getdir($path) ];
182 $required = 1;
183 $self->cachestatus(1);
184 }
185 elsif ($cached->[0] != (stat(_))[9])
186 {
187 my $ntime = (stat(_))[9];
188 # When a subdirectory is removed, this block is activated
189 #
190 # This is a parent directory. We store this as any
191 # update can be taken recursively from this dir:
192 #$self->modified_parentdirs($path);
193 # Current subdirs:
194 my %curdirs = map { $_ => 1 } $self->getdir($path);
195 my %olddirs = ();
196 for (my $i = 1; $i <= $#$cached; $i++)
197 {
198 my $d = $cached->[$i];
199 $olddirs{$d}=1;
200 if (!exists $curdirs{$d})
201 {
202 $self->prune($d,1);
203 }
204 }
205
206 foreach my $d (keys %curdirs)
207 {
208 if (!exists $olddirs{$d})
209 {
210 if ($self->extra_suffix($d))
211 {
212 delete $curdirs{$d};
213 }
214 }
215 }
216
217 $self->{ADDEDDIR}{$path}=1;
218 $self->cachestatus(1);
219 @items = keys %curdirs;
220 $required = 0;
221 $self->{DIRCACHE}{$path} = [ $ntime, @items ];
222 }
223 else
224 {
225 (undef, @items) = @{$self->{DIRCACHE}{$path}};
226 $required = 0;
227 }
228 if (($self->{cachereset}) && (!exists $self->{ADDEDDIR}{$path}))
229 {
230 $self->{ADDEDDIR}{$path}=1;
231 $self->cachestatus(1);
232 }
233
234 my $bfcachedir=$ENV{LOCALTOP}."/".$ENV{SCRAM_TMP}."/".$ENV{SCRAM_ARCH}."/cache/bf/${path}";
235 my $bftime=0;
236 my $bf="${path}/$ENV{SCRAM_BUILDFILE}";
237 if (($self->{convertxml}) && (!-f "${bf}.xml") && (-f "$bf"))
238 {
239 my $fref;
240 $self->{nonxml} = $self->{nonxml}+1;
241 if (open($fref,">${bf}.xml"))
242 {
243 print ">> Converting $bf => ${bf}.xml\n";
244 $self->{convertxml}->clean();
245 my $xml=$self->{convertxml}->convert($bf);
246 foreach my $line (@$xml){print $fref "$line\n";}
247 close($fref);
248 $self->{convertxml}->clean();
249 }
250 else
251 {
252 print STDERR "**** WARNING: Can not open file for writing: ${bf}.xml\n";
253 }
254 }
255 foreach my $ext (".xml","")
256 {
257 my $bfn="$bf$ext";
258 if (! stat ($bfn))
259 {
260 if (exists $self->{BFCACHE}{$bfn})
261 {
262 $self->{REMOVEDBF}{$bfn}=1;
263 delete $self->{BFCACHE}{$bfn};
264 AddDir::adddir($bfcachedir);
265 open(BF,">${bfcachedir}/$ENV{SCRAM_BUILDFILE}");close(BF);
266 $self->cachestatus(1);
267 }
268 }
269 else
270 {
271 $bftime = (stat(_))[9];
272 if ((! exists $self->{BFCACHE}{$bfn}) ||
273 ($bftime != $self->{BFCACHE}{$bfn}))
274 {
275 AddDir::adddir($bfcachedir);
276 open(BF,">${bfcachedir}/$ENV{SCRAM_BUILDFILE}");close(BF);
277 $self->{ADDEDBF}{$bfn}=1;
278 delete $self->{BFCACHE}{"${path}/$ENV{SCRAM_BUILDFILE}"};
279 $self->{BFCACHE}{$bfn}=$bftime;
280 if ($ext eq ""){$self->{nonxml}+=1;}
281 $self->cachestatus(1);
282 }
283 elsif($self->{cachereset})
284 {
285 $self->{ADDEDBF}{$bfn}=1;
286 if ($ext eq ""){$self->{nonxml}+=1;}
287 if (!-f "${bfcachedir}/$ENV{SCRAM_BUILDFILE}")
288 {
289 AddDir::adddir($bfcachedir);
290 open(BF,">${bfcachedir}/$ENV{SCRAM_BUILDFILE}");close(BF);
291 }
292 $self->cachestatus(1);
293 }
294 last;
295 }
296 }
297 # Process sub-directories
298 foreach my $item (@items)
299 {
300 $self->checktree($item, $required);
301 }
302 }
303
304 =item C<clean_cache_recursive($startdir)>
305
306 Recursive remove cached data for directories under $startdir.
307
308 =cut
309
310 sub clean_cache_recursive()
311 {
312 my $self=shift;
313 my ($startdir) = @_;
314 my $children = $self->{DIRCACHE}->{$startdir};
315
316 for (my $i = 2; $i <= $#$children; $i++)
317 {
318 # Remove all children:
319 $self->schedremoval($children->[$i]);
320 $self->clean_cache_recursive($children->[$i]);
321 }
322
323 delete $self->{DIRCACHE}->{$startdir};
324 return $self;
325 }
326
327 =item C<dirtree($dir,$dofiles)>
328
329 Starting from $dir, scan the directory tree. Ignore files unless $dofiles is set. This
330 function just calls checktree().
331
332 =cut
333
334 sub dirtree()
335 {
336 my $self=shift;
337 my ($dir) = @_;
338
339 # Get the directory tree:
340 $self->checktree($dir, 1);
341 return $self;
342 }
343
344 =item C<checkfiles()>
345
346 Function to actually run the timestamp checks. This is only run from
347 SCRAM::CMD::build().
348
349 =cut
350
351 sub checkfiles()
352 {
353 my $self=shift;
354 $self->{cachereset}=shift || 0;
355 $self->{convertxml}=shift || 0;
356 # Scan config dir for top-level data, then start from src:
357 my @scandirs=($ENV{SCRAM_CONFIGDIR}, $ENV{SCRAM_SOURCEDIR});
358 # Loop over all directories that need scanning (normally just src and config):
359 if ($self->{convertxml})
360 {
361 eval ("use SCRAM::Doc2XML");
362 if (!$@)
363 {
364 $self->{convertxml} = SCRAM::Doc2XML->new();
365 }
366 else
367 {
368 print STDERR "**** WARNING: Can not convert $ENV{SCRAM_BUILDFILE} in to XML format. Missing SCRAM::Doc2XML perl module.\n";
369 }
370 }
371 $self->{nonxml}=0;
372 foreach my $scand (@scandirs)
373 {
374 $self->logmsg("SCRAM: Scanning $scand [dofiles set to ".$dofiles."]\n");
375 # Check the directory tree:
376 $self->dirtree($scand);
377 }
378 if ($self->cachestatus())
379 {
380 foreach my $bf ("$ENV{SCRAM_BUILDFILE}.xml","$ENV{SCRAM_BUILDFILE}")
381 {
382 if (exists $self->{BFCACHE}{"$ENV{SCRAM_CONFIGDIR}/${bf}"})
383 {
384 $self->{ADDEDBF}{"$ENV{SCRAM_CONFIGDIR}/${bf}"}=1;
385 last;
386 }
387 }
388 }
389 if ($self->{nonxml} > 0)
390 {
391 print STDERR "**** WARNING: ",$self->{nonxml}," non-xml based $ENV{SCRAM_BUILDFILE} were read.\n";
392 }
393 return $self;
394 }
395
396 =item C<dircache()>
397
398 Return a reference to the directory cache hash.
399
400 =cut
401
402 sub dircache()
403 {
404 my $self=shift;
405 # Return the file cache:
406 return $self->{DIRCACHE};
407 }
408
409 =item C<added_dirs($path)>
410
411 Add $path to the list of directories added since last scan, or return
412 the list of added directories if no argument given.
413
414 =cut
415
416 sub added_dirs()
417 {
418 my $self=shift;
419 my ($path) = @_;
420
421 # If we have a path to add, add it.
422 if ($path)
423 {
424 if (exists($self->{ADDEDDIRS}))
425 {
426 push(@{$self->{ADDEDDIRS}}, $path);
427 }
428 else
429 {
430 $self->{ADDEDDIRS} = [ $path ];
431 }
432 }
433 else
434 {
435 # Otherwise, return the array of added dirs:
436 my @addeddirs = @{$self->{ADDEDDIRS}};
437 delete $self->{ADDEDDIRS};
438 return \@addeddirs;
439 }
440 }
441
442 =item C<modified_parentdirs($path)>
443
444 Add a directory $path to the list of parent directories (directories
445 having subdirectories), or return a reference to the list.
446 Storing this parent allows any update to be taken recursively from this
447 location.
448
449 =cut
450
451 sub modified_parentdirs()
452 {
453 my $self=shift;
454 my ($path) = @_;
455
456 # If we have a path to add, add it.
457 # Don't bother if it's the main source dir as we don't
458 # want to rescan everything from src (that would be silly):
459 if ($path && $path ne $ENV{SCRAM_SOURCEDIR})
460 {
461 if (exists($self->{MODPARENTDIRS}))
462 {
463 push(@{$self->{MODPARENTDIRS}}, $path);
464 }
465 else
466 {
467 $self->{MODPARENTDIRS} = [ $path ];
468 }
469 }
470 else
471 {
472 # Otherwise, return the array of added dirs:
473 my @moddeddirs = @{$self->{MODPARENTDIRS}};
474 delete $self->{MODPARENTDIRS};
475 return \@moddeddirs;
476 }
477 }
478
479 =item C<schedremoval($d)>
480
481 Add directory $d to list of directories that should be removed
482 recursively from the cache.
483 If no arguments given, return a reference to a list of
484 directories to be removed.
485
486 =cut
487
488 sub schedremoval()
489 {
490 my $self=shift;
491 my ($d)=@_;
492
493 if ($d)
494 {
495 if (exists($self->{REMOVEDATA}))
496 {
497 push(@{$self->{REMOVEDATA}},$d);
498 }
499 else
500 {
501 $self->{REMOVEDATA} = [ $d ];
502 }
503 }
504 else
505 {
506 my $remove = [ @{$self->{REMOVEDATA}} ];
507 $self->{REMOVEDATA} = [];
508 return $remove;
509 }
510 }
511
512 =item C<filestatus()>
513
514 Return a true or false value depending on whether
515 a BuildFile was changed or not.
516
517 =cut
518
519 sub filestatus()
520 {
521 my $self=shift;
522 # Here we want to return a true or false value depending on whether
523 # or not a buildfile was changed:
524 return $self->{STATUSSRC};
525 }
526
527 =item C<configstatus()>
528
529 Return a true or false value depending on whether
530 a file in the config directory was changed or not.
531
532 =cut
533
534 sub configstatus()
535 {
536 my $self=shift;
537 # Here we want to return a true or false value depending on whether or not a file
538 # in config dir was changed:
539 return $self->{STATUSCONFIG};
540 }
541
542 =item C<bf_for_scanning()>
543
544 Return a list of BuildFiles to re-read. Note that this is only done
545 if the status was changed (i.e. not necessary to read through the list
546 of BuildFiles to know whether something changed as the flag B<STATUSSRC>
547 is set as the source tree is checked).
548 If B<STATUSCONFIG> is true, all BuildFiles are marked to be read.
549
550 =cut
551
552 sub bf_for_scanning()
553 {
554 my $self=shift;
555 my $MODIFIED = [];
556 map { push(@$MODIFIED, $_) } @{$self->{ADDEDBF}};
557 return $MODIFIED;
558 }
559
560 =item C<paths()>
561
562 Return a reference to an array of directories for the current source tree.
563
564 =cut
565
566 sub paths()
567 {
568 my $self=shift;
569 my $paths = {};
570
571 my $ALLDIRS = [];
572 map { push(@$ALLDIRS, $_) } keys %{$self->{DIRCACHE}};
573 return $ALLDIRS;
574 }
575
576 =item C<verbose()>
577
578 Turn verbosity for the cache on or off.
579
580 =cut
581
582 sub verbose()
583 {
584 my $self=shift;
585 # Turn on verbose mode:
586 @_ ? $self->{VERBOSE} = shift
587 : $self->{VERBOSE}
588 }
589
590 =item C<cachestatus()>
591
592 Set or return the cache status to indicate whether or not a file
593 timestamp has changed since the last pass.
594
595 =cut
596
597 sub cachestatus()
598 {
599 my $self=shift;
600 # Set/return the status of the cache:
601 @_ ? $self->{STATUS} = shift
602 : $self->{STATUS}
603 }
604
605 =item C<logmsg(@message)>
606
607 Print a message to B<STDERR>. This is only used in
608 checktree(), checkfiles() and paths().
609
610 =cut
611
612 sub logmsg()
613 {
614 my $self=shift;
615 # Print a message to STDOUT if VERBOSE is true:
616 print STDERR @_ if $self->verbose();
617 }
618
619 =item C<name()>
620
621 Set or return the name of the cache. Normally set
622 to B<DirCache.db> (and not architecture dependent).
623
624 =cut
625
626 sub name()
627 {
628 my $self=shift;
629 # Set/return the name of the cache to use:
630 @_ ? $self->{CACHENAME} = shift
631 : $self->{CACHENAME}
632 }
633
634 sub get_data()
635 {
636 my $self=shift;
637 my $type=shift;
638 @_ ? $self->{$type} = shift
639 : $self->{$type};
640 }
641
642 sub extra_suffix()
643 {
644 my $self=shift;
645 my $path=shift;
646 @_ ? $self->{EXTRASUFFIX}{$path}=shift
647 : exists $self->{EXTRASUFFIX}{$path};
648 }
649
650 sub get_nonxml()
651 {
652 my $self=shift;
653 return $self->{nonxml};
654 }
655
656 1;
657
658 =back
659
660 =head1 AUTHOR
661
662 Shaun Ashby (with contribution from Lassi Tuura)
663
664 =head1 MAINTAINER
665
666 Shaun Ashby
667
668 =cut
669