ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/SCRAM/src/BuildSystem/BuildDataStorage.pm
Revision: 1.5
Committed: Wed Apr 6 18:10:33 2005 UTC (20 years, 1 month ago) by sashby
Content type: text/plain
Branch: MAIN
Changes since 1.4: +6 -2 lines
Log Message:
Fix to bug 7570 (tool version mismatch handled gracefully)

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