ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/SCRAM/src/BuildSystem/BuildDataStorage.pm
Revision: 1.7
Committed: Fri Apr 29 16:18:56 2005 UTC (20 years ago) by sashby
Content type: text/plain
Branch: MAIN
CVS Tags: v102p1
Changes since 1.6: +95 -11 lines
Log Message:
UPdates Scram to SCRAM

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