Subversion Repositories DevTools

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
5485 dpurdie 1
########################################################################
2
# Copyright (c) VIX TECHNOLOGY (AUST) LTD
3
#
4
# Module name   : create_dpkgFromTar.pl
5
# Module type   : JATS Utility
6
# Compiler(s)   : Perl
7
# Environment(s): jats
8
#
9
# Description   : This JATS utility is used by the build system to merge
10
#                 build artifacts from multiple build machines into one
11
#                 package.
12
#                 
13
#                 It complements the 'tarmode' provided by create_dpkg
14
#                 
15
#                 It is not intended to be run by a user.
16
#                 It is not intended to be run directly by the build system
17
#                 It is intended to be run from the build daemons via a shh session
18
#                       Progress is reported via stdout
19
#                       Exit code indicates success or error
20
#
21
# Usage         : See POD at the end of this file
22
#
23
#......................................................................#
24
 
25
require 5.008_002;
26
 
27
# Include Standard Perl Functions
28
#
29
use strict;
30
use warnings;
31
use Cwd;
32
use Getopt::Long;
33
use File::Basename;
34
use File::Find;
35
use File::Path;
36
use File::Copy;
37
use Pod::Usage;
38
use XML::Simple;
39
use Encode qw(decode encode);
40
use File::Temp qw/ tempfile tempdir /;
41
 
42
use JatsError;
43
use JatsEnv;
44
use FileUtils;
45
use JatsSystem;
5486 dpurdie 46
use ArrayHashUtils;
5485 dpurdie 47
 
48
# define Global variables
49
#
50
my $VERSION = "1.0.0";
51
my $PROGNAME = "create_dpkgFromTar.pl";
52
 
53
# Globals imported from environment
54
#
55
our $GBE_MACHTYPE;
56
our $GBE_HOSTNAME;
57
our $GBE_DPKG;
58
our $USER;
59
our $GBE_ABT;
60
 
61
# Global variables
62
#
5486 dpurdie 63
my $tmpDirInfo;
5485 dpurdie 64
my $workDir;
65
my $startDir;
66
my $maxHostNameLength = 8;
5498 dpurdie 67
my $maxTypeLength = 8;
5486 dpurdie 68
my $pkgTargetDir;
69
my $deleteTargetDir;
5485 dpurdie 70
 
71
#
72
#   Option variables
73
#
74
my $opt_help = 0;
75
my $opt_manual = 0;
76
my $opt_verbose = 0;
77
my $opt_pname;
78
my $opt_pversion;
79
my $opt_srcPath;
5486 dpurdie 80
my $opt_MergeErrors = 0;
5485 dpurdie 81
my $opt_outputPath;
5486 dpurdie 82
my $opt_preDelete;
83
my $opt_tmpDir;
5485 dpurdie 84
 
85
#-------------------------------------------------------------------------------
86
# Function        : main entry point 
87
#
88
# Description     : Main Entry point
89
#
90
# Inputs          : 
91
#
92
# Returns         : 
93
#
94
    # Process any command line arguements...
95
    my $result = GetOptions (
96
                'help:+'        => \$opt_help,              # flag, multiple use allowed
97
                'manual:3'      => \$opt_help,              # flag
98
                'verbose:+'     => \$opt_verbose,           # flag, multiple use allowed
99
                'pname=s'       => \$opt_pname,             # string
100
                'pversion=s'    => \$opt_pversion,          # string
101
                'srcpath=s'     => \$opt_srcPath,           # string
102
                'mergeErrors!'  => \$opt_MergeErrors,       # [no]flag
103
                'output=s'      => \$opt_outputPath,        # String
5486 dpurdie 104
                'tmpdir=s'      => \$opt_tmpDir,            # String
105
                'predelete!'    => \$opt_preDelete,         # [no]flag
5485 dpurdie 106
                );              
107
 
108
    #
109
    #   Process help and manual options
110
    #
111
    pod2usage(-verbose => 0, -message => "Version: $VERSION")  if ($opt_help == 1  || ! $result);
112
    pod2usage(-verbose => 1)  if ($opt_help == 2 );
