Subversion Repositories DevTools

Rev

Rev 7319 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
7318 dpurdie 1
########################################################################
2
# COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
3
#
4
# Module name   : JatsSignatureBuilder.pm
5
# Module type   : JATS Utility
6
# Compiler(s)   : Perl
7
# Environment(s): jats
8
#
9
# Description   : Generate a PAckage signature
10
#
11
#
12
#......................................................................#
13
 
14
require 5.008_002;
15
use strict;
16
use warnings;
17
 
18
package JatsSignatureBuilder;
19
 
20
use JatsError;
21
use FileUtils;
22
use JatsVersionUtils;
23
use JatsEnv;
24
use JatsSystem;
25
use ArrayHashUtils;
26
use BuildName;
27
 
28
use Digest::SHA::PurePerl qw(sha1);
29
use IPC::Open3;
30
use File::Path;
31
 
32
#-------------------------------------------------------------------------------
33
# Function        : GeneratePackageSignature 
34
#
35
# Description     : Generate a package 'signature' for this package
36
#               
37
#                   The signature is used to bypass the entire Make processing in a sandbox
38
#                   If we can find a matching package in the package store then we don't 
39
#                   need to 'make' this package.
40
#
41
#                   There are two scenarios:
42
#                       In a GIT enabled sandbox
43
#                       Without GIT
44
#                       
45
#                   In a GIT enabled sandbox the signature allows the use of a pre-built 
46
#                   package - even if the package has been built on a different branch.
47
#                   
48
#                       The signature includes:
49
#                           The name of this package
50
#                           The GIT sha1 of the directory trees that contain this package
51
#                           The package signatures of all dependent packages
52
#                           
53
#                   In a Non-GIT enabled sandbox the package signature will be set such that
54
#                   the package will never be found in the package store and the package MUST
55
#                   be built within the sandbox.
56
#                   
57
#                   The hard part is determing the directory trees that contains this package
58
#                   Ideally this is a single dir-tree, but this cannot be enforced.
59
#                   
60
#                   Source directories have been gathered during makefile generation
61
#                   
62
#                   This suits most cases, but there are a few where the user needs
63
#                   to give JATS a hint. Use the AsdSrcDir directive to extend
64
#                   the signature paths to directories not under the build.pl
65
#                   or any makefile included by the build.pl
66
#                   
67
#                   The generated file will be held in the sandbox directory.
68
#
7319 dpurdie 69
# Inputs          : $pkgBase        - Path to the package's build file
70
#                   $outPath        - Base of path to create signature files 
7318 dpurdie 71
#
72
# Returns         : The package signature
73
#
7319 dpurdie 74
sub GeneratePackageSignature
7318 dpurdie 75
{
7319 dpurdie 76
    my ($pkgBase, $outPath) = @_;
7318 dpurdie 77
    my %sigExcludeDirs;
78
    my %sigExcludeFiles;
79
    my $BuildSignatureSha1;
80
    my $BuildSignature;
81
    my @sigList;
82
    my $sigText;
83
 
7319 dpurdie 84
    Error ("No directory specified") unless $pkgBase;
7320 dpurdie 85
    Debug("Build Directory: $pkgBase");
7319 dpurdie 86
    Error ("Not a directory: $pkgBase") unless -d $pkgBase;
7318 dpurdie 87
 
7319 dpurdie 88
    my $parsedInfo = JatsParser::processBuild ($pkgBase);
89
    #DebugDumpData("GeneratePackageSignature::parsedInfo", $parsedInfo);
90
    Error ('BuildName not found') unless exists $parsedInfo->{BuildName};
91
 
7318 dpurdie 92
    #
93
    #   Determine the saved locations for the output files
94
    #
95
    mkpath ( $outPath ) unless -d $outPath;
96
    my $signatureFile = CatPaths($outPath, 'Package.sig');
97
    my $sigDebugFile  = CatPaths($outPath, 'Package.dsig');
98
 
99
    #
100
    #   Determine if this is a GIT enabled sandbox build
101
    #   Need a .git directory or file in the root of the sandbox
102
    #
103
    my $gitEnabled;
104
    if ($::GBE_SANDBOX && -e CatPaths ($::GBE_SANDBOX, '.git') ) {
105
        $gitEnabled = 1;
106
    }
107
 
108
    #
109
    #   Start generating the signature
110
    #       Include the package Name, Version and Project
111
    #
112
    $BuildSignatureSha1 = Digest::SHA::PurePerl->new;
113
    $sigText = "PKGNAME: " . join (' ', @{$parsedInfo->{BuildName}} );
114
 
115
    $BuildSignatureSha1->add( $sigText );
116
    push @sigList, $sigText . ": " . $BuildSignatureSha1->clone->hexdigest;
117
 
118
    #
119
    #   Include the signature of ALL dependent packages
120
    #   Ie: The package signature is a function of the source and its dependents
121
    #   Assume that we are starting with a sorted list
122
    #
123
    foreach my $tag ( @{$parsedInfo->{PkgList}} )
124
    {
125
        my ($pname, $pversion) = split ($;, $tag);
7319 dpurdie 126
        my $pkgSig = getPackageSignature($pname, $pversion);
7318 dpurdie 127
        $BuildSignatureSha1->add("PKGSIGNATURE: $pkgSig");
128
        push @sigList, sprintf("PKGSIGNATURE: [%s %s] %s: %s", $pname, $pversion, $pkgSig , $BuildSignatureSha1->clone->hexdigest);
129
    }
130
 
131
    if ($gitEnabled)
132
    {
133
        #
134
        #   Include the sha1 of all 'git' tree items that form the complete source image
135
        #   Warn user if not all components are version controlled
136
        #
137
        my @relDirList = @{$parsedInfo->{DirList}}; 
138
        my @cmdList = map { 'HEAD:' . $_ . '/'  } @relDirList;
139
        Debug3(" GIT CMD: " . "git rev-parse", @cmdList );
140
#DebugDumpData("parsedInfo",$parsedInfo);
141
        #
142
        #   Generate a 'nice' array of display paths used
143
        #   The display path will be used simply to report the location in the debug of the package signature
144
        #   The display path is relative to the base of the sandbox
145
        #
146
        my @absDirList = map { RelPath(FullPath( $_ ),$::GBE_SANDBOX) } @relDirList;
147
 
148
        #
149
        #   Callback function to process the output of the Git parse
150
        #   Expect one line for each HEAD: item
151
        #
152
        my $index = 0;
153
        my @notControlled;
154
        my $callback = sub {
155
            my ($cdata, $gitShar) = @_;
156
            $gitShar =~ s~\s+$~~;
157
            Debug3(" GIT OUT: " . $gitShar  );
158
            if ($gitShar =~ m~^HEAD:(.*)~) {
159
                push @notControlled, $1;
160
                $gitShar = 'MSG: Not version controlled';
161
            }
162
            $BuildSignatureSha1->add($gitShar);
163
            push @sigList, "PKGSRC: $absDirList[$index++]: $gitShar: " . $BuildSignatureSha1->clone->hexdigest;
164
            return 0;
165
        };
166
 
167
        my $rv = GitCmd('rev-parse', @cmdList, { process => $callback } );
168
        Debug2("GitCmd Result: $rv");
169
        $BuildSignature =  $BuildSignatureSha1->hexdigest;
170
 
171
        if (@notControlled) {
172
            Warning('The following paths are not version controlled:', @notControlled);
173
        }
174
    }
175
    else
176
    {
177
        $BuildSignature = 'MSG: Sandbox is not git enabled';
178
    }
179
 
180
    Message("Signature: $BuildSignature");
181
    push @sigList, "Signature: $BuildSignature";
182
    FileCreate( $signatureFile, $BuildSignature );
183
    FileCreate( $sigDebugFile, @sigList );
184
Debug0("sigDebugFile: $sigDebugFile");
185
 
186
    return $BuildSignature;
187
}
188
 
