Subversion Repositories DevTools

Rev

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

Rev Author Line No. Line
227 dpurdie 1
########################################################################
6177 dpurdie 2
# COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
227 dpurdie 3
#
4
# Module name   : jats.sh
5
# Module type   : Makefile system
6
# Compiler(s)   : n/a
7
# Environment(s): jats build system
8
#
9
# Description   : Provide access to information from the build.pl file as parsed
10
#                 by JATS. This is more complete than the parser in the
11
#                 "BuildFile.pm"
12
#
13
#                 The purpose of this module is to provide an interface
14
#                 between (essentially) internal data structures and user
15
#                 scripts that need to access the data. These are primarily
16
#                 deployment scripts.
17
#
18
#                 The 'All' tag is used for backward compatabilty. It simply
19
#                 exports all the known data structures. NOT to be used by new
20
#                 code.
21
#
22
#
23
#
24
# Interface     : ReadBuildConfig           - Initialise module
25
#                 getPlatformParts          - Get a list of Platform parts
6133 dpurdie 26
#                 
27
#                 PackageEntry - Internal class
28
#                   getUnifiedName
29
#                   getBase
30
#                   getName
31
#                   getVersion
32
#                   getType
33
#                   getDir
34
#                   getLibDirs
35
#                   getIncDirs
227 dpurdie 36
#
37
#......................................................................#
38
 
255 dpurdie 39
require 5.006_001;
227 dpurdie 40
use strict;
41
use warnings;
42
 
43
#===============================================================================
44
package ReadBuildConfig;
45
use JatsError;
46
use JatsMakeInfo qw(:basic);
4324 dpurdie 47
use FileUtils;
227 dpurdie 48
 
49
# automatically export what we need into namespace of caller.
50
use Exporter();
51
our (@ISA, @EXPORT, %EXPORT_TAGS, @EXPORT_OK);
52
@ISA         = qw(Exporter);
53
@EXPORT      = qw(  ReadBuildConfig
54
                    getPlatformParts
55
                    getPackagePaths
271 dpurdie 56
                    getPackageList
4324 dpurdie 57
                    getToolInfo
6177 dpurdie 58
                    testToolInfo
6133 dpurdie 59
                    getAliases
6276 dpurdie 60
                    isGenericBuild
227 dpurdie 61
                );
62
@EXPORT_OK =  qw(   $InterfaceVersion
63
                    $ScmBuildMachType
64
                    $ScmInterfaceVersion
65
                    $ScmBuildName
66
                    $ScmBuildPackage
67
                    $ScmBuildVersion
6511 dpurdie 68
                    $ScmBuildBaseVersion
227 dpurdie 69
                    $ScmBuildProject
70
                    $ScmBuildVersionFull
71
                    $ScmBuildPreviousVersion
72
                    $ScmSrcDir
73
                    $ScmLocal
74
                    $ScmDeploymentPatch
75
                    $ScmBuildSrc
76
                    $ScmExpert
261 dpurdie 77
                    $ScmAll
4003 dpurdie 78
                    $ScmNoBuild
6177 dpurdie 79
                    $ScmBuildUuid
227 dpurdie 80
                    %ScmBuildAliases
81
                    %ScmBuildProducts
82
                    %ScmBuildPlatforms
6276 dpurdie 83
                    %ScmBuildMatrix
227 dpurdie 84
                    %ScmBuildPkgRules
85
                    @BUILDPLATFORMS
86
                    @DEFBUILDPLATFORMS
87
                    @BUILDTOOLSPATH
88
                    %BUILDPLATFORM_PARTS
89
                    %BUILDINFO
247 dpurdie 90
                    %BUILD_KNOWNFILES
227 dpurdie 91
                );
92
 
93
%EXPORT_TAGS = (All => [@EXPORT, @EXPORT_OK]);
94
 
95
#-------------------------------------------------------------------------------
96
#   Global variables
97
#
98
 
99
my $interface;
100
my $platform;
101
 
102
#
103
#   The $InterfaceVersion value is manually maintained
104
#   The integer part should be changed to indicate a incompatible change
105
#   to the JATS files created within the interface directory
106
#
107
#   $InterfaceVersion is treated as a float. The fractional part can be
108
#   used to indicate minor changes to the file format.
109
#
110
our $InterfaceVersion       = "2.0";            # Change will issue error message
111
 
