ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/SCRAM/src/BuildSystem/BuildDataStorage.pm
Revision: 1.6
Committed: Thu Apr 7 13:47:24 2005 UTC (20 years, 1 month ago) by sashby
Content type: text/plain
Branch: MAIN
Changes since 1.5: +41 -7 lines
Log Message:
Fix to exit when groups redefined locally, overriding another group def. bug 7731.

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