189
#-------------------------------------------------------------------------------
7319 dpurdie 190
# Function        : getPackageSignature 
191
#
192
# Description     : Helper routine 
193
#                   Given a package name and package version determine the package
194
#                   signature.
195
#                   
196
#                   Can used predetermined data or perform a package repo scan
197
#                   
198
#                   This version assumes that we are buildign within a jats sandbox
199
#                   Each packages signature file Package.sig is stored in the packages
200
#                   interface directory (at the moment).
201
#                   
202
#                   Process:
203
#                       Locate the packages interface directory - we have a link file to it
204
#                       Read in the PAckage Signature file
205
#
206
# Inputs          : $pname      - Package Name
207
#                   $pversion   - Package Version
208
#                   $mode       - Optional. true -> do not error if not found
209
#
210
# Returns         : The package signature. Undefined if the package canot be found
211
#                    
212
sub getPackageSignature
213
{
214
    my ($pname, $pversion, $mode ) = @_;
215
    my $prj = '';
216
    my $pkg;
217
    my $version;
218
    my $pkgSig;
219
 
220
    # 
221
    #   We are in a sandbox and expect to find a interface/Package.sig file
222
    #   This will allow us to locate the package in the package store
223
    #   
224
    #   If there is no interface/Package.sig, then the user must build (not make)
225
    #   the package in the sandbox.
226
    #   
227
    #   ie: the interface/Package.sig file allows us to use the package from package cache
228
    #       or indicates that the user has not yet built the package
229
    #       
230
    #   First locate the packages interface directory
231
    #   We have a nice link from the sandbox to assist in this
232
    #
233
    my ($pn, $pv, $ps ) = SplitPackage ($pname, $pversion );
234
    $version = 'sandbox';
235
    $prj = '.' . $ps if ( $ps ); 
236
    $version .= $prj;
237
 
238
    my $ifaceDir = CatPaths($::GBE_SANDBOX, 'sandbox_dpkg_archive', $pname, $version . '.int');
239
    $ifaceDir = TagFileRead($ifaceDir);
240
    $ifaceDir =~ s~\\~/~g;
241
    $ifaceDir =~ s~GBE_SANDBOX/~$::GBE_SANDBOX/~;
242
    my $pkgSigFile = CatPaths( $ifaceDir, 'Package.sig');
243
 
244
    if ( -f $pkgSigFile)
245
    {
246
#Debug0("$pname, $pversion --> $pkgSigFile");
247
        $pkgSig = TagFileRead($pkgSigFile);
248
        Error("Package signature invalid for $pname/$version", "Signature: $pkgSig") 
249
            if((length($pkgSig) != 40) && $pkgSig !~ m~^MSG:~) ;
250
    }
251
    else
252
    {
253
        Error("Package signature not found for $pname/$version", "You must 'build' the package before using it") unless $mode;
254
    }
255
 
256
    return $pkgSig;
257
}
258
 