112
#
113
#   The following varaibles are "read" in from the build.cfg file
114
#   In order to access simply access we need to declare them
115
#
116
our %BUILDINFO;
117
our %BUILDPLATFORM_PARTS;
118
our $ScmInterfaceVersion;
119
our %ScmBuildPkgRules;
120
our $ScmBuildMachType;
6276 dpurdie 121
our %ScmBuildMatrix; 
227 dpurdie 122
 
123
#-------------------------------------------------------------------------------
124
# Function        : ReadBuildConfig
125
#
126
# Description     : Read in the build config information
127
#                   Read in build.cfg
128
#
129
# Inputs          : $interface              - Path to the interface directory
130
#                   $platform               - Platform being processed
131
#
7326 dpurdie 132
# Returns         : True - data has been loaded
227 dpurdie 133
#
134
sub ReadBuildConfig
135
{
136
    $interface = shift;
137
    $platform = shift;
138
 
139
    my $no_test;
7326 dpurdie 140
    my $no_error;
227 dpurdie 141
    foreach  ( @_ )
142
    {
143
        if ( m/^--NoTest/i ) {
144
            $no_test = 1;
7326 dpurdie 145
        } elsif ( m/^--NoError/i ) {
146
             $no_error = 1;
227 dpurdie 147
        } else {
148
            Warning ("ReadBuildConfig, Unknown option: $_");
149
        }
150
    }
151
 
7326 dpurdie 152
    unless ($interface) {
153
        WarnError (!$no_error, "ReadBuildConfig. Interface directory is not defined");
154
        return 0;
155
    }
156
 
227 dpurdie 157
    Debug("BuildConfig::Reading config, $interface");
158
    my $cfgfile = "$interface/build.cfg";
7326 dpurdie 159
    unless ( -f $cfgfile ) {
160
        WarnError (!$no_error, "JATS internal file missing. Rebuild required",
161
                                "BuildConfig: Cannot find file: $cfgfile" ) ;
162
        return 0;
163
    }
227 dpurdie 164
 
165
    #
166
    #   Include the build.cfg data
167
    #
168
    require ( $cfgfile );
169
 
170
    #
171
    #   Ensure that the version of the interface files can be consumed
172
    #   The $ScmInterfaceVersion is a written copy of $InterfaceVersion
173
    #
174
    #   Allow build.cfg files that do not have a ScmInterfaceVersion
175
    #   Assume that these are at version 1.0.
176
    #
177
    $ScmInterfaceVersion = '1.0' unless ( $ScmInterfaceVersion );
178
    Debug ("ReadBuildConfig: Version: $ScmInterfaceVersion, Need: $InterfaceVersion");
179
    if ( int($ScmInterfaceVersion) != int($InterfaceVersion)  )
180
    {
7326 dpurdie 181
        WarnError (!$no_error, "JATS interface files are not compatible with this version of JATS",
227 dpurdie 182
               "Rebuild required.",
183
               "Current Interface Version: $ScmInterfaceVersion",
184
               "JATS Interface Version   : $InterfaceVersion" );
7326 dpurdie 185
        return 0;
227 dpurdie 186
    }
187
 
188
    #
189
    #   Ensure that this config file is designed for this machine type
190
    #   At make-time this test may not be valid. It should have been
191
    #   validated before make-time.
192
    #
193
    TestMachType ($ScmBuildMachType, "build.cfg") unless $no_test;
194
 
195
    #
196
    #   Remove some unused data
197
    #   Reduces the size of Makefile.cfg. Speeds up writting
198
    #
199
    if ( $platform )
200
    {
201
        for (keys %::ScmBuildPlatforms)
202
        {
203
            next if ($_ eq $platform );
204
            delete ($::BUILDPLATFORM_PARTS{$_} );
205
            delete ($::BUILDINFO{$_} );
206
            delete ($::ScmBuildPkgRules{$_} );
207
        }
208
    }
209
 
210
    #   dump
211
    #
212
    Debug( "Aliases:" );
369 dpurdie 213
    if ( ! (%::ScmBuildAliases) ) {
227 dpurdie 214
        Debug( "  undefined" );
215
 
216
    } else {
217
        foreach my $key (keys %::ScmBuildAliases) {
218
            my( @value ) = split( ' ', $::ScmBuildAliases{ $key } );
219
            Debug( " $key\t= @value" );
220
        }
221
    }
222
 
223
    Debug( "Products:" );
369 dpurdie 224
    if ( ! (%::ScmBuildProducts) ) {
227 dpurdie 225
        Debug( "  undefined" );
226
 
227
    } else {
228
        foreach my $key (keys %::ScmBuildProducts) {
229
            my( @value ) = split( ',', $::ScmBuildProducts{ $key } );
230
            Debug( " $key\t= @value" );
231
        }
232
    }
233
 
234
    Debug( "Platforms:" );
369 dpurdie 235
    if ( ! (%::ScmBuildPlatforms) ) {
227 dpurdie 236
        Debug( "  undefined" );
237
 
238
    } else {
239
        foreach my $key (keys %::ScmBuildPlatforms) {
240
            my( @args ) = split( /$;/, $::ScmBuildPlatforms{ $key } );
241
            Debug( " $key\t= @args" );
242
        }
243
    }
7326 dpurdie 244
 
245
    return 1;
227 dpurdie 246
}
247
 
