Subversion Repositories DevTools

Rev

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