259
#-------------------------------------------------------------------------------
7318 dpurdie 260
# Function        : GitCmd
261
#
262
# Description     : Run a Git Command and capture/process the output
263
#
264
#                   Based on JatsSvnCore:SvnCmd
265
#
266
# Inputs          : Command
267
#                   Command arguments
268
#                   Last argument may be a hash of options.
269
#                       nosavedata  - Don't save the data
270
#                       process     - Callback function
271
#                       printdata   - Print data
272
#                       error       - Error Message
273
#                                     Used as first line of an Error call
274
#
275
# Returns         : non-zero on errors detected
276
#
277
sub GitCmd
278
{
279
    my $self;           # Local storage
280
    Debug ("GitCmd");
281
 
282
    #
283
    #   Locate essential tools
284
    #
285
    our $GBE_SVN_PATH;
286
    EnvImportOptional('GBE_GIT_PATH', '');
287
    Debug ("GBE_GIT_PATH", $::GBE_GIT_PATH);
288
 
289
    my $stdmux = LocateProgInPath ( 'stdmux');
290
    my $git    = LocateProgInPath ( 'git', '--All', '--Path=' . $::GBE_GIT_PATH );
291
 
292
    #
293
    #   Extract arguments and options
294
    #   If last argument is a hash, then its a hash of options
295
    #
296
    my $opt;
297
    $opt = pop @_
298
        if (@_ > 0 and UNIVERSAL::isa($_[-1],'HASH'));
299
 
300
    $self->{PRINTDATA} = $opt->{'printdata'} if ( exists $opt->{'printdata'} );
301
 
302
    Verbose2 "GitCmd $git @_";
303
 
304
    #
305
    # Useful debugging
306
    #
307
    # $self->{LAST_CMD} = [$svn, @_];
308
 
309
    #
310
    #   Reset command output data
311
    #
312
    $self->{ERROR_LIST} = [];
313
    $self->{RESULT_LIST} = [];
314
#    $self->{LAST_CMD} = \@_;
315
 
316
    #
317
    #   Make use of a wrapper program to mux the STDERR and STDOUT into
318
    #   one stream (STDOUT). #   This solves a lot of problems
319
    #
320
    #   Do not use IO redirection of STDERR because as this will cause a
321
    #   shell (sh or cmd.exe) to be invoked and this makes it much
322
    #   harder to kill on all platforms.
323
    #
324
    #   Use open3 as it allows the arguments to be passed
325
    #   directly without escaping and without any shell in the way
326
    #
327
    local (*CHLD_OUT, *CHLD_IN);
328
    my $pid = open3( \*CHLD_IN, \*CHLD_OUT, '>&STDERR', $stdmux, $git, @_);
329
 
330
    #
331
    #   Looks as though we always get a PID - even if the process dies
332
    #   straight away or can't be found. I suspect that open3 doesn't set
333
    #   $! anyway. I know it doesn't set $?
334
    #
335
    Debug ("Pid: $pid");
336
    Error ("Can't run command: $!") unless $pid;
337
 
338
    #
339
    #   Close the input handle
340
    #   We don't have anything to send to this program
341
    #
342
    close(CHLD_IN);
343
 
344
    #
345
    #   Monitor the output from the utility
346
    #   Have used stdmux to multiplex stdout and stderr
347
    #
348
    #   Note: IO::Select doesn't work on Windows :(
349
    #   Note: Open3 will cause blocking unless both streams are read
350
    #         Can't read both streams because IO::Select doesn't work
351
    #
352
    #   Observation:
353
    #       svn puts errors to STDERR
354
    #       svn puts status to STDOUT
355
    #
356
    while (<CHLD_OUT>)
357
    {
358
        s~\s+$~~;
359
        tr~\\/~/~;
360
 
361
 
362
        Verbose3 ( "GitCmd:" . $_);
363
        m~^STD(...):(.+)~;
364
        my $data = $1 ? $2 : $_;
365
        next unless ( $data );
366
 
367
        if ( $1 && $1 eq 'ERR' )
368
        {
369
            #
370
            #   Process STDERR output
371
            #
372
            push @{$self->{ERROR_LIST}}, $data;
373
        }
374
        else
375
        {
376
            #
377
            #   Process STDOUT data
378
            #
379
            push @{$self->{RESULT_LIST}}, $data unless ($opt->{'nosavedata'});
380
 
381
            #
382
            #   If the user has specified a processing function then pass each
383
            #   line to the specified function.  A non-zero return will
384
            #   be taken as a signal to kill the command.
385
            #
386
            if ( exists ($opt->{'process'}) && $opt->{'process'}($self, $data) )
387
            {
388
                kill 9, $pid;
389
                sleep(1);
390
                last;
391
            }
392
        }
393
    }
394
 
395
    close(CHLD_OUT);
396
 
397
    #
398
    #   MUST wait for the process
399
    #   Under Windows if this is not done then we eventually fill up some
400
    #   perl-internal structure and can't spawn anymore processes.
401
    #
402
    my $rv = waitpid ( $pid, 0);
403
 
404
    #
405
    #   If an error condition was detected and the user has provided
406
    #   an error message, then display the error
407
    #
408
    #   This simplifies the user error processing
409
    #
410
    if ( @{$self->{ERROR_LIST}} && $opt->{'error'}  )
411
    {
412
        Error ( $opt->{'error'}, @{$self->{ERROR_LIST}} );
413
    }
414
 
415
    #
416
    #   Exit status has no meaning since open3 has been used
417
    #   This is because perl does not treat the opened process as a child
418
    #   Not too sure it makes any difference anyway
419
    #
420
    #
421
    Debug ("Useless Exit Status: $rv");
422
    my $result = @{$self->{ERROR_LIST}} ? 1 : 0;
423
    Verbose3 ("Exit Code: $result");
424
 
425
    return $result;
426
}
427
 