248
#-------------------------------------------------------------------------------
6276 dpurdie 249
# Function        : isGenericBuild 
250
#
251
# Description     : Determine if this is a 'GENERIC' build
252
#                   ie: can be built on ANY build machine
253
#
254
# Inputs          : 
255
#
256
# Returns         : TRUE - Is generic
257
#
258
sub isGenericBuild
259
{
260
    return exists $ScmBuildMatrix{'GENERIC'} ? 1 : 0;
261
}
262
 
263
#-------------------------------------------------------------------------------
6133 dpurdie 264
# Function        : getAliases 
265
#
266
# Description     : Return a list of aliases for the current platform
267
#
268
# Inputs          : 
269
#
270
# Returns         : A list of aliases
271
#
272
sub getAliases
273
{
274
    Error ("BuildConfig:getAliases. Not initialised") unless ( $platform );
275
    #
276
    #   Determine the aliases for this target
277
    #       The alias list includes the actual target too
278
    #
279
    my @AliasList;
280
    push @AliasList, $platform;
281
 
282
    if ( exists( $BUILDINFO{$platform}{'ALIAS'} ))
283
    {
284
        push @AliasList, $BUILDINFO{$platform}{'ALIAS'};
285
    }
286
 
287
    if ( exists( $BUILDINFO{$platform}{'USERALIAS'} ))
288
    {
289
        push @AliasList, @{$BUILDINFO{$platform}{'USERALIAS'}};
290
    }
291
 
292
    return @AliasList;
293
}
294
 
295
 
296
#-------------------------------------------------------------------------------
227 dpurdie 297
# Function        : getPlatformParts
298
#
299
# Description     : return a list of platform parts
300
#
301
# Inputs          : None
302
#
303
# Returns         : A list of platform parts to search in the interface
304
#                   directory, local directory or other
305
#
306
sub getPlatformParts
307
{
308
    Error ("BuildConfig. Not initialised") unless ( $platform );
309
    return @{$BUILDINFO{$platform}{PARTS}};
310
}
311
 
