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

File Contents

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