ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/SCRAM/src/BuildSystem/BuildDataStorage.pm
Revision: 1.3
Committed: Wed Mar 9 19:28:19 2005 UTC (20 years, 2 months ago) by sashby
Content type: text/plain
Branch: MAIN
Changes since 1.2: +74 -9 lines
Log Message:
Started adding support for skipping builds in some dirs.

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