Subversion Repositories DevTools

Rev

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