428
 
429
###############################################################################
430
#   Internal Package
431
#   Primarily to hide the use of the AUTOLOAD
432
#       Which still doesn't behave as expected
433
#       Have trouble with $self in AUTOLOAD. Its not appearig as an argument.
434
#   
435
package JatsParser;
436
use strict;
437
use warnings;
438
 
439
my $currentClass;
440
our $ProjectBase;
441
our $ScmRoot;
442
 
443
#-------------------------------------------------------------------------------
444
# Function        : JatsParser::processBuild 
445
#
446
# Description     : Process the build.pl file and associated makefile.pl's
447
#                   A static-ish method to do all of the hard work.
448
#
449
# Inputs          : $buildPath  - Path to the build file
450
#
451
# Returns         : A few globals 
452
#
453
sub processBuild
454
{
455
    my ($baseDir) = @_;
456
    my @AllSubDirs;
457
 
458
    #
459
    #   Process the build.pl file
460
    #
461
    my $filename = ::CatPaths($baseDir, 'build.pl');
462
    ::Error ("Build file not found : $filename") unless -f $filename;
463
    $baseDir = ::RelPath(::FullPath ($baseDir));
464
    my $buildParser = newJatsParser();
465
    $buildParser->parseFile($baseDir , 'build.pl');
466
 
467
    #DebugDumpData("parser", $parser);
468
 
469
    #
470
    #   If no source subdirs where specified in the build file then insert the  default one
471
    #   This is the same action as perform by jats build
472
    #
473
    if ( ! defined $buildParser->{SubDirs}) {
474
        push @{$buildParser->{SubDirs}}, ::CatPaths($baseDir, 'src');
475
    }
476
 
477
    #
478
    #   If the 'common' makefile exists then parse it as well
479
    #
480
    my $commonMakefile = ::CatPaths($baseDir, 'makefile.pl');
481
    unless ( -f $commonMakefile) {
482
        $commonMakefile = undef;
483
    }
484
 
485
    #
486
    #   Add the build path to the list of known subdirectories
487
    #
488
    @AllSubDirs = $baseDir;
489
 
490
    #
491
    #   Process all subdirs
492
    #       Order is not important - in this case
493
    #
494
    my @SubDirs = @{$buildParser->{SubDirs}};
495
    my $parser = newJatsParser($baseDir, $commonMakefile);
496
    while (@SubDirs)
497
    {
498
        my $makeDir = ::CleanDirName(pop @SubDirs);
499
 
500
        @{$parser->{SubDirs}} = ();
501
        $parser->parseFile($makeDir, 'makefile.pl');
502
 
503
        push @SubDirs, @{$parser->{SubDirs}} if (defined $parser->{SubDirs});
504
        ::UniquePush (\@AllSubDirs, $makeDir);
505
    }
506
 
507
    my @AllInclude = @{$parser->{Includes}} if defined $parser->{Includes};
508
 
509
    #
510
    #   Generate a list of root directories used by the package
511
    #   ie: want top level directories only and not subdirectories
512
    #
513
    my @PackageDirs = generateMinDirList(@AllSubDirs, @AllInclude);
514
 
515
    #
516
    #   Generate a list of all the external packages
517
    #   Don't sort the list. Order may be important
518
    #   
519
    my @AllPackages = ();
520
    push @AllPackages, @{$buildParser->{PkgList}} if (defined $buildParser->{PkgList});
521
 
522
    #
523
    #   Prepare a structure to be returned
524
    #   
525
    my $data;
526
    $data->{BuildName} = $buildParser->{BuildName};
527
    $data->{BaseDir} = $baseDir; 
528
    $data->{PkgList} = \@AllPackages;
529
    $data->{DirList} = \@PackageDirs;
530
    return $data;
531
}
532
 
