ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/SCRAM/src/Cache/Cache.pm
Revision: 1.2
Committed: Fri Dec 10 13:41:39 2004 UTC (20 years, 5 months ago) by sashby
Content type: text/plain
Branch: MAIN
CVS Tags: V1_0_0
Changes since 1.1: +481 -0 lines
Log Message:
Merged V1_0 branch to HEAD

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.1.2.9 2004/11/16 18:47:37 sashby Exp $
9 #
10 # Copyright: 2003 (C) Shaun Ashby
11 #
12 #--------------------------------------------------------------------
13 package Cache::Cache;
14 require 5.004;
15
16 use Exporter;
17 @ISA=qw(Exporter);
18 #
19 sub new()
20 ###############################################################
21 # new #
22 ###############################################################
23 # modified : Thu Nov 27 16:45:27 2003 / SFA #
24 # params : #
25 # : #
26 # function : #
27 # : #
28 ###############################################################
29 {
30 my $proto=shift;
31 my $class=ref($proto) || $proto;
32 my $self=
33 {
34 CACHENAME => "DirCache.db", # Name of global file/dir cache;
35 BFCACHE => {}, # BuildFile cache;
36 DIRCACHE => {}, # Source code cache;
37 STATUS => 0, # Status of cache: 1 => something changed. If so, force save;
38 VERBOSE => 0 # Verbose mode (0/1);
39 };
40
41 bless $self,$class;
42 return $self;
43 }
44
45 sub getdir()
46 {
47 my $self=shift;
48 my ($path) = @_;
49 opendir (DIR, $path) || die "$path: cannot read: $!\n";
50 # Skip .admin subdirectories too:
51 my @items = map { "$path/$_" } grep ($_ ne "." && $_ ne ".." && $_ ne ".admin", readdir(DIR));
52 closedir (DIR);
53 return @items;
54 }
55
56 sub prune()
57 {
58 my $self=shift;
59 my ($path) = @_;
60 $self->cachestatus(1);
61 return if ! exists $self->{DIRCACHE}->{$path};
62 my (undef, undef, @subs) = @{$self->{DIRCACHE}->{$path}};
63 delete $self->{DIRCACHE}->{$path};
64 foreach my $sub (@subs)
65 {
66 $self->prune($sub);
67 }
68 }
69
70 sub checktree()
71 {
72 my ($self, $path, $required, $dofiles) = @_;
73 # Check if this path needs to be checked. If it exists, has the same mode
74 # and the same time stamp, it's up to date and doesn't need to be checked.
75 # Otherwise if it is a directory whose time-stamp has changed, rescan it.
76 # If the path has be removed, prune it from the cache. Note that we skip
77 # non-directories unless $dofiles is set. Considering only directories is
78 # dramatically faster.
79 next if ($path =~ /\.admin/); # skip .admin dirs
80
81 # NB: We stat each path only once ever. The special "_" file handle uses
82 # the results from the last stat we've made. See man perlfunc/stat.
83 if (! stat($path))
84 {
85 die "$path: $!\n" if $required;
86 $self->logmsg("SCRAM: $path: missing: removing from cache\n");
87 $self->prune($path);
88 # Something changed so force write of cache:
89 $self->cachestatus(1);
90 return;
91 }
92
93 # If the entry in the cache is not the same mode or time, force an update.
94 # Otherwise use the cache as the list of items we need to change.
95 my $cached = $self->{DIRCACHE}->{$path};
96 my @items = ();
97
98 if (! -d _)
99 {
100 if ($dofiles)
101 {
102 $self->logmsg("SCRAM: $path: updating cache\n");
103 $self->{DIRCACHE}->{$path} = [ (stat(_))[2, 9] ];
104 }
105 else
106 {
107 $self->logmsg("SCRAM: $path: not a directory: removing from parent's list\n");
108 my $parent = $path;
109 $parent =~ s|(.*)/[^/]+$|$1|;
110 if ($parent ne $path && exists $self->{DIRCACHE}->{$parent})
111 {
112 my ($mode, $time, @subs) = @{$self->{DIRCACHE}->{$parent}};
113 $self->{DIRCACHE}->{$parent} = [ $mode, $time, grep ($_ ne $path, @subs) ];
114 }
115 $self->cachestatus(1);
116 }
117 }
118 elsif (! $cached || $cached->[0] != (stat(_))[2])
119 {
120 # When a directory is added, this block is activated
121 $self->added_dirs($path); # Store the newly-added dir
122 $self->logmsg("SCRAM: $path: new or changed: pruning and recreating cache\n");
123 $self->prune($path);
124 $self->{DIRCACHE}->{$path} = [ (stat(_))[2, 9], @items = $self->getdir($path) ];
125 $required = 1;
126 $self->cachestatus(1);
127 }
128 elsif ($cached->[1] != (stat(_))[9])
129 {
130 # When a subdirectory is removed, this block is activated
131 #
132 # This is a parent directory. We store this as any
133 # update can be taken recursively from this dir:
134 $self->modified_parentdirs($path);
135
136 $self->logmsg("SCRAM: $path: modified: updating cache\n");
137 # Current subdirs:
138 @items = $self->getdir($path);
139
140 # Start checking from element number 2:
141 for (my $i = 2; $i <= $#$cached; $i++)
142 {
143 if (! grep($cached->[$i] eq $_, @items))
144 {
145 # Add the removed path to a store for later access
146 # from the project cache. This info is needed to update
147 # the cached data:
148 $self->schedremoval($cached->[$i]);
149 # Remove all child data:
150 $self->clean_cache_recursive($cached->[$i]);
151 }
152 }
153
154 $self->{DIRCACHE}->{$path} = [ (stat(_))[2, 9], @items ];
155 $required = 1;
156 $self->cachestatus(1);
157 }
158 else
159 {
160 $self->logmsg("SCRAM: $path: valid: using cached directory list\n");
161 (undef, undef, @items) = @{$self->{DIRCACHE}->{$path}};
162 $required = 0;
163 }
164
165 # Process sub-directories
166 foreach my $item (@items)
167 {
168 $self->checktree($item, $required, $dofiles);
169 }
170 }
171
172 sub clean_cache_recursive()
173 {
174 my $self=shift;
175 my ($startdir) = @_;
176 my $children = $self->{DIRCACHE}->{$startdir};
177
178 for (my $i = 2; $i <= $#$children; $i++)
179 {
180 # Remove all children:
181 $self->schedremoval($children->[$i]);
182 $self->clean_cache_recursive($children->[$i]);
183 }
184
185 delete $self->{DIRCACHE}->{$startdir};
186 return $self;
187 }
188
189 sub dirtree()
190 {
191 my $self=shift;
192 my ($dir,$dofiles) = @_;
193
194 # Get the directory tree:
195 $self->checktree($dir, 1, $dofiles);
196 return $self;
197 }
198
199 sub checkfiles()
200 {
201 my $self=shift;
202 # Scan config dir for top-level data, then start from src:
203 my @scandirs=($ENV{LOCALTOP}."/".$ENV{SCRAM_CONFIGDIR}, $ENV{SCRAM_SOURCEDIR});
204 my $dofiles=1;
205 # Loop over all directories that need scanning (normally just src and config):
206 foreach my $scand (@scandirs)
207 {
208 $self->logmsg("SCRAM: Scanning $scand [dofiles set to ".$dofiles."]\n");
209 # Check the directory tree:
210 $self->dirtree($scand, $dofiles);
211 $dofiles=0;
212 }
213
214 # Mark everything in the cache old:
215 map { $_->[0] = 0 } values %{$self->{BFCACHE}};
216 map { $_->[0] = 0 } values %{$self->{CONFIGCACHE}};
217
218 # Remember which directories have buildfiles in them:
219 my %files = map { -f $_ ? ($_ => [ (stat(_))[9] ]) : () }
220 map { "$_/BuildFile" }
221 keys %{$self->{DIRCACHE}};
222
223 # Get list of files in config dir:
224 my $configcache = {};
225 my %configfiles = map { -f $_ &&
226 $_ =~ m|$ENV{LOCALTOP}/$ENV{SCRAM_CONFIGDIR}.*?$|
227 ? ($_ => [ (stat(_))[9] ]) : () } keys %{$self->{DIRCACHE}};
228
229 # Also add ToolCache.db to the cache: FIXME: should probably use aglob here so
230 # that all SCRAM_ARCHs are taken into account.
231 $configfiles{$ENV{LOCALTOP}."/.SCRAM/".$ENV{SCRAM_ARCH}."/ToolCache.db"} =
232 [ (stat($ENV{LOCALTOP}."/.SCRAM/".$ENV{SCRAM_ARCH}."/ToolCache.db"))[9] ];
233
234 # Compare or add to config file cache. We need this to be separate so we can tell if a
235 # file affecting our build has been changed:
236 while (my ($path, $vals) = each %configfiles)
237 {
238 if ($self->{CONFIGCACHE}->{$path} && $self->{CONFIGCACHE}->{$path}[1] == $vals->[0])
239 {
240 $configcache->{$path} = $self->{CONFIGCACHE}->{$path};
241 delete $self->{CONFIGCACHE}->{$path};
242 }
243 else
244 {
245 $self->{STATUSCONFIG}=1;
246 $self->logmsg("SCRAM: $path: changed\n");
247 $configcache->{$path} = [ 1, @$vals ];
248 delete $self->{CONFIGCACHE}->{$path};
249 }
250 }
251
252 # Compare with existing cache: remove from cache what no longer
253 # exists, then check which build files are newer than the cache.
254 my $newcache = {};
255
256 while (my ($path, $vals) = each %files)
257 {
258 if ($self->{BFCACHE}->{$path} && $self->{BFCACHE}->{$path}[1] == $vals->[0])
259 {
260 $newcache->{$path} = $self->{BFCACHE}->{$path};
261 delete $self->{BFCACHE}->{$path};
262 }
263 else
264 {
265 $self->{STATUSSRC}=1;
266 $self->logmsg("SCRAM: $path: changed\n");
267 $newcache->{$path} = [ 1, @$vals ];
268 delete $self->{BFCACHE}->{$path};
269 }
270 }
271
272 # If there were BuildFiles that were removed, force update of cache
273 # and remove the BUILDFILEDATA entries:
274 foreach my $path (keys %{$self->{BFCACHE}})
275 {
276 $self->logmsg("SCRAM: $path: removed. Build data will be removed from build cache.\n");
277 $self->cachestatus(1);
278 # Store this so that later, we can tell the BuildDataStore to remove it:
279 $self->schedremoval($path);
280 }
281
282 # Save the BuildFile cache:
283 delete $self->{BFCACHE};
284 $self->{BFCACHE} = $newcache;
285
286 # Save the config cache:
287 delete $self->{CONFIGCACHE};
288 $self->{CONFIGCACHE} = $configcache;
289 return $self;
290 }
291
292 sub dircache()
293 {
294 my $self=shift;
295 # Return the file cache:
296 return $self->{DIRCACHE};
297 }
298
299 sub added_dirs()
300 {
301 my $self=shift;
302 my ($path) = @_;
303
304 # If we have a path to add, add it.
305 if ($path)
306 {
307 if (exists($self->{ADDEDDIRS}))
308 {
309 push(@{$self->{ADDEDDIRS}}, $path);
310 }
311 else
312 {
313 $self->{ADDEDDIRS} = [ $path ];
314 }
315 }
316 else
317 {
318 # Otherwise, return the array of added dirs:
319 my @addeddirs = @{$self->{ADDEDDIRS}};
320 delete $self->{ADDEDDIRS};
321 return \@addeddirs;
322 }
323 }
324
325 sub modified_parentdirs()
326 {
327 my $self=shift;
328 my ($path) = @_;
329
330 # If we have a path to add, add it.
331 # Don't bother if it's the main source dir as we don't
332 # want to rescan everything from src (that would be silly):
333 if ($path && $path ne $ENV{SCRAM_SOURCEDIR})
334 {
335 if (exists($self->{MODPARENTDIRS}))
336 {
337 push(@{$self->{MODPARENTDIRS}}, $path);
338 }
339 else
340 {
341 $self->{MODPARENTDIRS} = [ $path ];
342 }
343 }
344 else
345 {
346 # Otherwise, return the array of added dirs:
347 my @moddeddirs = @{$self->{MODPARENTDIRS}};
348 delete $self->{MODPARENTDIRS};
349 return \@moddeddirs;
350 }
351 }
352
353 sub schedremoval()
354 {
355 my $self=shift;
356 my ($d)=@_;
357
358 if ($d)
359 {
360 if (exists($self->{REMOVEDATA}))
361 {
362 push(@{$self->{REMOVEDATA}},$d);
363 }
364 else
365 {
366 $self->{REMOVEDATA} = [ $d ];
367 }
368 }
369 else
370 {
371 my $remove = [ @{$self->{REMOVEDATA}} ];
372 $self->{REMOVEDATA} = [];
373 return $remove;
374 }
375 }
376
377 sub filestatus()
378 {
379 my $self=shift;
380 # Here we want to return a true or false value depending on whether
381 # or not a buildfile was changed:
382 return $self->{STATUSSRC};
383 }
384
385 sub configstatus()
386 {
387 my $self=shift;
388 # Here we want to return a true or false value depending on whether or not a file
389 # in config dir was changed:
390 return $self->{STATUSCONFIG};
391 }
392
393 sub bf_for_scanning()
394 {
395 my $self=shift;
396 my $MODIFIED = [];
397
398 $self->{STATUSSRC} = 0;
399
400 # Return a list of buildfiles to be reread. Note that we only do this
401 # if the status was changed (i.e. don't have to read through the list of BFs to know
402 # whether something changed as the flags STATUSSRC is set as the src tree is checked).
403 # Also we check to see if STATUSCONFIG is true. If so all BuildFiles are marked to be read:
404 if ($self->{STATUSCONFIG})
405 {
406 $self->{STATUSCONFIG} = 0;
407 # Return all the buildfiles since they'll all to be read:
408 return [ keys %{$self->{BFCACHE}} ];
409 }
410 else
411 {
412 # Only return the files that changed:
413 map { ( $self->{BFCACHE}{$_}->[0] == 1 ) && push(@$MODIFIED, $_) } keys %{$self->{BFCACHE}};
414 # Reset the flag:
415 $self->{STATUSCONFIG} = 0;
416 }
417 return $MODIFIED;
418 }
419
420 sub paths()
421 {
422 my $self=shift;
423 my $paths = {};
424
425 $self->{ALLDIRS} = [];
426
427 # Pass over each dir, skipping those that are not wanted and
428 # storing those that are relevant to an array:
429 foreach my $path (keys %{$self->{DIRCACHE}})
430 {
431 if ( ! -d $path && $path != m|$ENV{LOCALTOP}/$ENV{SCRAM_CONFIGDIR}.*?$|)
432 {
433 $self->logmsg("SCRAM: $path no longer exists. Clearing from cache.\n");
434 $self->cachestatus(1);
435 delete $self->{DIRCACHE}->{$path};
436 }
437 else
438 {
439 next if $path =~ m|/CVS$|; # Ignore CVS directories.
440 next if $path =~ m|/\.admin$|; # Ignore .admin directories.
441 next if $path =~ m|\Q$ENV{LOCALTOP}/$ENV{SCRAM_CONFIGDIR}\L|;
442 push(@{$self->{ALLDIRS}},$path);
443 }
444 }
445
446 # Return the array:
447 return $self->{ALLDIRS};
448 }
449
450 sub verbose()
451 {
452 my $self=shift;
453 # Turn on verbose mode:
454 @_ ? $self->{VERBOSE} = shift
455 : $self->{VERBOSE}
456 }
457
458 sub cachestatus()
459 {
460 my $self=shift;
461 # Set/return the status of the cache:
462 @_ ? $self->{STATUS} = shift
463 : $self->{STATUS}
464 }
465
466 sub logmsg()
467 {
468 my $self=shift;
469 # Print a message to STDOUT if VERBOSE is true:
470 print STDERR @_ if $self->verbose();
471 }
472
473 sub name()
474 {
475 my $self=shift;
476 # Set/return the name of the cache to use:
477 @_ ? $self->{CACHENAME} = shift
478 : $self->{CACHENAME}
479 }
480
481 1;