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

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