533
#-------------------------------------------------------------------------------
534
# Function        : generateMinDirList 
535
#
536
# Description     : Generate a list of root directories used by the package
537
#                   ie: want top level directories onyl and not subdirectories
538
#  
539
#
540
# Inputs          : A list of paths to process
541
#
542
# Returns         : A list of processed paths 
543
#
544
sub generateMinDirList
545
{
546
    #
547
    #   Convert all to absolute paths
548
    #
549
    my @baseList;
550
    foreach  (@_) {
551
        push @baseList, ::FullPath($_);
552
    }
553
 
554
 
555
    #   Process the complete list to remove subdirectories
556
    #   Process is:
557
    #       Sort list. Will end up with shortest directories first, thus subdirs will follow parents
558
    #       Insert each item into a new list iff it is not a subdir of something already in the list
559
    #
560
    my @dirList = sort {uc($a) cmp uc($b)} @baseList;
561
 
562
    my @newlist; 
563
    foreach my $newItem ( @dirList ) {
564
        my $match = 0;
565
        foreach my $item ( @newlist ) {
566
            if (index ($newItem, $item) == 0) {
567
                $match = 1;
568
                last;
569
            }
570
        }
571
        push @newlist, $newItem if (! $match);
572
   }
573
 
574
   #
575
   #   Convert back to relative paths
576
   #
577
   @baseList = ();
578
   foreach ( @newlist ) {
579
       push @baseList, ::RelPath($_);
580
   }
581
 
582
   return @baseList;
583
}
584
 