113
    pod2usage(-verbose => 2)  if ($opt_help > 2);
114
 
115
    #
116
    #   Init the error and message subsystem
117
    #
118
    ErrorConfig( 'name'    =>'CREATE_DPKG',
119
                 'verbose' => $opt_verbose );
120
 
121
    if ($opt_verbose)
122
    {
123
       Verbose ("Program: $PROGNAME");
124
       Verbose ("Version: $VERSION");
125
    }
126
 
127
    #
128
    #   Needed EnvVars
129
    #
130
    EnvImport ('GBE_MACHTYPE');
131
    EnvImport ('GBE_HOSTNAME');
132
    EnvImport ('USER' );
133
    EnvImport ('GBE_DPKG' );
134
    EnvImportOptional ('GBE_ABT', '');
135
 
5486 dpurdie 136
    # Defaults
137
    InitFileUtils();
138
    $startDir = Getcwd;
139
    $opt_outputPath = $::GBE_DPKG unless defined $opt_outputPath;
140
    $opt_srcPath = AbsPath($opt_srcPath) if defined $opt_srcPath;
141
    $opt_tmpDir = AbsPath($opt_tmpDir) if defined $opt_tmpDir;
142
 
5485 dpurdie 143
    #
144
    #   Basic sanity testing
145
    #
146
    Error ("Path for package fragments not specified") unless defined $opt_srcPath;
147
    Error ("Package fragment path not found", $opt_srcPath) unless -d $opt_srcPath;
148
    Error ("DPKG_ARCHIVE not found", $GBE_DPKG) unless -d $GBE_DPKG;
149
    Error ("Package name not specified") unless defined $opt_pname;
150
    Error ("Package version not specified") unless defined $opt_pversion;
5486 dpurdie 151
    Error ("Output path not specified" ) unless defined $opt_outputPath;
152
    Error ("Output path does not exist", $opt_outputPath) unless -d $opt_outputPath;
153
    Error ("TmpDir does not exist:", $opt_tmpDir) if (defined($opt_tmpDir) && ! -d ($opt_tmpDir));
5485 dpurdie 154
 
155
    #
156
    #   Create a temp work directory for this
157
    #       This will be removed on program exit 
158
    #       Not by File:Temp as it doesn't handle the case where we have chdir'd to the temp area
159
    #
5486 dpurdie 160
    if ($opt_tmpDir)
161
    {
162
        $workDir = $opt_tmpDir;
163
    }
164
    else
165
    {
166
        $tmpDirInfo = File::Temp->newdir( 'dpkgFromTar_XXXX', CLEANUP => 0, DIR => '/tmp' );
167
        $workDir = $tmpDirInfo->dirname;
168
    }
5485 dpurdie 169
    Verbose("WorkDir", $workDir);
170
    chdir($workDir)|| Error ("Cannot chdir to working directory: $workDir");
171
 
172
    #
173
    #   Locate all package fragements
174
    #   There must be at least one
175
    #   Package fragments are named after the package name and version and have a .tar.gz suffix
176
    #
177
    my $basename = join('_', $opt_pname, $opt_pversion);
178
    my $basenameLen = 1 + length $basename;
179
    $basename .= '_*.tar.gz';
180
    my @packageFragments = glob (catfile($opt_srcPath, $basename ));
181
    Error ("No package fragments found.", "Path: $opt_srcPath", "Glob: $basename" ) unless @packageFragments;
182
    Message("Package Fragmnets found:", @packageFragments);
183
 
184
    #
185
    #   Extract the built.files.<hostname>.xml and descpkg from each of package fragments
5486 dpurdie 186
    #   Note: Use of -m flag to tar is to overcome issues with the bsdtar used under windows
187
    #         to create the tar.gz files. It appears to insert localtime and not GMT into 
188
    #         the file.
5485 dpurdie 189
    #
190
    my %pkgData;   
191
    foreach my $srcfile ( @packageFragments)