312
#-------------------------------------------------------------------------------
313
# Function        : getPackagePaths
314
#
315
# Description     : Return a list of all packages
316
#                   LinkPkgarchive packages will be provided as is
317
#                   BuildPkgArchive packages will be provided as a single
318
#                   reference to the interface directory
319
#
320
# Inputs          : Options
321
#                       --Interface=xxxx            Path to the interface dir
322
#                                                   If provided, then the path
323
#                                                   will be used for the first
324
#                                                   BuildPkgArchive
325
#                       --All                       All paths
326
#                       --Tools                     All Tools Paths
327
#                       --Gbe                       All Gbe paths
328
#
329
# Returns         : An array of paths
330
#
331
sub getPackagePaths
332
{
333
    Error ("BuildConfig. Not initialised") unless ( $platform );
334
 
335
    my $interface;
336
    my $all;
337
    my $need;
338
    my @result;
339
 
340
    #
341
    #   Parse Options
342
    #
343
    foreach ( @_ )
344
    {
345
        if ( m~^--Interface=(.+)~ ) {
346
            $interface = $1;
347
        } elsif ( m~^--All~ ) {
348
            $all = 1;
349
        } elsif ( m~^--Tools~ ) {
350
            $need = 'TOOLDIRS';
351
        } elsif ( m~^--Gbe~ ) {
352
            $need = 'CFGDIR';
353
        } else {
354
            Error ("BuildConfig. Unknown Option: $_");
355
        }
356
    }
357
 
358
    #
311 dpurdie 359
    #   Locate required entries
227 dpurdie 360
    #
361
    for my $entry (@{$ScmBuildPkgRules{$platform} })
362
    {
363
        #
364
        #   Do we need this entry
365
        #   Select tools and gbe entries
366
        #
367
        my @subdirs = '/';
368
        if ( $need )
369
        {
370
            next unless ( exists ($entry->{$need} ) );
371
            my $subdir = $entry->{$need};
372
            if ( ref($subdir) eq 'ARRAY' ) {
373
                @subdirs = @{$subdir};
374
            } else {
375
                @subdirs = $subdir;
376
            }
377
        }
378
 
379
        #
311 dpurdie 380
        #   Skip the Pseudo INTERFACE package if we are processing all packages
381
        #   Skip BuildPkgArchives if we aren't processing all
227 dpurdie 382
        #
311 dpurdie 383
        next if ( ($entry->{'TYPE'} eq 'interface') && $all );
384
        next if ( ($entry->{'TYPE'} eq 'build') && !$all );
385
 
386
        #
387
        #   Select appropriate root
388
        #       Use provided interface path - not too sure why
389
        #       Should be able to simplify this
390
        #
391
        my $dir = $entry->{'ROOT'};
392
        $dir = $interface if ( $entry->{'TYPE'} eq 'interface' );
393
 
394
        foreach my $subdir ( @subdirs )
227 dpurdie 395
        {
311 dpurdie 396
            my $dir = $entry->{'ROOT'} . $subdir;
397
            $dir =~ s~/+~/~g;
398
            $dir =~ s~/+$~~g;
399
            push @result, $dir;
227 dpurdie 400
        }
401
    }
402
 
403
    return @result;
404
}
405
 
271 dpurdie 406
#-------------------------------------------------------------------------------
407
# Function        : getPackageList
408
#
409
# Description     : Returns a list of package entries
4324 dpurdie 410
#                   Only real use of the returned values is to iterate over it
411
#                   and pass the values into other functions within this
271 dpurdie 412
#                   class.
413
#
414
# Inputs          : Nothing
415
#
416
# Returns         : A list of refs to package data
417
#
418
sub getPackageList
419
{
420
    Error ("BuildConfig. Not initialised") unless ( $platform );
421
    my @result;
422
 
423
    foreach ( @ {$ScmBuildPkgRules{$platform} } )
424
    {
425
#        my %self;
426
#        $self{DATA} = $_;
427
        push @result, bless $_, "PackageEntry";
428
    }
429
    return ( @result );
430
}
431
 
4324 dpurdie 432
#-------------------------------------------------------------------------------
6177 dpurdie 433
# Function        : testToolInfo 
4324 dpurdie 434
#
6177 dpurdie 435
# Description     : Test to see if suitable toolInfo exists
436
#                   May be used if the need for the toolInfo is optional
4324 dpurdie 437
#
6177 dpurdie 438
# Inputs          : $toolname       - Tool to locate
439
#                   $error          - True: Error on not found
4324 dpurdie 440
#
6177 dpurdie 441
# Returns         : undefined       - No tool located
442
#                   refToToolEntry  - Used internally
4324 dpurdie 443
#
6177 dpurdie 444
sub testToolInfo
4324 dpurdie 445
{
6177 dpurdie 446
    my ($toolname, $error) = @_;
4324 dpurdie 447
    my $toolroot;
448
    my $toolinfo;
449
    my $pentry;
450
    my @searchPath;
451
 
452
    foreach my $entry ( getPackageList() )
453
    {
454
        my $path = $entry->getBase(2);
455
        Verbose("getToolInfo: $path");
456
        #   Generic
457
        $toolinfo = catdir($path, 'gbe', 'INFO', 'info.' . $toolname . '.generic');
458
        push @searchPath, $toolinfo;
459
        if ( -f $toolinfo )
460
        {
461
            $toolroot = $path;
462
            $pentry = $entry;
463
            last;
464
        }
465
        #   Machine specific
466
        $toolinfo = catdir($path, 'gbe', 'INFO', 'info.' . $toolname . '.'. $ENV{GBE_HOSTMACH});
467
        push @searchPath, $toolinfo;
468
        if ( -f $toolinfo )
469
        {
470
            $toolroot = $path;
471
            $pentry = $entry;
472
            last;
473
        }
474
    }
6177 dpurdie 475
 
476
    unless (defined $toolroot)
4324 dpurdie 477
    {
6177 dpurdie 478
        #   Didn't find the required tool
479
        Error ("Cannot find required tool in any package: $toolname", "Search Path:", @searchPath) if ($error );
480
        return undef ;
481
    }
482
 
483
    #
484
    #   Return user info
485
    #
486
    return ($pentry, $toolroot, $toolinfo) if (wantarray);
487
    return $pentry;
488
}
489
 