585
#-------------------------------------------------------------------------------
586
# Function        : AUTOLOAD
587
#
588
# Description     : Intercept and process user directives
589
#                   It does not attempt to distinguish between user errors and
590
#                   programming errors. It assumes that the program has been
591
#                   tested. 
592
#
593
# Inputs          : Original function arguments (captured)
594
#
595
#
596
our $AUTOLOAD;
597
sub AUTOLOAD
598
{
599
    #
600
    #   Don't respond to class destruction
601
    #
602
    return if our $AUTOLOAD =~ /::DESTROY$/;
603
 
604
    my $self = $currentClass;
605
    my $type = ref ($self) || ::Error("$self is not an object");
606
 
607
    my $args = ::JatsError::ArgsToString( \@_);
608
    my $fname = $AUTOLOAD;
609
    $fname =~ s~^\w+::~~;
610
    my ($package, $filename, $line) = caller;
611
 
612
    #
613
    #   If directive is inlined
614
    #   Replace it with the raw text of the directive
615
    #       Really only for display purposes
616
    #
617
    if ($fname eq 'If')
618
    {
619
        return $fname . '(' . join( ',', map { qq/"$_"/ } @_ ) . ')' ;
620
    }
621
 
622
    #
623
    #   Capture  and process some directives
624
    #
625
    my %directives = ( AddIncDir => 1, 
626
                       AddSrcDir => 1, 
627
                       AddDir => 1, 
628
                       AddLibDir => 1,
629
 
630
                       LinkPkgArchive => 2, 
631
                       BuildPkgArchive => 2,
632
 
633
                       BuildName => 3,
634
 
635
                       SetProjectBase  => 4,
636
 
637
                       SubDir => 5,
638
                       BuildSubDir => 5,
639
 
640
                       );
641
 
642
    if ($directives{$fname})
643
    {
644
#        ::Debug0 ("Directive: $fname( $args );", "File: $filename, Line: $line, Mode: $directives{$fname}" );
645
 
646
        #   AddIncDir
647
        #   AddSrcDir
648
        #   AddLibDir
649
        #   AddDir
650
        #       Directives that specify directories that extend paths
651
        #
652
        if ($directives{$fname} == 1)
653
        {
654
            for (my $ii = 1; $ii < scalar @_; $ii++)
655
            {
656
                my $arg = $_[$ii];
657
                next if ( $arg =~ m~^--~);
658
#::Debug0("Processing: $arg");
659
#::DebugDumpData("Self", $self);
660
                #
661
                #   Skip if the path looks like it conatins keywords
662
                #   interface and local
663
                #   
664
                if ($arg =~ m~/interface/~ || $arg =~ m~/local/~ ) {
665
                    $arg = '.'
666
                }
667
 
668
                my $dirtyPath = $arg;
669
                $dirtyPath = join( '/', $self->{baseDir}, $arg) unless ($arg =~ m~^/~ || $arg =~ m~^\w:~) ;
670
#::Debug0("DirtyPath: $dirtyPath");
671
                my $path =  ::CleanPath($dirtyPath );
672
#::Debug0("CleanPath: $path");
673
                ::UniquePush (\@{$self->{Includes}}, $path);
674
                ::Error ("Included directory does not exist: $path") unless -d $path;
675
            }
676
        }
677
 
678
        #
679
        #   LinkPkgArchive
680
        #   BuildPkgArchive
681
        #       Directives that define external packages
682
        #
683
        if ($directives{$fname} == 2) {
684
            push @{$self->{PkgList}}, join($;, @_);
685
        }
686
 
687
        #
688
        #   BuildName
689
        #       Directive that specifies the Build Name
690
        #       Format into name, version, suffix
691
        #   
692
        if ($directives{$fname} == 3) {
693
            my $build_info = BuildName::parseBuildName( @_ );
694
            $build_info->{BUILDNAME_PROJECT}  = $build_info->{BUILDNAME_PROJECT} ? '.' . $build_info->{BUILDNAME_PROJECT} : '';
695
            my @data = ($build_info->{BUILDNAME_PACKAGE}, $build_info->{BUILDNAME_VERSION}, $build_info->{BUILDNAME_PROJECT}); 
696
            $self->{BuildName} = \@data;
697
        }
698
 
699
        #
700
        #   SetProjectBase
701
        #       Handle ProjectBase variable
702
        #       Only handle a subset as I want to deprecate this
703
        #       Handle ONLY one arg
704
        #       Must be either : --Up=nn, or a string ( ../.. );
705
        #
706
        if ($directives{$fname} == 4) {
707
            if (scalar @_ > 1 ) {
708
               :: Error ("Multiple arguments to SetProjectBase not supported");
709
            }
710
 
711
            my $dirString = $_[0];
712
            if ($dirString =~ m~--Up=(\d+)~) {
713
                my $count = $1;
714
                $dirString = '/..' x $count;
715
            }
716
            my $newProjectBase = $self->{ProjectBase} . $dirString;
717
            $newProjectBase = ::CleanPath($newProjectBase);
718
#::Debug0 ("SetProjectBase:" . $newProjectBase);
719
 
720
            no strict;
721
            no warnings 'all';
722
            $ProjectBase =  $newProjectBase;
723
#::Debug0 ("ProjectBase: $ProjectBase");
724
        }
725
 
726
        #
727
        #   SubDir
728
        #   BuildSubDir
729
        #       Directives that specify subdirectories to be included in the build
730
        #       Assume they are relative
731
        #
732
        if ($directives{$fname} == 5) {
733
            foreach ( @_ ) {
734
                push  @{$self->{SubDirs}}, ::CatPaths($self->{baseDir}, $_ );
735
            }
736
        }
737
    }
738
}
739
 