192
    {
5486 dpurdie 193
        Message ("Extracting metadata from " . StripDir($srcfile));
5485 dpurdie 194
        my $basename = $srcfile;
195
        $basename =~ s~^.*/~~;
196
        $basename =~ s~\.gz$~~;
197
        $basename =~ s~\.tar$~~;
198
        $basename = substr($basename, $basenameLen);
199
        $pkgData{$srcfile}{basename} = $basename;
200
        mkpath ($basename);
201
        Error ("Temp subdir $basename not created: $!") unless -d $basename;
5486 dpurdie 202
        my $rv = System ('tar', '-xzmf', $srcfile, 
5485 dpurdie 203
                            IsVerbose(1) ? '-v' : undef, 
204
                            '-C', $basename, 
5486 dpurdie 205
                            '--wildcards', './built.files.*.xml' );
5485 dpurdie 206
        Error("Tar extraction error: $srcfile") if ($rv);
207
    }
208
 
209
    #
210
    #   Read in the XML from each of the files
5498 dpurdie 211
    #   Process the XML
212
    #       Detect merge clashes
213
    #       Create new XML - assuming the extraction will NOT overwrite existing files
5485 dpurdie 214
    #
215
    my %fileData;
5498 dpurdie 216
    my @newXml;
5485 dpurdie 217
    foreach my $srcfile ( keys %pkgData )
218
    {
219
        my @extracted = glob(catfile($pkgData{$srcfile}{basename}, 'built.files.*.xml'));
5486 dpurdie 220
        foreach my $srcfile ( @extracted)
5485 dpurdie 221
        {
5486 dpurdie 222
            my $ref = XML::Simple::XMLin($srcfile, ForceArray => 1, KeyAttr => []);
223
            #DebugDumpData("REF - $srcfile, " .ref($ref), $ref);
224
 
5498 dpurdie 225
            my $entryExists;
226
            my $keepEntry;
5485 dpurdie 227
            foreach my $entry (@{$ref->{file}})
228
            {
5498 dpurdie 229
                #
230
                #   Calculate some common data items
231
                #       Calc max host name length for pretty printing
232
                my $hostnameLen = length ($entry->{host} || '');
233
                $maxHostNameLength = $hostnameLen if ($hostnameLen > $maxHostNameLength);
234
 
235
                my $typeLen = length ($entry->{type} || '');
236
                $maxTypeLength = $typeLen if ($typeLen > $maxTypeLength);
237
 
238
                my $hostEntry = {host => $entry->{host}, md5sum => $entry->{md5sum}, type => $entry->{type}};
239
                push @{$fileData{$entry->{fullname}}{hosts}}, $hostEntry;
240
                my $store = $fileData{$entry->{fullname}};
241
 
242
                #
243
                #   Determine if we have seen this file before
244
                #   If so then we need to:
245
                #       Perform a merge clash
246
                #       Ensure that its of the same type
247
                #       Mark the new XML as 'merge'
248
                #
249
                $entryExists = 0;
250
                $keepEntry = 1;
251
                if (exists $store->{type})
252
                {
253
                    $entryExists = 1;
254
                    if ($store->{type} ne $entry->{type})
255
                    {
256
                        $store->{bad} = 1;
257
                        $store->{badType} = 1;
258
                    }
259
                }
260
                else
261
                {
262
                    $store->{type} = $entry->{type};
263
                }
264
 
5486 dpurdie 265
                #   directory - no processing required
5498 dpurdie 266
                if ($entry->{type} eq 'dir')
267
                {
268
                    $keepEntry = 0 if $entryExists;
269
                    next;
270
                }
5486 dpurdie 271
 
272
                #   link - no processing reqiuired
5498 dpurdie 273
                if ($entry->{type} eq 'link')
274
                {
275
                    $keepEntry = 0 if $entryExists;
276
                    next;
277
                }
5486 dpurdie 278
 
279
                #   file - ensure there is no clash
5485 dpurdie 280
                if ($entry->{type} eq 'file')
281
                {
282
                    if (exists $store->{md5sum})
283
                    {
5498 dpurdie 284
                        $store->{bad} = 1 unless ($store->{md5sum} eq $entry->{md5sum});
5485 dpurdie 285
                    }
286
                    else
287
                    {
288
                        $store->{md5sum} = $entry->{md5sum};
289
                    }
290
                next;
291
                }
5486 dpurdie 292
                #   Unknown - just a warning for now
5498 dpurdie 293
                Warning( "Unknown type: " . $entry->{type} , "    Path: ". $entry->{fullname} );
5485 dpurdie 294
            }
5498 dpurdie 295
            continue
296
            {
297
                #
298
                #   This block is always executed
299
                #   It is used to maintain the entry and the rewrite the XML file list
300
                #   Do not include the build.files.xxx.xml
301
                #       They are about to be deleted
302
                #       Not detailed in the non-tar package merge process
303
                #
304
                if ($keepEntry)
305
                {
306
                    unless ($entry->{fullname} =~ m~^built\.files\..*\.xml$~ )
307
                    {
308
                        if ($entryExists)
309
                        {
310
                            delete $entry->{md5sum};
311
                            delete $entry->{size};
312
                            $entry->{type} = 'merge';
313
                        }
314
                        push @newXml, $entry;
315
                    }
316
                }
317
            }
5485 dpurdie 318
        }
319
    }
