Subversion Repositories DevTools

Rev

Rev 5568 | Go to most recent revision | Details | Last modification | View Log | RSS feed

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