740
 
741
#-------------------------------------------------------------------------------
742
# Function        : newJatsParser 
743
#
744
# Description     : New instance of a JatsParser object
745
#
746
# Inputs          : $buildBase - Root of the build 
747
#                   $commonFile - Common makefile to be prefixed to all
748
#
749
# Returns         : 
750
#
751
sub newJatsParser
752
{
753
    my ($buildBase, $commonFile) = @_;
754
    my $class = 'JatsParser';
755
    my $self  = {};
756
    bless $self, $class;
757
 
758
    #
759
    #   Init Data
760
    #
761
    $self->{baseDir} = '';
762
    $self->{filename} = '';
763
    $self->{SubDirs} = ();
764
    $self->{PkgList} = ();
765
    $self->{Includes} = ();
766
    $self->{ScmRoot} = ::FullPath($buildBase) if defined $buildBase ;
767
    $self->{ProjectBase} = $self->{ScmRoot};
768
    $self->{Common} = $commonFile if defined $commonFile;
769
 
770
    #
771
    #   Return class
772
    #
773
    return $self;
774
}
775
 
776
#-------------------------------------------------------------------------------
777
# Function        : parseFile 
778
#
779
# Description     : Parse a build or makefile and return data
780
#
781
# Inputs          : $baseDir      - Base directory
782
#                   $filename     - File to process
783
#
784
# Returns         : stuff 
785
#
786
sub parseFile
787
{
788
    my ($self, $baseDir, $filename) = @_;
789
 
790
    $currentClass = $self;
791
    $self->{baseDir} = $baseDir;
792
    $self->{filename} = $filename;
793
    $filename =  ::CatPaths($baseDir,$filename);
7320 dpurdie 794
    ::Error("parseFile. File not found: $filename") unless -f $filename;
7318 dpurdie 795
 
796
    #
797
    #   Set Jats-global variables
798
    #       $ProjectBase
799
    #       $ScmRoot
800
    #
801
    no strict;
802
    $ProjectBase =  $self->{ProjectBase};
803
    $ScmRoot = ::RelPath( $self->{ScmRoot}, ::FullPath($self->{baseDir}) ) if defined( $self->{ScmRoot}); 
804
 
805
    local @ARGV;
806
    $ARGV[1] = 'Dummy';
807
    use strict;
808
 
809
    #
810
    #   Create the code to be processed
811
    #   Join the common-makefile and the user-makefile
812
    #       Set Line numbers and filenames
813
    #
814
    my $commonCode = "#No Common Code\n";
815
    $commonCode = slurpFile($self,$self->{Common}) if (exists $self->{Common});
816
    my $code =  slurpFile($self,$filename);
817
 
818
#    ::Debug0("code:\n", $commonCode . $code);
819
 
820
    #
821
    #   Evaluate the code
822
    #
823
    no strict;
824
    no warnings 'all';
825
    eval $commonCode . $code;
826
    if ($@) {
827
        ::Error('Bad eval of Code:', $@);
828
        ::Debug0("Code", $code);
829
    }
830
    use strict;
831
    use warnings;
832
 
833
#    ::DebugDumpData("ParsedData", $self);
834
}
835
 