5498 dpurdie 320
    #DebugDumpData("newXml",\@newXml);
5485 dpurdie 321
 
322
    #
323
    #   Cleanout the non-bad entries
324
    #   Report on merge errors
325
    #
326
    my $headerReported;
327
    foreach my $entry (keys %fileData)
328
    {
329
        #
330
        #   Some entries are allowed to differ
331
        #       descpkg
332
        #       version_*.h 
333
        #           files as these are generated and may contain different dates and line endings
334
        #
5486 dpurdie 335
        if ($entry eq 'descpkg')
5485 dpurdie 336
        {
337
            delete $fileData{$entry};
338
            next;
339
        }
340
 
341
        if ($entry =~ m~/version[^/]*\.h$~)
342
        {
343
            Verbose("Ignore merge error on: $entry");
344
            delete $fileData{$entry};
345
            next;
346
        }
347
 
348
        #
349
        #   Delete entry if its not marked as bad
5486 dpurdie 350
        unless (exists $fileData{$entry}{bad} )
351
        {
5485 dpurdie 352
            delete $fileData{$entry};
353
            next;
354
        }
355
 
356
        unless ($headerReported)
357
        {
358
            $headerReported = 1;
359
            reportMergeError('Package Merge Error. File provided by different builds are not identical');
360
            reportMergeError('This prevents the build from being reproducible.');
361
        }
5486 dpurdie 362
 
5498 dpurdie 363
        if ($fileData{$entry}{badType})
364
        {
365
            #
366
            #   Have a TYPE merge error
367
            #       Detail what has happened
368
            #       Generate pretty output showning on which machines that are command.
369
            #
370
            my %typeList;
371
            foreach my $e ( @{$fileData{$entry}{hosts}} ) {
372
                UniquePush (\@{$typeList{$e->{type}}}, $e->{host});
373
            }
5486 dpurdie 374
 
5498 dpurdie 375
            reportMergeError('Entry Path: ' . $entry);
376
            foreach my $e ( @{$fileData{$entry}{hosts}} )
377
            {
378
                my $hostList;
379
                my @sameHosts = @{$typeList{$e->{type}}};
380
                ArrayDelete (\@sameHosts, $e->{host});
381
                if (@sameHosts) {
382
                    $hostList = ' Same as: ' . join(', ', @sameHosts);
383
                } else {
384
                    $hostList = ' Unique to: '. $e->{host};
385
                }
5486 dpurdie 386
 
5498 dpurdie 387
                reportMergeError('    Provided by: ' . sprintf('%-*s',$maxHostNameLength,$e->{host}) . ' Type: ' . sprintf('%-*s',$maxTypeLength,$e->{type}) . $hostList );
5486 dpurdie 388
            }
389
 
5485 dpurdie 390
        }
5498 dpurdie 391
        else
392
        {
393
            #
394
            #   Have a FILE merge error
395
            #       Detail what has happened
396
            #       Generate pretty output showning on which machines that are common.
397
            #
398
            my %md5List;
399
            foreach my $e ( @{$fileData{$entry}{hosts}} ) {
400
                UniquePush (\@{$md5List{$e->{md5sum}}}, $e->{host});
401
            }
5486 dpurdie 402
 
5498 dpurdie 403
            reportMergeError('File Name: ' . $entry);
404
            foreach my $e ( @{$fileData{$entry}{hosts}} )
405
            {
406
                my $hostList;
407
                my @sameHosts = @{$md5List{$e->{md5sum}}};
408
                ArrayDelete (\@sameHosts, $e->{host});
409
                if (@sameHosts) {
410
                    $hostList = ' Same as: ' . join(', ', @sameHosts);
411
                } else {
412
                    $hostList = ' Unique to: '. $e->{host};
413
                }
5486 dpurdie 414
 
5498 dpurdie 415
                reportMergeError('    Provided by: ' . sprintf('%-*s',$maxHostNameLength,$e->{host}) . $hostList );
416
            }
417
        }
5485 dpurdie 418
    }