490
 
491
#-------------------------------------------------------------------------------
492
# Function        : getToolInfo 
493
#
494
# Description     : Locate and load the tool information for the named tool
495
#
496
# Inputs          : $toolname           - tool to locate
497
#                   ...                 - Optional,Names of fields to expect in the package
498
#                                         If any of the required fields ar missing an error
499
#                                         will be reported
500
#
501
# Returns         : A hash of Tool info
502
#
503
sub getToolInfo
504
{
505
    my ($toolname, @fnames) = @_;
506
    my %data;
507
 
508
    #
509
    #   Check existence and get essential information
510
    #       Will not return if not found
511
    #
512
    my ($pentry, $toolroot, $toolinfo) = testToolInfo ($toolname, 1);
513
 
514
    open (my $DATA, '<', $toolinfo ) || Error("Cannot open tool info file. $!", "File: $toolinfo");
515
    while ( <$DATA> )
516
    {
517
        $_ =~ s~\s+$~~;
518
        next if ( m~^#~ );
519
        next if length($_) < 1;
520
        m~(.*?)\s*=\s*(.*)~;
521
        $data{$1} = $2;
522
    }
523
    close $DATA;
524
    $data{PKGBASE} = $toolroot;
525
    $data{PKGENTRY} = $pentry;
4324 dpurdie 526
#DebugDumpData("Data", \%data);
527
 
6177 dpurdie 528
    #
529
    #   Ensure that the required fields are in the info file
530
    #   These will be a mix of mandatory and user fields
531
    #
532
    my @missing;
533
    for my $fname ('TOOLNAME','TOOLROOT', @fnames)
534
    {
535
        next if defined $data{$fname};
536
        push @missing, $fname;
4324 dpurdie 537
    }
6177 dpurdie 538
    if (@missing)
539
    {
540
        Error("Tool Package '$toolname' is missing required fields:", @missing);
541
    }
542
    return \%data;
4324 dpurdie 543
}
544
 
545
 
271 dpurdie 546
################################################################################
547
#   PackageEntry
548
################################################################################
549
#
550
#   A class to access the data embedded into $ScmBuildPkgRules
551
#   Use a class interface to abstract the data
552
#
553
package PackageEntry;
554
 
555
#-------------------------------------------------------------------------------
556
# Function        : dump
557
#
558
# Description     : Diagnostic Dump of the body of the package entry
559
#
560
# Inputs          : None
561
#
562
# Returns         : None
563
#
564
sub dump
565
{
566
    my $self = shift;
567
    ::DebugDumpData("PackageEntry", $self );
568
}
569
 
570
#-------------------------------------------------------------------------------
5411 dpurdie 571
# Function        : getUnifiedName 
572
#
573
# Description     : Return a Package Name based on PackageName and project suffix
574
#                   Package Name is cleaned up a bit
575
#                       Replace non-alphanumenrics with a '_'
576
#                       Converted to upper case 
577
#
578
# Inputs          : $self
579
#                   $prefix         - Optional Prefix 
580
#                   $suffix         - Optional Suffix 
581
#
582
# Returns         : String
583
#
584
sub getUnifiedName
585
{
586
    my ($self,$prefix,$suffix) = @_;
587
 
588
    my $name = $prefix || '';
589
    $name .= $self->{'DNAME'};
590
    $name .= '_' . $self->{'DPROJ'} if $self->{'DPROJ'};
591
    $name .= $suffix || '';
592
 
593
    $name =~ s~\W~_~g; 
594
    $name =~ s~-~_~g; 
595
    $name =~ tr~_~_~s;
596
    $name = uc $name;
597
    return $name;
598
}
599
 
600
 
601
#-------------------------------------------------------------------------------
271 dpurdie 602
# Function        : getBase
603
#
604
# Description     : Determine the base directory of the package
605
#
606
# Inputs          : $self                   - Class Ref
607
#                   $type                   - 0: Empty
608
#                                             1: abs dpkg_archive
609
#                                             2: May be in the interface
311 dpurdie 610
#                                             3: Interface, LinkPkgs
271 dpurdie 611
#
612
# Returns         : As above
613
#
614
sub getBase
615
{
616
    my ($self, $type ) = @_;
6177 dpurdie 617
    ::Error ("Internal: PackageEntry::getBase called without 'type' argument") unless defined $type;
371 dpurdie 618
 
271 dpurdie 619
    if ( $type == 1 ) {
620
        return $self->{ROOT};
621
    } elsif ( $type == 2 ) {
311 dpurdie 622
        if ( $self->{'TYPE'} eq 'build' ) {
623
            return $interface;
624
        } else {
271 dpurdie 625
            return $self->{ROOT};
626
        }
311 dpurdie 627
    } elsif ( $type == 3 ) {
628
        return if ( $self->{'TYPE'} eq 'build' );
371 dpurdie 629
        return $self->{ROOT};
271 dpurdie 630
    } else  {
631
        return '';
632
    }
633
}
634
 
635
#-------------------------------------------------------------------------------
6133 dpurdie 636
# Function        : getXxxx 
637
#
638
# Description     : Various getters
639
#
640
# Inputs          : $self - Reference to the class 
641
#
642
# Returns         : Value
643
#
644
 
645
sub getName
646
{
647
    my ($self) = @_;
648
    return $self->{NAME};
649
}
650
 
651
sub getVersion
652
{
653
    my ($self) = @_;
654
    return $self->{VERSION};
655
}
656
 
657
sub getType
658
{
659
    my ($self) = @_;
660
    return $self->{TYPE};
661
}
662
 
663
#
664
#   Returns the location of the package.
665
#       This will be a ref into the package store
666
#       Used only if access to dpkg_archive is needed
667
#       Only useful if TYPE is build or link
668
#
669
sub getDir
670
{
671
    my ($self) = @_;
672
    return $self->{ROOT};
673
}
674
 
675
#-------------------------------------------------------------------------------
271 dpurdie 676
# Function        : getLibDirs
677
#
678
# Description     : Return an array of library directories
679
#
680
# Inputs          : $self                   - Class ref
681
#                   $type                   - 0 : Relative to base of the package
682
#                                             1 : abs to the dpkg_archive package
683
#                                             2 : abs to the interface
6133 dpurdie 684
#                                             3 : Interface, LinkPkgs
271 dpurdie 685
#
686
# Returns         : An array
687
#
688
sub getLibDirs
689
{
690
    my ($self, $type ) = @_;
691
    my @result;
692
    my $prefix = getBase( $self, $type );
693
 
694
    foreach ( @{$self->{PLIBDIRS}} )
695
    {
696
        push @result, $prefix . $_;
697
    }
698
    return @result;
699
}
700
 
701
#-------------------------------------------------------------------------------
702
# Function        : getIncDirs
703
#
704
# Description     : Return an array of include directories
705
#
706
# Inputs          : $self                   - Class ref
707
#                   $type                   - 0 : Relative to base of the package
708
#                                             1 : abs to the dpkg_archive package
709
#                                             2 : abs to the interface
311 dpurdie 710
#                                             3: Interface, LinkPkgs
271 dpurdie 711
#
712
# Returns         : An array
713
#
714
sub getIncDirs
715
{
716
    my ($self, $type ) = @_;
717
    my @result;
718
    my $prefix = getBase( $self, $type );
719
 
720
    foreach ( @{$self->{PINCDIRS}} )
721
    {
722
        push @result, $prefix . $_;
723
    }
724
    return @result;
725
}
726
 
227 dpurdie 727
#------------------------------------------------------------------------------
728
1;