ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/SCRAM/src/BuildSystem/BuildDataStorage.pm
Revision: 1.16
Committed: Tue Feb 27 11:59:43 2007 UTC (18 years, 2 months ago) by sashby
Content type: text/plain
Branch: MAIN
CVS Tags: V1_1_0, v110p1, V110p6, V110p5, V110p4, V110p3
Branch point for: v200branch
Changes since 1.15: +27 -159 lines
Log Message:
Merged from XML branch to HEAD. Start release prep.

File Contents

# User Rev Content
1 sashby 1.2 #____________________________________________________________________
2     # File: BuildDataStorage.pm
3     #____________________________________________________________________
4     #
5     # Author: Shaun Ashby <Shaun.Ashby@cern.ch>
6     # Update: 2004-06-22 15:16:01+0200
7 sashby 1.16 # Revision: $Id: BuildDataStorage.pm,v 1.15 2006/11/14 17:43:14 sashby Exp $
8 sashby 1.2 #
9     # Copyright: 2004 (C) Shaun Ashby
10     #
11     #--------------------------------------------------------------------
12     package BuildSystem::BuildDataStorage;
13     require 5.004;
14 sashby 1.16 use BuildSystem::BuildFile;
15 sashby 1.2 use Exporter;
16 sashby 1.16
17 sashby 1.2 @ISA=qw(Exporter);
18     @EXPORT_OK=qw( );
19    
20     sub new()
21     ###############################################################
22     # new #
23     ###############################################################
24     # modified : Tue Jun 22 15:16:08 2004 / SFA #
25     # params : #
26     # : #
27     # function : #
28     # : #
29     ###############################################################
30     {
31     my $proto=shift;
32     my $class=ref($proto) || $proto;
33     my ($configdir) = @_;
34     my $self=
35     {
36     BUILDTREE => {}, # Path/data pairs;
37     STATUS => 0, # Status of cache
38     VERBOSE => 0 # Verbose mode (0/1);
39     };
40    
41     bless $self,$class;
42    
43     # The location of the top-level BuildFile:
44     $self->{CONFIGDIR} = $configdir;
45    
46     # Somewhere to store the dependencies:
47     $self->{DEPENDENCIES} = {}; # GLOBAL dependencies
48 sashby 1.3 $self->{SKIPPEDDIRS} = {}; # Global skipped dirs
49 sashby 1.2
50     # Initialize the Template Engine:
51     $self->init_engine();
52    
53     return $self;
54     }
55    
56     sub grapher()
57     {
58     my $self=shift;
59     my ($mode,$writeopt)=@_;
60    
61     if ($mode)
62     {
63     $mode =~ tr[A-Z][a-z];
64     # Check to see what the mode is:
65     if ($mode =~ /^g.*?/)
66     {
67     $self->{GRAPH_MODE} = 'GLOBAL';
68     # GLOBAL package graphing:
69     use BuildSystem::SCRAMGrapher;
70     $self->{SCRAMGRAPHER} = BuildSystem::SCRAMGrapher->new();
71     }
72     elsif ($mode =~ /^p.*?/)
73     {
74     # All other cases assume per package. This means that each package
75     # is responsible for creating/destroying grapher objects and writing
76     # out graphs, if required:
77     $self->{GRAPH_MODE} = 'PACKAGE';
78     }
79     else
80     {
81     print "SCRAM error: no mode (w=p,w=g) given for graphing utility!","\n";
82     exit(1);
83     }
84    
85     # Set write option:
86     $self->{GRAPH_WRITE} = $writeopt;
87     }
88     else
89     {
90     print "SCRAM error: no mode (w=p,w=g) given for graphing utility!","\n";
91     exit(1);
92     }
93     }
94    
95     sub global_graph_writer()
96     {
97     my $self=shift;
98     my $name='Project';
99     # Only produce graphs with DOT if enabled. This routine is
100     # only used at Project level:
101     if (defined($self->{SCRAMGRAPHER}) && $self->{GRAPH_WRITE})
102     {
103     my $data; # Fake data - there isn't a DataCollector object
104     $self->{SCRAMGRAPHER}->graph_write($data, $name);
105     delete $self->{SCRAMGRAPHER};
106     }
107     else
108     {
109     print "SCRAM error: can't write graph!","\n";
110     exit(1);
111     }
112    
113     return;
114     }
115    
116     #### The methods ####
117     sub datapath()
118     {
119     my $self=shift;
120     my ($path)=@_;
121     my $datapath;
122     # At project-level, the path is src so just return src. Also,
123     # if we received a BuildFile path that we need to determine the data path for,
124     # check first to see if the path matches config/BuildFile. If it does, we have the top-level
125     # datapath which should be src:
126 sashby 1.16 if ($path eq "$ENV{LOCALTOP}/$ENV{SCRAM_CONFIGDIR}/BuildFile.xml" || $path eq $ENV{SCRAM_SOURCEDIR})
127 sashby 1.2 {
128     return $ENV{SCRAM_SOURCEDIR};
129     }
130    
131     # For other paths, strip off the src dir (part of LOCALTOP) and the final BuildFile to
132     # get a data position to be used as a key:
133     ($datapath = $path) =~ s|^\Q$ENV{SCRAM_SOURCEDIR}\L/||;
134    
135 sashby 1.16 if ($datapath =~ m|(.*)/BuildFile.xml$|)
136 sashby 1.2 {
137     return $1;
138     }
139    
140     return $datapath;
141     }
142    
143     sub check_global_config()
144     {
145     my $self=shift;
146 sashby 1.16 my $topbuildfile = $self->{CONFIGDIR}."/BuildFile.xml";
147 sashby 1.2
148     if ( ! -f $topbuildfile )
149     {
150     print "SCRAM error: no BuildFile at top-level (config)! Invalid area!","\n";
151     exit(1);
152     }
153    
154     return $self;
155     }
156    
157     sub processtree()
158     {
159     my $self=shift;
160     my $parent = $ENV{SCRAM_SOURCEDIR};
161     $self->procrecursive($parent);
162     return $self;
163     }
164    
165     sub updatetree()
166     {
167     my $self=shift;
168     my ($startdir) = @_;
169     print "Updating metadata from $startdir","\n",if ($ENV{SCRAM_DEBUG});
170     $self->updaterecursive($startdir);
171     return $self;
172     }
173    
174     sub updatemkfrommeta()
175     {
176     my $self=shift;
177     my ($startdir)=$ENV{SCRAM_SOURCEDIR};
178     print "Updating Makefile from $startdir","\n",if ($ENV{SCRAM_DEBUG});
179     $self->updatefrommeta($startdir);
180     return $self;
181     }
182    
183     sub scanbranch()
184     {
185     my $self=shift;
186 sashby 1.7 my ($files, $datapath)=@_;
187     my $bfbranch;
188     my $buildfiles;
189 sashby 1.4 # Fix (or rather hack) so that only the current buildfile is parsed, not the parent.
190     # This is required becuase it's not desired to pick up dependencies from the level lower:
191     # one should always do it via a <use name=x> to get the package deps. We don't care about
192     # deps in subsystems (they're only used to define groups) and project-wide deps are added at
193     # template level:
194 sashby 1.16 my $file = $files->[0];
195     return unless -f $file; # Just in case metabf() is empty...
196     $bfbranch=BuildSystem::BuildFile->new();
197     $bfbranch->parse($file);
198 sashby 1.2 # Store:
199     $self->storebranchmetadata($datapath,$bfbranch);
200     return $self;
201     }
202    
203     sub procrecursive()
204     {
205     my $self=shift;
206     my ($dir)=@_;
207     my $datacollector;
208 sashby 1.11
209 sashby 1.13 # Check to see if the dir was skipped. If so, don't push anything to
210     # the Makefile:
211     if ($self->skipdir($dir))
212     {
213     print "procrecursive -> $dir skipped.","\n",if ($ENV{SCRAM_DEBUG});
214     return $self;
215     }
216    
217 sashby 1.2 # Data for current dir:
218     my $treedata = $self->buildtreeitem($dir);
219     # Data for the parent:
220     my $parent = $treedata->parent();
221     my $parenttree = $self->buildtreeitem($parent);
222     # Base classes. These are structural template classes which are fixed in SCRAM:
223 sashby 1.8 my $baseclasses = [ qw( DOMAIN SUBSYSTEM PACKAGE ) ];
224 sashby 1.2
225     # If we have a parent dir, collect METABF. Skip inheriting from config/BuildFile:
226     if (defined ($parenttree) && $parenttree->metabf() && $parent ne 'src')
227     {
228     # Add the meta (BuildFile) location to the current locations meta:
229     $treedata->metabf(@{$parenttree->metabf()});
230     }
231    
232     # Perfect match to class:
233     if ($treedata->suffix() eq '')
234     {
235     # For directories where there's a full match to the classpath, check the class.
236     # Only process Buildfiles if the match occurs for a build product class. In either case,
237     # run the template engine.
238     # Don't process BuildFiles unless we happen to be in a product branch (i.e.,
239     # not a baseclass as defined above) except for Project which we do want:
240     if (! grep($treedata->class() eq $_, @$baseclasses))
241     {
242     # Scan all BuildFiles in this branch:
243     $self->scanbranch($treedata->metabf(),$self->datapath($dir));
244     # Process the build data:
245     $datacollector = $self->processbuildfile($dir, $treedata->path());
246     $treedata->clean(); # Get rid of BRANCHMETA
247     $treedata->branchdata($datacollector);
248     }
249    
250     # And run the engine:
251     $self->run_engine($treedata);
252    
253     foreach my $c ($treedata->children())
254     {
255     if ($c ne '')
256     {
257     $self->procrecursive($c);
258     }
259     }
260     }
261     else
262     {
263     # For directories where there isn't a full match, just run the template engine:
264     $self->run_engine($treedata);
265    
266     foreach my $c ($treedata->children())
267     {
268     if ($c ne '')
269     {
270     $self->procrecursive($c);
271     }
272     }
273     }
274    
275     return $self;
276     }
277    
278     sub updaterecursive()
279     {
280     my $self=shift;
281     my ($dir)=@_;
282     my $datacollector;
283 sashby 1.13
284     # Check to see if the dir was skipped. If so, don't push anything to
285     # the Makefile:
286     if ($self->skipdir($dir))
287     {
288     print "updaterecursive -> $dir: skipped.","\n",if ($ENV{SCRAM_DEBUG});
289     return;
290     }
291    
292 sashby 1.2 # updaterecursive() only SCANS and UPDATES METADATA. The Makefile is rebuilt in
293     # its entirety using updatefrommeta(), called after metadata is updated and stored:
294     # Data for current dir:
295     my $treedata = $self->buildtreeitem($dir);
296     # Data for the parent:
297     my $parent = $treedata->parent();
298     my $parenttree = $self->buildtreeitem($parent);
299     # Base classes. These are structural template classes which are fixed in SCRAM:
300 sashby 1.8 my $baseclasses = [ qw( DOMAIN SUBSYSTEM PACKAGE ) ];
301 sashby 1.2
302     # If we have a parent dir, collect METABF. Skip inheriting from config/BuildFile:
303     if (defined ($parenttree) && $parenttree->metabf() && $parent ne 'src')
304     {
305     # Add the meta (BuildFile) location to the current locations meta:
306     $treedata->metabf(@{$parenttree->metabf()});
307     }
308    
309     # Perfect match to class:
310     if ($treedata->suffix() eq '')
311     {
312     # For directories where there's a full match to the classpath, check the class.
313     # Only process Buildfiles if the match occurs for a build product class. In either case,
314     # run the template engine.
315     # Don't process BuildFiles unless we happen to be in a product branch (i.e.,
316     # not a baseclass as defined above):
317     if (! grep($treedata->class() eq $_, @$baseclasses))
318     {
319     # Scan all BuildFiles in this branch:
320     $self->scanbranch($treedata->metabf(),$self->datapath($dir));
321     # Process the build data:
322     $datacollector = $self->processbuildfile($dir, $treedata->path());
323     $treedata->clean();
324     $treedata->branchdata($datacollector);
325     }
326    
327     foreach my $c ($treedata->children())
328     {
329     if ($c ne '')
330     {
331     $self->updaterecursive($c);
332     }
333     }
334     }
335     else
336     {
337     foreach my $c ($treedata->children())
338     {
339     if ($c ne '')
340     {
341     $self->updaterecursive($c);
342     }
343     }
344     }
345    
346     return $self;
347     }
348    
349     sub updatefrommeta()
350     {
351     my $self=shift;
352     my $datacollector;
353     my ($startdir)=@_;
354 sashby 1.13
355     # Check to see if the dir was skipped. If so, don't push anything to
356     # the Makefile:
357     if ($self->skipdir($startdir))
358     {
359     print "updatefrommeta -> $startdir: skipped.","\n",if ($ENV{SCRAM_DEBUG});
360     return;
361     }
362    
363 sashby 1.2 # Data for current dir:
364     my $treedata = $self->buildtreeitem($startdir);
365     # Run the engine:
366 sashby 1.3 $self->run_engine($treedata);
367 sashby 1.2
368     foreach my $c ($treedata->children())
369     {
370     if ($c ne '')
371     {
372     $self->updatefrommeta($c);
373     }
374     }
375    
376     return $self;
377     }
378    
379     sub buildtreeitem()
380     {
381     my $self=shift;
382     my ($datapath)=@_;
383     # This will return the TreeItem object for
384     # the corresponding data path:
385     return $self->{BUILDTREE}->{$datapath};
386     }
387    
388     sub bproductparse()
389     {
390     my $self=shift;
391     my ($dataposition, $path, $bcollector, $product, $localg)=@_;
392     my $packdir;
393    
394     if ($dataposition =~ m|(.*)/src|)
395     {
396     $packdir=$1;
397     }
398     elsif ($dataposition =~ m|(.*)/|)
399     {
400     $packdir=$dataposition;
401     }
402    
403     # Probably better to use the bin name/safename:
404     $packdir = $product->safename();
405     my $label = $product->name();
406    
407     # Look for architecture-specific tags:
408     if (my $archdata=$product->archspecific())
409     {
410     $bcollector->resolve_arch($archdata,$packdir);
411     }
412    
413     # Groups:
414     if (my @groups=$product->group())
415     {
416     $bcollector->resolve_groups(\@groups,$packdir);
417     }
418    
419     # Check for packages and external tools:
420     if (my @otheruses=$product->use())
421     {
422     $bcollector->localgraph()->vertex($packdir);
423    
424     # Add vertex and edges for current package and its dependencies:
425     foreach my $OU (@otheruses)
426     {
427     $bcollector->localgraph()->edge($packdir, $OU);
428     }
429    
430     $bcollector->resolve_use(\@otheruses);
431     }
432    
433     # For each tag type that has associated data in this buildfile
434     # data object, get the data and store it:
435     map { my $subname = lc($_); $bcollector->storedata($_, $product->$subname(),$packdir); }
436     $product->basic_tags();
437    
438     # Prepare the metadata for this location:
439     my $graphexists = $bcollector->prepare_meta($packdir);
440    
441     # Write out the graph if required:
442     if ($localg && $self->{GRAPH_WRITE} && $graphexists)
443     {
444     $bcollector->localgraph()->graph_write($bcollector->attribute_data(), $packdir);
445     }
446    
447     # Clean up:
448     $bcollector->clean4storage();
449     return $bcollector;
450     }
451    
452     sub processbuildfile()
453     {
454     my $self=shift;
455     my ($dataposition, $path)=@_;
456     my $collector;
457     my $packdir;
458     my $CURRENTBF = $self->metaobject($dataposition);
459     my $localgrapher=0;
460     my $scramgrapher;
461 sashby 1.4
462 sashby 1.2 if (defined($CURRENTBF))
463     {
464     use BuildSystem::DataCollector;
465    
466     # Graphing:
467     if (! defined($self->{SCRAMGRAPHER}))
468     {
469     # We don't have a grapher object so we must we working at package level.
470     $localgrapher=1;
471     # Create the object here:
472     use BuildSystem::SCRAMGrapher;
473     $scramgrapher = BuildSystem::SCRAMGrapher->new();
474     }
475     else
476     {
477     $scramgrapher = $self->{SCRAMGRAPHER};
478     }
479    
480     my %projects = %{$self->{SCRAM_PROJECTS}};
481     my %projectbases = %{$self->{SCRAM_PROJECT_BASES}};
482    
483     # Set up the collector object:
484     $collector = BuildSystem::DataCollector->new($self, $self->{TOOLMANAGER},
485     $path, \%projects, \%projectbases,
486     $scramgrapher);
487    
488     # Need the package name for our dep tracking:
489     if ($dataposition =~ m|(.*)/src|)
490     {
491     $packdir=$1;
492     }
493     elsif ($dataposition =~ m|(.*)/|)
494     {
495     $packdir=$dataposition;
496     }
497     elsif ($dataposition eq $ENV{SCRAM_SOURCEDIR})
498     {
499     $packdir = $ENV{SCRAM_SOURCEDIR};
500     }
501    
502     # Look for architecture-specific tags:
503     if (my $archdata=$CURRENTBF->archspecific())
504     {
505     $collector->resolve_arch($archdata,$packdir);
506     }
507    
508     # Groups:
509     if (my @groups=$CURRENTBF->group())
510     {
511     $collector->resolve_groups(\@groups,$packdir);
512     }
513    
514     # Check for packages and external tools:
515     if (my @otheruses=$CURRENTBF->use())
516     {
517     $scramgrapher->vertex($packdir);
518    
519     # Add vertex and edges for current package and its dependencies:
520     foreach my $OU (@otheruses)
521     {
522     $scramgrapher->edge($packdir, $OU);
523     }
524    
525     $collector->resolve_use(\@otheruses);
526     }
527    
528     # If we are at project-level, also resolve the 'self' tool. We ONLY do this
529     # at project-level:
530     if ($dataposition eq $ENV{SCRAM_SOURCEDIR})
531     {
532     $collector->resolve_use(['self']);
533     }
534    
535     # For each tag type that has associated data in this buildfile
536     # data object, get the data and store it:
537     map { my $subname = lc($_); $collector->storedata($_, $CURRENTBF->$subname(),$packdir); }
538     $CURRENTBF->basic_tags();
539    
540     # Check for build products and process them here:
541     my $buildproducts=$CURRENTBF->buildproducts();
542    
543     my $BUILDP = {};
544    
545     # If we have build products:
546     if ($buildproducts)
547     {
548     # Build a list of target types that should built at this location in
549     # addition to normal libraries:
550     foreach my $type (keys %$buildproducts)
551     {
552     my $typedata=$CURRENTBF->values($type);
553     while (my ($name,$product) = each %$typedata)
554     {
555     # We make a copy from existing collector object. This is basically a "new()"
556     # followed by some copying of relevant data elements:
557     $bcollector = $collector->copy($localgrapher);
558     # The Product object inherits from same core utility packages
559 sashby 1.4 # as BuildFile so all BuildFile methods can be used on the Product object:
560 sashby 1.2 $self->bproductparse($dataposition,$path,$bcollector,$product,$localgrapher);
561     $product->data($bcollector);
562     $BUILDP->{$product->safename()} = $product;
563     }
564     }
565    
566     # Return the hash of products (safe_name/Product object pairs):
567     return $BUILDP;
568     }
569     else
570     {
571     # Prepare the metadata for this location. Also needed for each build product:
572     my $graphexists = $collector->prepare_meta($packdir);
573    
574     # Write out the graph if required (also to be done for each product):
575     if ($localgrapher && $self->{GRAPH_WRITE} && $graphexists)
576     {
577     $scramgrapher->graph_write($collector->attribute_data(), $packdir);
578     }
579    
580     # At this point I think we can clean away the graph object:
581     $collector->clean4storage();
582    
583     # No products: return main collector:
584     return $collector;
585     }
586     }
587     else
588     {
589     # No build data, just return:
590     return $collector;
591     }
592     }
593    
594     sub populate()
595     {
596     my $self=shift;
597     my ($paths,$filecache,$toolmanager)=@_;
598     my $datapath;
599     my $buildfile;
600     $|=1; # Flush
601    
602     # The tool manager:
603     $self->{TOOLMANAGER} = $toolmanager;
604 sashby 1.5 # If there are some paths to iterate over, get scram projects from
605     # toolbox. Each project cache is loaded at this point too.
606     # Note that this could be done later, when running processtree() which
607     # is when access to the project caches is really needed (actually when
608     # running datacollector::processbuildfile):
609 sashby 1.2 $self->scramprojects();
610    
611     # Check that there's a global config. Exit if not:
612     $self->check_global_config();
613    
614     # Loop over all paths. Apply a sort so that src (shortest path) is first (FIXME!):
615     foreach my $path (sort(@$paths))
616     {
617     # Ignore config content here:
618     next if ($path !~ m|^\Q$ENV{SCRAM_SOURCEDIR}\L|);
619     # Set the data path:
620 sashby 1.16 $datapath = $self->datapath($path);
621 sashby 1.2 # Create a TreeItem object:
622     use BuildSystem::TreeItem;
623     my $treeitem = BuildSystem::TreeItem->new();
624     $self->{BUILDTREE}->{$datapath} = $treeitem;
625 sashby 1.16 $buildfile = $path."/BuildFile.xml";
626 sashby 1.2
627     # If we have the project root (i.e. src), we want to process the
628     # top-level (project config) BuildFile:
629     if ($path eq $ENV{SCRAM_SOURCEDIR})
630     {
631 sashby 1.16 $buildfile = $ENV{SCRAM_CONFIGDIR}."/BuildFile.xml";
632 sashby 1.2 # Parse the top-level BuildFile. We must do this here
633     # because we need the ClassPaths. Store as RAWDATA:
634     $self->scan($buildfile, $datapath);
635     # We need scram project base vars at project-level:
636     $treeitem->scramprojectbases($self->{SCRAM_PROJECT_BASES});
637     }
638    
639     # If this BuildFile exists, store in METABF:
640     if ( -f $buildfile )
641     {
642     # This level has a buildfile so store this path:
643     $treeitem->metabf($buildfile);
644 sashby 1.16 # Scan to get dependencies:
645 sashby 1.2 $self->scan($buildfile, $datapath);
646     ($ENV{SCRAM_DEBUG}) ? print "Scanning ",$buildfile,"\n" : print "." ;
647     }
648    
649 sashby 1.3 if ($self->skipdir($datapath))
650     {
651     $treeitem->skip(1);
652     print $datapath," building skipped.\n", if ($ENV{SCRAM_DEBUG});
653     }
654    
655 sashby 1.2 # Now add the class and path info to the TreeItem:
656     my ($class, $classdir, $suffix) = @{$self->buildclass($path)};
657    
658     $treeitem->class($class);
659     $treeitem->classdir($classdir);
660     $treeitem->suffix($suffix);
661     $treeitem->path($path);
662     $treeitem->safepath($path);
663     $treeitem->parent($datapath);
664     $treeitem->children($filecache);
665     $treeitem->name();
666     }
667    
668     print "\n";
669    
670     # Check dependencies- look for cycles in the global dependency data:
671     $self->check_dependencies();
672 sashby 1.3 $self->skipdir() if ($ENV{SCRAM_DEBUG});
673 sashby 1.2 }
674    
675     sub check_dependencies()
676     {
677     my $self=shift;
678     # Use the SCRAMGrapher to process the deps and return a
679     # Graph object:
680     use BuildSystem::SCRAMGrapher;
681    
682     my $SG = BuildSystem::SCRAMGrapher->new($self->{DEPENDENCIES}); # GLOBAL dependencies
683     my $G = $SG->_graph_init();
684     my @classification = $G->edge_classify();
685     my @cycles;
686     my $status=0;
687    
688     # Dump the vertex classification if required:
689     if ($ENV{SCRAM_DEBUG})
690     {
691     print "\n";
692     print "Dumping vertex/path classifications:","\n";
693     print "\n";
694     printf("%-40s %-40s %-15s\n",'Vertex_i','Vertex_j','CLASS');
695     printf("%-95s\n",'-'x95);
696     }
697    
698     foreach my $element (@classification)
699     {
700     printf("%-40s %-40s %-15s\n",$element->[0],$element->[1],$element->[2]), if ($ENV{SCRAM_DEBUG});
701     # Save our cycles to list separately:
702     if ($element->[2] eq 'back')
703     {
704     push(@cycles,$element);
705     $status++;
706     }
707     }
708    
709     print "\n";
710     if ($status)
711     {
712     map
713     {
714     print $::fail."SCRAM buildsystem ERROR: Cyclic dependency ",$_->[0]," <--------> ",$_->[1].$::normal."\n";
715     } @cycles;
716     print "\n";
717    
718     # Exit:
719     exit(1);
720     }
721    
722     # Otherwise return:
723     return;
724     }
725    
726     sub update_toplevel()
727     {
728     my $self=shift;
729     my (@buildfiles) = @_;
730     my $treeitem;
731    
732     print "Re-scanning at top-level..\n";
733    
734 sashby 1.16 my $datapath = $self->datapath($ENV{LOCALTOP}."/".$ENV{SCRAM_CONFIGDIR}."/BuildFile.xml");
735 sashby 1.2
736     # This updates the raw data:
737 sashby 1.16 $self->scan($ENV{LOCALTOP}."/".$ENV{SCRAM_CONFIGDIR}."/BuildFile.xml", $datapath);
738 sashby 1.2
739     # Update everything else:
740     foreach my $B (@buildfiles)
741     {
742 sashby 1.16 next if ($B eq $ENV{LOCALTOP}."/config/BuildFile.xml");
743 sashby 1.2 $datapath = $self->datapath($B);
744     # Check to see if we already have the raw data for this buildfile.
745     # Note that we won't if this scan was run from update mode. In this
746     # case, we set up the TreeItem object:
747     if (! exists($self->{BUILDTREE}->{$datapath}))
748     {
749     use BuildSystem::TreeItem;
750     $treeitem = BuildSystem::TreeItem->new();
751     my $path=$ENV{SCRAM_SOURCEDIR}."/".$datapath;
752     my ($class, $classdir, $suffix) = @{$self->buildclass($path)};
753    
754     $treeitem->class($class);
755     $treeitem->classdir($classdir);
756     $treeitem->suffix($suffix);
757     $treeitem->path($path);
758     $treeitem->safepath($path);
759     $treeitem->parent($datapath);
760     $treeitem->children($filecache);
761     $treeitem->name();
762    
763     $self->{BUILDTREE}->{$datapath} = $treeitem;
764    
765     print "Scanning ",$B,"\n";
766     $self->scan($B,$datapath); # This updates the raw data
767     }
768     else
769     {
770     print "Scanning ",$B,"\n";
771     $self->scan($B,$datapath); # This updates the raw data
772     }
773    
774     # Recursively update the tree from this data path:
775     $self->updatetree($datapath);
776     }
777     }
778    
779     sub update()
780     {
781     my $self=shift;
782     my ($changeddirs, $addeddirs, $bf, $removedpaths, $toolmanager, $filecache) = @_;
783     my $buildfiles = {};
784     # Copy the contents of the array of BuildFiles to a hash so that
785     # we can track which ones have been parsed:
786     map
787     {
788     $buildfiles->{$_} = 0;
789     } @$bf;
790    
791     # Tool manager:
792     $self->{TOOLMANAGER} = $toolmanager;
793     # Get scram projects from toolbox. Each project cache is
794     # loaded at this point too:
795     $self->scramprojects();
796    
797     # Remove build data for removed directories:
798     $self->removedata($removedpaths);
799    
800     # Now check to see if something changed at the top-level. If so we reparse everything:
801 sashby 1.16 my $toplevel = $ENV{LOCALTOP}."/".$ENV{SCRAM_CONFIGDIR}."/BuildFile.xml";
802 sashby 1.2
803     if (exists($buildfiles->{$toplevel}))
804     {
805     $buildfiles->{$toplevel} = 1; # Parsed
806     $self->update_toplevel(@$bf);
807     }
808     else
809     {
810     # Process all new directories first then changed ones. This means that everything will be in
811     # place once we start parsing any modified BuildFiles and once we run updatetree():
812    
813     $self->update_newdirs($addeddirs);
814    
815     $self->update_existingdirs($changeddirs);
816    
817     # Now check for any modified BuildFiles that have not yet been rescanned:
818     foreach my $bftoscan (keys %$buildfiles)
819     {
820     if ($buildfiles->{$bftoscan} == 0)
821     {
822     my $datapath = $self->datapath($bftoscan);
823     $self->scan($bftoscan,$datapath); # This updates the raw data
824     }
825     }
826     }
827    
828     # Also rebuild the project Makefile from scratch:
829     $self->updatemkfrommeta();
830     print "\n";
831     }
832    
833     sub update_newdirs()
834     {
835     my $self=shift;
836     my ($newdirs) = @_;
837     foreach my $path (@$newdirs)
838     {
839     print "Processing new directory \"",$path,"\"\n",if ($ENV{SCRAM_DEBUG});
840     $self->updateadir($path);
841 sashby 1.11 # Now check to see if the current (newly-added) package is needed by some
842     # packages that have already built their metadata. If so, force an update
843     # of those packages:
844     my $locations = $self->unresolved_locations($self->datapath($path));
845     if ($#$locations >= 0)
846     {
847     # Also need to check to see if a location is updated more than once.
848     foreach my $notified_dir (@$locations)
849     {
850 sashby 1.12 print "Going to notify $notified_dir of update","\n", if ($ENV{SCRAM_DEBUG});
851 sashby 1.11 $self->updateadir($notified_dir);
852     $self->remove_unresolved($self->datapath($path),$notified_dir);
853     }
854     }
855 sashby 1.2 }
856     }
857    
858     sub update_existingdirs()
859     {
860     my $self=shift;
861     my ($changeddirs) = @_;
862     foreach my $path (@$changeddirs)
863     {
864     print "Processing modified directory \"",$path,"\"\n",if ($ENV{SCRAM_DEBUG});
865     $self->updateadir($path);
866     }
867     }
868    
869     sub updateadir()
870     {
871     my $self=shift;
872     my ($path) = @_;
873     my $datapath = $self->datapath($path);
874 sashby 1.16 my $possiblebf = $path."/BuildFile.xml";
875 sashby 1.2 my $treeitem;
876    
877     if (! exists($self->{BUILDTREE}->{$datapath}))
878     {
879     use BuildSystem::TreeItem;
880     $treeitem = BuildSystem::TreeItem->new();
881    
882     # Get the class info:
883     my ($class, $classdir, $suffix) = @{$self->buildclass($path)};
884    
885     $treeitem->class($class);
886     $treeitem->classdir($classdir);
887     $treeitem->suffix($suffix);
888     $treeitem->path($path);
889     $treeitem->safepath($path);
890     $treeitem->parent($datapath);
891     $treeitem->children($filecache);
892     $treeitem->name();
893     # Store the TreeItem object:
894     $self->{BUILDTREE}->{$datapath} = $treeitem;
895     }
896    
897     # Update the status of the parent. Add the child and update
898     # the safe subdirs:
899     my $parent = $self->{BUILDTREE}->{$datapath}->parent();
900 sashby 1.9
901     if (defined($self->{BUILDTREE}->{$parent}))
902     {
903     $self->{BUILDTREE}->{$parent}->updateparentstatus($datapath);
904     }
905 sashby 1.2
906     # Now check to see if there is a BuildFile here. If there is, parse it:
907     if ( -f $possiblebf)
908     {
909     # This level has a buildfile so store this path:
910     $self->{BUILDTREE}->{$datapath}->metabf($possiblebf);
911 sashby 1.16 # Scan to get dependencies:
912 sashby 1.2 print "Scanning ",$possiblebf,"\n";
913     $self->scan($possiblebf, $datapath);
914     # Check to see if this BuildFile is known to have needed scanning. If so,
915     # mark it as read:
916     if (exists($buildfiles->{$possiblebf}))
917     {
918     $buildfiles->{$possiblebf} = 1;
919     }
920     }
921    
922     # Recursively update the tree from this data path:
923     $self->updatetree($datapath);
924     }
925    
926     sub scan()
927     {
928     my $self=shift;
929 sashby 1.16 my ($buildfile, $datapath) = @_;
930 sashby 1.7 my $bfparse;
931 sashby 1.16 $bfparse=BuildSystem::BuildFile->new();
932 sashby 1.7 # Execute the parse:
933 sashby 1.2 $bfparse->parse($buildfile);
934 sashby 1.3 # See if there were skipped dirs:
935     my $skipped = $bfparse->skippeddirs($datapath);
936     # Check to see if there was an info array for this location.
937     # If so, we extract the first element of the array (i.e. ->[1])
938     # and store it under the datapath entry. This is just so that useful
939     # messages explaining why the dir was skipped can be preserved.
940     if (ref($skipped) eq 'ARRAY')
941     {
942     $self->skipdir($datapath,$skipped->[1]);
943     }
944 sashby 1.2
945     $self->storedata($datapath, $bfparse);
946 sashby 1.3 # Add the dependency list to our store:
947     $self->{DEPENDENCIES}->{$datapath} = $bfparse->dependencies();
948 sashby 1.2 return $self;
949     }
950    
951     sub init_engine()
952     {
953     my $self=shift;
954     # Create the interface to the template engine:
955     use BuildSystem::TemplateInterface;
956     # Pass in the config dir as the location where templates live:
957     $self->{TEMPLATE_ENGINE} = BuildSystem::TemplateInterface->new();
958     }
959    
960     sub run_engine()
961     {
962     my $self=shift;
963     my ($templatedata)=@_;
964    
965     $self->{TEMPLATE_ENGINE}->template_data($templatedata);
966     $self->{TEMPLATE_ENGINE}->run();
967     return $self;
968     }
969    
970     sub buildclass
971     {
972     my $self=shift;
973     my ($path)=@_;
974     my $cache=[];
975 sashby 1.11 # From Lassi TUURA (with mods by me):
976     #
977 sashby 1.2 # Associate a path with ClassPath setting.
978     # For now, just assumes global data has been scanned and class settings
979     # are already known (in $self->{CONFIGDATA}->classpath()).
980     # Generate more optimal classpath data structure, only once.
981     # Split every cache definition into an array of pairs, directory
982     # name and class. So ClassPath of type "+foo/+bar/src+library"
983     # becomes [ [ "" "foo" ] [ "" "bar" ] [ "src" "library" ] ]
984 sashby 1.16 my @CLASSPATHS=@{$self->{BUILDTREE}->{$ENV{SCRAM_SOURCEDIR}}->rawdata()->{content}->{CLASSPATH}};
985     # This does not work, even though classpath() is a valid method and rawdata()
986     # returns an object blessed into the correct type:
987     # my @CLASSPATHS=@{$self->{BUILDTREE}->{$ENV{SCRAM_SOURCEDIR}}->rawdata()->classpath()};
988    
989 sashby 1.2 if (! scalar @$cache)
990     {
991     foreach my $classpath (@CLASSPATHS)
992     {
993     push (@$cache, [map { [ split(/\+/, $_) ] } split(/\//, $classpath)]);
994     }
995     }
996    
997     print "WARNING: No ClassPath definitions, nothing will be done!","\n",
998     if (! scalar @$cache);
999 sashby 1.15
1000 sashby 1.2 # Now scan the class paths. All the classpaths are given a rank
1001     # to mark how relevant they are, and then the best match is chosen.
1002     #
1003     # The ranking logic is as follows. We scan each class path and
1004     # drop if it doesn't match at all. For paths that match, we
1005     # record how many components of the class was *not* used to match
1006     # on the class: for a short $path, many classes will match.
1007     # For each path component we record whether the match was exact
1008     # (if the class part is empty, i.e. "", it's a wildcard that
1009     # matches everything). Given these rankings, we pick
1010     # - the *first* class that
1011     # - has least *unmatched* components
1012     # - with *first* or *longest* exact match sequence in
1013     # left-to-right order.
1014     my @ranks = ();
1015     my @dirs = split(/\/+/, $path);
1016     CLASS: foreach my $class (@$cache)
1017     {
1018     # The first two members of $rank are fixed: how much of path
1019     # was and was not used in the match.
1020     my $rank = [[], [@dirs]];
1021     foreach my $component (@$class)
1022     {
1023     my $dir = $rank->[1][0];
1024     if (! defined $dir)
1025     {
1026     # Path exhausted. Leave used/unused as is.
1027     last;
1028     }
1029     elsif ($component->[0] eq "")
1030     {
1031     # Wildcard match, push class and use up path
1032     push(@$rank, [1, $component->[1]]);
1033     push(@{$rank->[0]}, shift(@{$rank->[1]}));
1034     }
1035     elsif ($component->[0] eq $dir)
1036     {
1037     # Exact match, push class and use up path
1038     push(@$rank, [0, $component->[1]]);
1039     push(@{$rank->[0]}, shift(@{$rank->[1]}));
1040     }
1041     else
1042     {
1043     # Unmatched, leave used/unused as is.
1044     last;
1045     }
1046     }
1047    
1048     push(@ranks, $rank);
1049     }
1050    
1051     # If no classes match, bail out:
1052     if (! scalar @ranks)
1053     {
1054     return "";
1055     }
1056    
1057     # Sort in ascending order by how much was of class was not used;
1058     # the first entry has least "extra" trailing match data. Then
1059     # truncate to only those equal to the best rank.
1060     my @sorted = sort { scalar(@{$a->[1]}) <=> scalar(@{$b->[1]}) } @ranks;
1061     my @best = grep(scalar(@{$_->[1]}) == scalar(@{$sorted[0][1]}), @sorted);
1062    
1063     # Now figure which of the best-ranking classes have the longest
1064     # exact match in left-to-right order (= which one is first, and
1065     # those with equal first exact match, longest exact match).
1066     my $n = 0;
1067     my $class = $best[$n][scalar @{$best[$n]}-1];
1068     # Return the class data:
1069     return [ $class->[1], join('/', @{$best[$n][0]}), join('/', @{$best[$n][1]}) ];
1070     }
1071    
1072     sub storedata
1073     {
1074     my $self=shift;
1075     my ($datapath, $data)=@_;
1076     # Store the content of this BuildFile in cache:
1077     $self->{BUILDTREE}->{$datapath}->rawdata($data);
1078     return $self;
1079     }
1080    
1081     sub removedata
1082     {
1083     my $self=shift;
1084     my ($removedpaths) = @_;
1085    
1086     foreach my $rd (@$removedpaths)
1087     {
1088     my $datapath = $self->datapath($rd);
1089     # Remove all data, recursively, from $datapath:
1090     $self->recursive_remove_data($datapath);
1091     }
1092    
1093     return $self;
1094     }
1095    
1096     sub recursive_remove_data()
1097     {
1098     my $self=shift;
1099     my ($datapath)=@_;
1100    
1101     # Delete main entry in build data via TreeItem:
1102     if (exists($self->{BUILDTREE}->{$datapath}))
1103     {
1104     # We also must modify the parent TreeItem to remove the child
1105     # from SAFE_SUBDIRS as well as from CHILDREN array:
1106     my $parent = $self->{BUILDTREE}->{$datapath}->parent();
1107     $self->{BUILDTREE}->{$parent}->updatechildlist($datapath);
1108    
1109     # Get the children:
1110     my @children = $self->{BUILDTREE}->{$datapath}->children();
1111    
1112     foreach my $childpath (@children)
1113     {
1114     # The child path value is the datapath so can be used
1115     # directly when deleting data entries
1116     $self->recursive_remove_data($childpath);
1117     }
1118    
1119     # Finally, delete the parent data (a TreeItem):
1120     delete $self->{BUILDTREE}->{$datapath};
1121     }
1122    
1123     # return:
1124     return $self;
1125     }
1126    
1127     sub storebranchmetadata()
1128     {
1129     my $self=shift;
1130     my ($datapath,$data)=@_;
1131    
1132     # Store the content of this BuildFile in cache:
1133     $self->{BUILDTREE}->{$datapath}->branchmetadata($data);
1134     return $self;
1135     }
1136    
1137     sub buildobject
1138     {
1139     my $self=shift;
1140     my ($datapath)=@_;
1141    
1142     if (exists($self->{BUILDTREE}->{$datapath}) && defined($self->{BUILDTREE}->{$datapath}->rawdata()))
1143     {
1144     return $self->{BUILDTREE}->{$datapath}->rawdata();
1145     }
1146     else
1147     {
1148     return undef;
1149     }
1150     }
1151    
1152     sub metaobject
1153     {
1154     my $self=shift;
1155     my ($datapath)=@_;
1156    
1157     if (exists($self->{BUILDTREE}->{$datapath}) && defined($self->{BUILDTREE}->{$datapath}->branchmetadata()))
1158     {
1159     return $self->{BUILDTREE}->{$datapath}->branchmetadata();
1160     }
1161     else
1162     {
1163     return undef;
1164     }
1165     }
1166    
1167 sashby 1.6 sub searchprojects()
1168     {
1169     my $self=shift;
1170     my ($group,$projectref)=@_;
1171    
1172     foreach my $pjt (keys %{$self->{SCRAM_PROJECTS}})
1173     {
1174     print "Checking for group $group in SCRAM project $pjt","\n", if ($ENV{SCRAM_DEBUG});
1175     # As soon as a project is found to have defined $group, we return
1176     # the project name:
1177     if (exists $self->{SCRAM_PROJECTS}->{$pjt}->{KNOWNGROUPS}->{$group})
1178     {
1179     # Store the project name and data path:
1180     $$projectref="project ".uc($pjt)." (".$self->{SCRAM_PROJECTS}->{$pjt}->{KNOWNGROUPS}->{$group}."/BuildFile)";
1181     return(1);
1182     }
1183     }
1184    
1185     # No group found to have been defined already so return false:
1186     return (0);
1187     }
1188    
1189 sashby 1.2 sub findgroup
1190     {
1191     my $self=shift;
1192     my ($groupname) = @_;
1193    
1194     if (exists $self->{KNOWNGROUPS}->{$groupname})
1195     {
1196     # If group exists, return data:
1197     return $self->{KNOWNGROUPS}->{$groupname};
1198     }
1199     else
1200     {
1201     # Not found so return:
1202     return(0);
1203     }
1204     }
1205    
1206     sub knowngroups
1207     {
1208     my $self=shift;
1209     @_ ? $self->{KNOWNGROUPS}=shift
1210     : $self->{KNOWNGROUPS}
1211     }
1212    
1213     sub scramprojects()
1214     {
1215     my $self=shift;
1216     # Need this to be able to read our project cache:
1217     use Cache::CacheUtilities;
1218    
1219     $self->{SCRAM_PROJECTS} = $self->{TOOLMANAGER}->scram_projects();
1220    
1221     # Also store the BASE of each project:
1222     $self->{SCRAM_PROJECT_BASES}={};
1223    
1224     # Load the project cache for every scram-managed project in our toolbox:
1225     while (my ($project, $info) = each %{$self->{SCRAM_PROJECTS}})
1226     {
1227     if ( -f $info."/.SCRAM/".$ENV{SCRAM_ARCH}."/ProjectCache.db")
1228     {
1229     print "Reading cache for ",uc($project),"\n", if ($ENV{SCRAM_DEBUG});
1230     $self->{SCRAM_PROJECTS}->{$project} =
1231     &Cache::CacheUtilities::read($info."/.SCRAM/".$ENV{SCRAM_ARCH}."/ProjectCache.db");
1232     $self->{SCRAM_PROJECT_BASES}->{uc($project)."_BASE"} = $info;
1233     }
1234     else
1235     {
1236     print "WARNING: Unable to read project cache for ",uc($project)," tool.\n", if ($ENV{SCRAM_DEBUG});
1237     print " It could be that the project has not been built for your current architecture.","\n",
1238     if ($ENV{SCRAM_DEBUG});
1239     delete $self->{SCRAM_PROJECTS}->{$project};
1240     }
1241     }
1242    
1243     # Also check to see if we're based on a release area. If so, store the cache as above. Don't store
1244     # the project name but instead just use 'RELEASE':
1245     if (my $releasearea=$::scram->releasearea() && exists $ENV{RELEASETOP})
1246     {
1247     if ( -f $ENV{RELEASETOP}."/.SCRAM/".$ENV{SCRAM_ARCH}."/ProjectCache.db")
1248     {
1249     # OK, so we found the cache. Now read it and store in the projects list:
1250     $self->{SCRAM_PROJECTS}->{RELEASE} =
1251     &Cache::CacheUtilities::read($ENV{RELEASETOP}."/.SCRAM/".$ENV{SCRAM_ARCH}."/ProjectCache.db");
1252     print "OK found release cache ",$self->{SCRAM_PROJECTS}->{RELEASE},"\n", if ($ENV{SCRAM_DEBUG});
1253     }
1254     else
1255     {
1256     print "WARNING: Current area is based on a release area but the project cache does not exist!","\n";
1257     }
1258     }
1259     }
1260    
1261     sub scramprojectbases()
1262     {
1263     my $self=shift;
1264     return $self->{SCRAM_PROJECT_BASES};
1265     }
1266    
1267     sub alldirs
1268     {
1269     my $self=shift;
1270     return @{$self->{ALLDIRS}};
1271     }
1272    
1273 sashby 1.3 sub skipdir
1274     {
1275     my $self=shift;
1276     my ($dir, $message) = @_;
1277    
1278     # Set the info if we have both args:
1279     if ($dir && $message)
1280     {
1281     $self->{SKIPPEDDIRS}->{$dir} = $message;
1282     }
1283     # If we have the dir name only, return true if
1284     # this dir is to be skipped:
1285     elsif ($dir)
1286     {
1287     (exists($self->{SKIPPEDDIRS}->{$dir})) ? return 1 : return 0;
1288     }
1289     else
1290     {
1291     # Dump the list of directories and the message for each:
1292     foreach my $directory (keys %{$self->{SKIPPEDDIRS}})
1293     {
1294     print "Directory \"",$directory,"\" skipped by the build system";
1295     if (length($self->{SKIPPEDDIRS}->{$directory}->[0]) > 10)
1296     {
1297     chomp($self->{SKIPPEDDIRS}->{$directory}->[0]);
1298     my @lines = split("\n",$self->{SKIPPEDDIRS}->{$directory}->[0]); print ":\n";
1299     foreach my $line (@lines)
1300     {
1301     next if ($line =~ /^\s*$/);
1302     print "\t-- ",$line,"\n";
1303     }
1304     print "\n";
1305     }
1306     else
1307     {
1308     print ".","\n";
1309     }
1310     }
1311     }
1312     }
1313    
1314 sashby 1.11 # Keep a record of which packages are missed by each location
1315     # so that, on subsequent updates, these can be inserted auto-
1316     # matically in the metadata for the location:
1317     sub unresolved()
1318     {
1319     my $self=shift;
1320     my ($location, $pneeded) = @_;
1321     # Need to record a mapping "LOCATION -> [ missing packages ]" and a
1322     # reverse-lookup "<missing package> -> [ LOCATIONS (where update required) ]"
1323     $self->{UNRESOLVED_DEPS_BY_LOC}->{$location}->{$pneeded} = 1;
1324     $self->{UNRESOLVED_DEPS_BY_PKG}->{$pneeded}->{$location} = 1;
1325     }
1326    
1327     sub remove_unresolved()
1328     {
1329     my $self=shift;
1330     my ($package, $dir) = @_;
1331     if (exists($self->{UNRESOLVED_DEPS_BY_PKG}->{$package}->{$dir}))
1332     {
1333     delete $self->{UNRESOLVED_DEPS_BY_PKG}->{$package}->{$dir};
1334     # Check to see if there are any keys left. If not, remove the
1335     # package entry:
1336     my $nkeys = scalar(keys %{$self->{UNRESOLVED_DEPS_BY_PKG}->{$package}});
1337     if ($nkeys == 0)
1338     {
1339     delete $self->{UNRESOLVED_DEPS_BY_PKG}->{$package};
1340     }
1341     }
1342     }
1343    
1344     sub unresolved_locations()
1345     {
1346     my $self=shift;
1347     my ($package)=@_;
1348    
1349     if (exists ($self->{UNRESOLVED_DEPS_BY_PKG}->{$package}))
1350     {
1351     # Return locations which miss the metadata of $package:
1352     return [ keys %{$self->{UNRESOLVED_DEPS_BY_PKG}->{$package}} ];
1353     }
1354     }
1355    
1356     sub unresolved_packages()
1357     {
1358     my $self=shift;
1359     my ($location)=@_;
1360    
1361     if (exists ($self->{UNRESOLVED_DEPS_BY_LOC}->{$location}))
1362     {
1363     # Return packages which are needed by $location:
1364     return [ keys %{$self->{UNRESOLVED_DEPS_BY_LOC}->{$location}} ];
1365     }
1366     }
1367    
1368 sashby 1.2 sub verbose
1369     {
1370     my $self=shift;
1371     # Turn on verbose mode:
1372     @_ ? $self->{VERBOSE} = shift
1373     : $self->{VERBOSE}
1374     }
1375    
1376     sub cachestatus()
1377     {
1378     my $self=shift;
1379     # Set/return the status of the cache:
1380     @_ ? $self->{STATUS} = shift
1381     : $self->{STATUS}
1382     }
1383    
1384     sub logmsg
1385     {
1386     my $self=shift;
1387     # Print a message to STDOUT if VERBOSE is true:
1388     print STDERR @_ if $self->verbose();
1389     }
1390    
1391     sub name()
1392     {
1393     my $self=shift;
1394     # Set/return the name of the cache to use:
1395     @_ ? $self->{CACHENAME} = shift
1396     : $self->{CACHENAME}
1397     }
1398    
1399     sub save()
1400     {
1401     my $self=shift;
1402     # Delete unwanted stuff:
1403     delete $self->{DEPENDENCIES};
1404     delete $self->{TOOLMANAGER};
1405     delete $self->{TEMPLATE_ENGINE};
1406     delete $self->{SCRAM_PROJECTS};
1407     delete $self->{SCRAM_PROJECT_BASES};
1408     return $self;
1409     }
1410    
1411     1;