419
    ErrorDoExit();
420
 
421
    #
5486 dpurdie 422
    #   Calculate target package location
423
    #   
424
    $pkgTargetDir = catdir($opt_outputPath, $opt_pname, $opt_pversion);
425
    Verbose("Package Target: $pkgTargetDir");
426
    RmDirTree($pkgTargetDir) if $opt_preDelete;
427
    Error ("Target package directory exists") if -d $pkgTargetDir;
428
    mkpath ($pkgTargetDir);
429
    Error ("Package target not created: $!", $pkgTargetDir) unless -d $pkgTargetDir;
430
    $deleteTargetDir = 1;
431
 
432
    #
5485 dpurdie 433
    #   Extract the archive contents and merge them into one directory
434
    #       If there are overlaps - don't replace them
435
    #
436
    foreach my $srcfile ( keys %pkgData )
437
    {
5486 dpurdie 438
        Message ("Extracting all files from " . StripDir($srcfile));
439
        my $rv = System ('tar', '-xzmf', $srcfile, IsVerbose(1) ? '-v' : undef, '-C', $pkgTargetDir );
5485 dpurdie 440
        Error("Tar extraction error: $srcfile") if ($rv);
441
    }
442
 
443
    #
5498 dpurdie 444
    #   Replace the built.files.xxx.xml files that came with each package fragment
445
    #   with a new one caclulated as we merged the fragemnts. The new one will not
446
    #   have duplicate files - they will be merked as merged.
447
    #   
448
    #   Delete existing built.files.xxx.xml
449
    #   Write out file meta data for the assembled package
450
    #
451
    foreach my $item (glob(catdir($pkgTargetDir, 'built.files.*.xml')))
452
    {
453
        Verbose("Delete metadata file: $item");
454
        unlink $item;
455
    }
456
 
457
    Message("Write new archive metadata");
458
    writeFileInfo(catfile($pkgTargetDir, 'built.files.packageAssembly.xml'),\@newXml);
459
 
460
    #
5485 dpurdie 461
    #   Fix file permissions
462
    #   We know we are running under unix so we will use a unix command
463
    #
464
    Message('Setting file permissions');
5486 dpurdie 465
    System('chmod', '-R', 'a+rx', $pkgTargetDir);
5485 dpurdie 466
 
467
    #
468
    #   Fix descpkg file
469
    #   Original create_dpkg uses the CopyDescpkg function. This is a bit wonky
470
    #   All it appears to do is:
471
    #       Force build machine name
472
    #       Force user name
473
    #       Force build time into the descpkg file
474
    #  If a package was built on multiple machines then the build machine names were lost
5486 dpurdie 475
    #  
476
    #   This implementation
477
    #       Use the descpkg file in the first package fragment
478
    #       There is enough other information in the build system to track where the package
479
    #       was built. This was not available when CopyDescpkg was implemented
5485 dpurdie 480
 
481
 
482
    #
483
    #   All Done
5486 dpurdie 484
    #       Flag  - don't cleanup generated dierctory
485
    #       
486
    Information("Package Target: $pkgTargetDir");
487
    $deleteTargetDir = 0;
5485 dpurdie 488
    exit 0;
489
 