836
#-------------------------------------------------------------------------------
837
# Function        : slurpFile 
838
#
839
# Description     : Read and entire (build / makefile ) into a string
840
#                   Clean it up a little bit
841
#
842
# Inputs          : $file   - File to process 
843
#
844
# Returns         : Entire file as a sinngle string
845
#
846
 
847
 
848
sub slurpFile
849
{
850
    my ($self, $file) = @_;
851
    local $/;
852
    open my $fh, '<', $file or ::Error("Cannot open $file. $!" );
853
    $/ = undef;
854
    my $data = <$fh>;
855
    close $fh;
856
 
857
    #
858
    #   Remove ugly directives
859
    #   Messes with line numbers
860
    #
861
    $data =~ s~^\s*require.*~_JatsRequire();~gm;
862
    $data =~ s~^\s*die.*~_JatsDie();~mg;
863
    $data =~ s~^\s*unless.*~_JatsUnless();~mg;
864
 
865
    $data =~ s~^\s*\$MAKELIB_PL\s+.*~_JatsDefine();~mg;
866
    $data =~ s~^\s*\$BUILDLIB_PL\s+.*~_JatsDefine();~mg;
867
 
868
    #
869
    #   Some old build files use some rubbish perl
870
    #
871
#    $data =~ s~^my~#my~mg;
872
 
873
    #
874
    #   Put a nice header on the file for error reporting
875
    #
876
    my $absName = ::FullPath($file);
877
    my $header = "#line 1 \"$absName\"\n" ;
878
 
879
    return $header . $data;
880
}
881
1;