490
#-------------------------------------------------------------------------------
491
# Function        : END 
492
#
493
# Description     : Cleanup process 
494
#
495
# Inputs          : 
496
#
497
# Returns         : 
498
#
499
END
500
{
501
    #
502
    #   Delete everything in the temp directory
503
    #   It was a directory created by this instance for the use of this instance
504
    #
5486 dpurdie 505
    if ($tmpDirInfo)
5485 dpurdie 506
    {
507
        Message("Cleanup processing");
508
        chdir($startDir);
5486 dpurdie 509
        RmDirTree($workDir);
510
        if (-d $workDir)
5485 dpurdie 511
        {
5486 dpurdie 512
            Warning("TMPDIR still exists: $workDir");
5485 dpurdie 513
        }
5486 dpurdie 514
    } 
515
    elsif ($workDir)
516
    {
517
        Message ("Retaining workdir: $workDir");
5485 dpurdie 518
    }
5486 dpurdie 519
 
520
    #
521
    #   Delete the package target dir
522
    #   We must have created it - as we error if it exists.
523
    #   
524
    #   Remove the packageName and packageVersion directories fi possible
525
    #   
526
    if ($deleteTargetDir)
527
    {
528
        Message("Remove partially created package");
529
        RmDirTree($pkgTargetDir);
530
 
531
        my $pkgDir = StripFileExt($pkgTargetDir);
532
        rmdir($pkgDir) && Message("Remove package dir: $pkgDir");
533
    }
5485 dpurdie 534
}
535
 
536
#-------------------------------------------------------------------------------
5498 dpurdie 537
# Function        : writeFileInfo 
538
#
539
# Description     : Write out an XML file that contains this processes
540
#                   contribution to the output package 
541
#
542
# Inputs          : $targetFile             - File to write XML into
543
#                   $fileList               - Ref to an array of file data 
544
#
545
# Returns         : 
546
#
547
sub writeFileInfo
548
{
549
    my ($targetFile, $fileList) = @_;
550
 
551
    my $data;
552
    $data->{file} = $fileList;
553
 
554
    #
555
    #   Write out sections of XML
556
    #       Want control over the output order
557
    #       Use lots of attributes and only elements for arrays
558
    #       Save as one attribute per line - for readability
559
    #
560
    my $xs = XML::Simple->new( NoAttr =>0, AttrIndent => 1 );
561
 
562
    open (my $XML, '>', $targetFile) || Error ("Cannot create output file: $targetFile", $!);
563
    $xs->XMLout($data, 
564
                'RootName' => 'files', 
565
                'XMLDecl'  => '<?xml version="1.0" encoding="UTF-8"?>',
566
                'OutputFile' => $XML);
567
    close $XML;
568
 
569
}
570
 
571
 
572
#-------------------------------------------------------------------------------
5485 dpurdie 573
# Function        : reportMergeError 
574
#
575
# Description     : Report an error or a warning
576
#
577
# Inputs          : All arguments passed to ReportError or Warning
578
#
579
# Returns         : Nothing 
580
#
581
sub reportMergeError
582
{
5486 dpurdie 583
    $opt_MergeErrors ? Warning(@_) : ReportError(@_);
5485 dpurdie 584
}
585
 
586
#-------------------------------------------------------------------------------
587
#   Documentation
588
#
589
 
590
=pod
591
 
592
=for htmltoc    SYSUTIL::
593
 
594
=head1 NAME
595
 
596
create_dpkgFromTar - Create a dpkg_archive entry from a set of tar files
597
 
598
=head1 SYNOPSIS
599
 
600
 jats create_dpkgFromTar [options]
601
 
602
 Options:
603
    -help              - Brief help message
604
    -help -help        - Detailed help message
605
    -man               - Full documentation
606
    -verbose           - Display additional progress messages
607
    -pname=name        - Ensure package is named correctly
608
    -pversion=version  - Ensure package version is correct
609
    -srcdir=path       - Location of the package fragments
610
 
5486 dpurdie 611
  Debug and Testing:
612
    -[no]mergeErrors   - Allow merge errors
613
    -[no]predelete     - Predelete generated package
614
    -output=path       - Base of test package archive
615
 
5485 dpurdie 616
=head1 OPTIONS
617
 
618
=over 8
619
 
620
=item B<-help>
621
 
622
Print a brief help message and exits.
623
 
624
=item B<-help -help>
625
 
626
Print a detailed help message with an explanation for each option.
627
 
628
=item B<-man>
629
 
630
Prints the manual page and exits.
631
 
632
=item B<-srcdir=path>
633
 
634
This option specifies the path of the packages fragments. The fragments will be
635
located using the package name and package version.
636
 
637
=item B<-pname=name>
638
 
639
The name of the target package
640
 
641
=item B<-pversion=version>
642
 
643
The version of the target package.
644
 
5486 dpurdie 645
=item B<-[no]mergeErrors>
646
 
647
This option allows the merging process to continue if merge errors are located.
648
The default is -noMergeErrors
649
 
650
This option is  intended for testing use only.
651
 
652
=item B<-[no]predelete>
653
 
654
This option will delete the target package instance before the package is assembled.
655
The default is -noPreDelete
656
 
657
This option is  intended for testing use only.
658
 
659
=item B<-output=path>
660
 
661
This option allows the user to specify to root of a test package archive.
662
The dafualt is to use the value provided by GBE_DPKG - the main package archive.
663
 
664
This option is  intended for testing use only.
665
 
5485 dpurdie 666
=back
667
 
668
=head1 DESCRIPTION
669
 
670
This utility program is used by the build system to merge build artifacts from several
671
build machines into one package.
672
 
673
The build artifacts have been delivered to the package store as a collection
674
of zipped tar files (.tar.gz). There will be one tar file from each machine in the build set.
675
 
676
The process has been designed to overcome several problems:
677
 
678
=over 4
679
 
680
=item Speed
681
 
682
If some of the build machines are not co-located with the master package server, then 
683
the process of transferring a package with a large number of files can be very slow.
684
 
685
ie: > 1 second per file to transfer a file from AWS(Sydney) to PCC(Perth). 
686
If a package has several thousand files then this can take an hour.
687
 
688
If the packaged files are compressed into a single file, then the file creation overhead is eliminated.
689
 
690
=item Atomic File Creation
691
 
692
For package fragments to be transferred from multiple machines without error some form of 
693
multi-machine mutex is required. This has not been successfully implemented - after many attempts.
694
 
695
If the merge operation is done by the package server, then there is no need for a mutex.
696
 
697
=back
698
 
699
The process of transferring tarballs and then merging then in one location solves these two problems.
700
 
701
The reconstruction process is performed by a daemon on the package archive server to address the following issues:
702
 
703
=over 4
704
 
705
=item * Windows handling of symlinks
706
 
707
Symbolic links will be handled correctly on the package server as the file system is native.
708
 
709
=item * Network Speed
710
 
711
By running the merge on the package server the contents of the package are not dragged to and 
712
from the build server. If the build server is not co-located with the package archive then there
713
will be a major speed penalty.
714
 
715
=back
716
 
717
The basic process performed by this utility is:
718
 
719
=over 4
720
 
721
=item * 
722
 
723
Locate all parts of the package. There should be one from each build machine that is a part 
724
of the build set, unless the build was generic. For each package fragment:
725
 
726
=over 4
727
 
728
=item * 
729
 
730
Extract a 'built.files.<machname>' file - the file must exist.
731
 
732
=item *
733
 
734
Read all 'built.files.<machname>' files and in the process determine if there are any conflicts.
735
A conflict is deemed to exist if the files have different MD5 digests. This allows the same file
736
to be provided by different builds - as long as the content is the same. Line endings are handled
737
in a machine independent manner. 
738
 
739
=item *
740
 
741
Detect dead symbolic links.
742
 
743
=back
744
 
745
=item *
746
 
747
If there are no file conflicts or other detected errors, then all parts of the package will be 
748
extracted into a single directory.
749
 
750
=item *
751
 
752
File permisions will be adjusted. All directories will be made world readable and all files will be made world executable.
753
 
754
=back
755
 
756
=cut
757