Subversion Repositories DevTools

Rev

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

Rev Author Line No. Line
4625 dpurdie 1
########################################################################
2
# Copyright (c) VIX TECHNOLOGY (AUST) LTD
3
#
4
# Module name   : jats_gen_releasenote.pl
5
# Module type   : Makefile system
6
# Compiler(s)   : Perl
7
# Environment(s): jats
8
#
9
# Description   : Generate the package Release Note
10
#                 Requires
11
#                   1) Release Note data generated by jats_get_releasenote_data.pl
12
#                   2) Target package in dpkg_archive
13
#                   3) One or more built.files.xxx.xml (optional, but desirable)
14
#
15
#                 Will:
16
#                   Merge built.files.xxx.xml files into the release note data
17
#                   Insert build meta data
18
#                   Save the Release Note MetaData within the package
19
#                   Generate the HTML release note - save within the package
20
#                   Insert Release_Contents into RM Database
21
#
22
# Usage:        See POD
23
#
24
#......................................................................#
25
 
26
require 5.008_002;
27
use strict;
28
use warnings;
29
 
30
use Pod::Usage;
31
use Getopt::Long;
32
use XML::Simple;
33
use File::Path;
34
use File::Copy;
35
use XML::Simple;
36
use Digest::MD5;
37
use File::Find;
38
use FindBin;                                # Determine the current directory
39
 
40
use JatsError;
41
use JatsSystem;
42
use Getopt::Long;
43
use Pod::Usage;
44
use FileUtils;
45
use JatsEnv;
46
use JatsRmApi;
47
 
48
use DBI;
49
 
50
my $VERSION = "1.0.0";                                      # Update this. Inserted into meta data field
51
 
52
# User options
53
my $opt_verbose = 0;
54
my $opt_help = 0;
55
my $opt_archive;
56
my $archive_tag;
57
my $opt_pname;
58
my $opt_pversion;
59
my $opt_release_note;
60
my $opt_pvid;
61
my $opt_outname;
62
my $opt_outputXml;
63
my $opt_updateRmFiles;
64
my $opt_workingDir;
65
 
66
#   Global vars
67
our $GBE_HOSTNAME;
68
my $DPKG_ROOT;
69
my $DPKG_DOC;
70
my $JATS_RN;
71
my $OUT_ROOT;
72
 
73
 
74
#
75
#   Structure to translate -archive=xxx option to archive variable
76
#   These are the various dpkg_archives known to JATS
77
#
78
my %Archive2Var =( 'main'       =>  'GBE_DPKG',
79
                   'store'      =>  'GBE_DPKG_STORE',
80
                   'cache'      =>  'GBE_DPKG_CACHE',
81
                   'local'      =>  'GBE_DPKG_LOCAL',
82
                   'sandbox'    =>  'GBE_DPKG_SBOX',
83
                   'deploy'     =>  'GBE_DPLY',
84
                   );
85
 
86
#-------------------------------------------------------------------------------
87
# Function        : Main Entry
88
#
89
# Description     :
90
#
91
# Inputs          :
92
#
93
# Returns         :
94
#
95
{
96
    my $result = GetOptions (
97
                    "help+"         => \$opt_help,              # flag, multiple use allowed
98
                    "manual:3"      => \$opt_help,              # flag, set value
99
                    "verbose:+"     => \$opt_verbose,           # flag, increment
100
                    "archive=s"     => \$opt_archive,           # string
101
                    "releasenote=s" => \$opt_release_note,      # string
102
                    "UpdateRmFiles" => \$opt_updateRmFiles,     # flag
103
                    "WorkDir=s"     => \$opt_workingDir,        # String
104
                    );
105
 
106
    #
107
    #   Process help and manual options
108
    #
109
    pod2usage(-verbose => 0, -message => "Version: $VERSION")  if ($opt_help == 1  || ! $result);
110
    pod2usage(-verbose => 1)  if ($opt_help == 2 );
111
    pod2usage(-verbose => 2)  if ($opt_help > 2);
112
 
113
    ErrorConfig( 'name'    =>'GenRn', 'verbose' => $opt_verbose );
114
    InitFileUtils();
115
 
116
    #
117
    #   Needed EnvVars
118
    #
119
    EnvImport ('GBE_HOSTNAME');
120
    $JATS_RN = $FindBin::Bin;
121
    Error("Release Note tools not found", $JATS_RN)
122
        unless (-d $JATS_RN);
123
 
124
 
125
    #
126
    #   Sanity Check
127
    #
128
    Error("Release Note data not provided") unless $opt_release_note;
129
    Error("Release Note data not found") unless -f $opt_release_note;
130
    if (defined $opt_workingDir)
131
    {
132
        $opt_workingDir = FullPath($opt_workingDir);
133
        Error("Working directory is not a directory") unless -d $opt_workingDir;
134
        Error("Working directory is writable") unless -w $opt_workingDir;
135
    }
136
 
137
    #
138
    #   Determine the target archive
139
    #   The default archive is GBE_DPKG, but this may be changed
140
    #
141
    $opt_archive = 'main' unless ( $opt_archive );
142
    my $archive_tag = $Archive2Var{$opt_archive};
143
    Error("Unknown archive specified: $opt_archive")
144
        unless ( $archive_tag );
145
    $DPKG_ROOT = $ENV{$archive_tag} || '';
146
    Verbose ("Archive Variable: $archive_tag" );
147
    Verbose2 ("Archive Path: $DPKG_ROOT" );
148
 
149
    #
150
    #   Process the release note and extract essential information
151
    #
152
    processReleaseNote();
153
 
154
    #
155
    #   Locate the target package
156
    #
157
    $DPKG_ROOT = catdir($DPKG_ROOT, $opt_pname, $opt_pversion);
158
    Verbose ("Package Path: ", DisplayPath($DPKG_ROOT) );
159
    Error ("Package not found in archive", $DPKG_ROOT) unless -d $DPKG_ROOT;
160
 
161
    #   OUT_ROOT is really only used for testing
162
    #   Needs to mimic $DPKG_ROOT
163
    $OUT_ROOT = defined($opt_workingDir) ? $opt_workingDir : $DPKG_ROOT; 
164
    Verbose2 ("Output Path: $OUT_ROOT" );
165
 
166
    #
167
    #   Calculate output filenames
168
    #   Html Release Note:
169
    #       RELEASE_NOTES_PVID_PKGNAME_CLEANV.html
170
    #
171
    $DPKG_DOC = catdir($OUT_ROOT, 'doc');
172
    $opt_outputXml = catdir($DPKG_DOC, 'release_note.xml');
173
 
174
    my $cleanv = $opt_pversion;
175
    $cleanv =~ s~\.~_~g;
176
    $opt_outname = join('_',
177
                        'RELEASE_NOTES',
178
                        $opt_pvid,
179
                        $opt_pname,
180
                        $cleanv,    
181
                        ) . '.html';
182
    Verbose("Note Name: $opt_outname");
183
 
184
    #
185
    #   Locate the release note data file
186
    #   This was created as a prerequisite to the build
187
    #   Note: windows requires '/' in the file list
188
    #
189
    my @filesList;
190
    foreach my $item ( glob(catdir($DPKG_ROOT, 'built.files.*.xml'))) {
191
        $item =~ s~\\~/~g;
192
        push @filesList, $item;
193
    }
194
    unless (scalar @filesList > 0)
195
    {
196
        Warning("No file list found within the package");
197
        #
198
        #   No filelist found in the package
199
        #   This may occur for packages that did not go though the build system
200
        #   Create a package-list
201
        #
202
        my $item = generateFileList();
203
        $item =~ s~\\~/~g;
204
        push @filesList, $item;
205
    }
206
 
207
    #
208
    #   Create output directory within the package
209
    #
210
    mkpath($DPKG_DOC, 0, 0775);
211
 
212
    #
213
    #   Merge the Release Note and the various file lists into a single
214
    #   XML file. Keep the file local
215
    #
216
    System('--NoShell', '--Exit', 'java', '-jar', 
217
           catdir($JATS_RN, 'saxon9he.jar'), 
218
           '-xsl:' . catdir($JATS_RN, 'merge.xslt'),
219
           "-s:$opt_release_note",
220
           'fileList=' . join(',', @filesList),
221
           '-o:' . 'built.files.releasenote.xml'
222
            );
223
 
224
    #
225
    #   Generate the HTML Release Note
226
    #   Output directly to the body of the package
227
    #
228
    System('--NoShell', '--Exit', 'java', '-jar', 
229
           catdir($JATS_RN, 'saxon9he.jar'), 
230
           '-xsl:' . catdir($JATS_RN, 'releaseNote2html.xslt'),
231
           '-s:' . 'built.files.releasenote.xml',
232
           "-o:" . catdir($DPKG_DOC, $opt_outname)
233
            );
234
 
235
    #
236
    #   Transfer the XML database directly into the package
237
    #
238
    unless( File::Copy::copy('built.files.releasenote.xml', $opt_outputXml) )
239
    {
240
        Error("Cannot transfer XML release note", $!, $opt_outputXml);
241
    }
242
 
243
    #
244
    #   Delete the built.files.xxx.xml that may be present in the root of the package
245
    #   These are no longer needed - they have been incorporated into the release_note.xml file
246
    #
247
    foreach my $item ( glob(catdir($DPKG_ROOT, 'built.files.*.xml'))) {
248
        unlink $item;
249
    }
250
 
251
    #
252
    #   Update the Release Manager entry - File Data
253
    #
254
    if ($opt_updateRmFiles)
255
    {
256
        updateRmFiles();
257
        updateRmNoteInfo();
258
    }
259
 
260
    #
261
    #   All done
262
    #
263
    Verbose ("Release Note:", DisplayPath(catdir($DPKG_DOC, $opt_outname)));
264
    exit 0;
265
}
266
 
267
#-------------------------------------------------------------------------------
268
# Function        : processReleaseNote 
269
#
270
# Description     : Extract essential information from the Release Note Data
271
#
272
# Inputs          : 
273
#
274
# Returns         : Populates global variables 
275
#
276
sub processReleaseNote
277
{
278
    my $xml = XMLin($opt_release_note);
279
 
280
    foreach my $item ( qw (name version pvid) ) {
281
        Error("Unexpected ReleaseNote format: package $item")
282
            unless (exists $xml->{package}{$item});
283
    }
284
 
285
    $opt_pname = $xml->{package}{name};
286
    $opt_pversion = $xml->{package}{version};
287
    $opt_pvid = $xml->{package}{pvid};
288
 
289
    Verbose("Package Name: $opt_pname") ;
290
    Verbose("Package Version: $opt_pversion") ;
291
    Verbose("Package Pvid: $opt_pvid") ;
292
}
293
 
294
#-------------------------------------------------------------------------------
295
# Function        : generateFileList
296
#                   generateFileListProc 
297
#
298
# Description     : A fileList has not been found in the target package
299
#                   Perhaps the package was not created by the build system
300
#                   
301
#                   Generate a fileList        
302
#
303
# Inputs          : None
304
#
305
# Returns         : Path to the generated file list
306
#
307
my @generateFileListData;
308
my $baseLength;
309
sub generateFileList
310
{
311
    my $outXmlFile;
312
    Verbose("Generate internal filelist - none found in package");
313
 
314
    #
315
    #   Scan the package and process each file
316
    #
317
    $baseLength = length($DPKG_ROOT);
318
    File::Find::find( \&generateFileListProc, $DPKG_ROOT );
319
 
320
    #
321
    #   Write out XML of the Generated file list
322
    #
323
    my $data;
324
    $data->{file} = \@generateFileListData;
325
 
326
    #
327
    #   Write out sections of XML
328
    #       Want control over the output order
329
    #       Use lots of attributes and only elements for arrays
330
    #       Save as one attribute per line - for readability
331
    #
332
    $outXmlFile = catdir($OUT_ROOT,"built.files.$GBE_HOSTNAME.xml");
333
 
334
    Verbose2('Meta File', $outXmlFile);
335
    my $xs = XML::Simple->new( NoAttr =>0, AttrIndent => 1 );
336
 
337
    open (my $XML, '>', $outXmlFile) || Error ("Cannot create output file: $outXmlFile", $!);
338
    $xs->XMLout($data, 
339
                'RootName' => 'files', 
340
                'XMLDecl'  => '<?xml version="1.0" encoding="UTF-8"?>',
341
                'OutputFile' => $XML);
342
    close $XML;
343
 
344
    return $outXmlFile;
345
}
346
 
347
sub generateFileListProc
348
{
349
    my %data;
350
    my $md5sum;
351
    my $source = $File::Find::name;
352
    my $type = 'dir';
353
 
354
    my $target = substr($source, $baseLength);
355
    $target =~ s~^/~~;
356
    $target =~ s~/$~~;
357
    Verbose2("GenFileList: $source, $target");
358
    return if (length ($target) == 0);
359
 
360
    if (-l $source)
361
    {
362
        $type = 'link';
363
    }
364
    elsif ( -f $source)
365
    {
366
        $type = 'file';
367
        Verbose2("Calculate MD5 Digest: $source");
368
        open(my $fh , $source) or Error ("Can't open '$source': $!");
369
        binmode $fh, ':crlf';
370
        $md5sum = Digest::MD5->new->addfile($fh)->hexdigest;
371
        close $fh;
372
    }
373
 
374
 
375
    if (-d $source)
376
    {
377
        $data{path} = $target;
378
    }
379
    else
380
    {
381
        $data{path} = StripFileExt($target);
382
        $data{name} = StripDir($target);
383
        if (defined $md5sum)
384
        {
385
            $data{size} = (stat($source))[7];
386
            $data{md5sum} = $md5sum;
387
        }
388
    }
389
 
390
    $data{fullname} = $target;
391
    $data{type} = $type;
392
    $data{machtype} = 'unknown';
393
    $data{host} = $GBE_HOSTNAME;
394
 
395
    # Put a nice '/' on the end of the patch elements
396
    $data{path} .= '/'
397
        if ( exists ($data{path}) && length($data{path}) > 0);
398
 
399
    push @generateFileListData, \%data;
400
}
401
 
402
#-------------------------------------------------------------------------------
403
# Function        : updateRmFiles 
404
#
405
# Description     : 
406
#
407
# Inputs          : 
408
#
409
# Returns         : 
410
#
411
my $RM_DB;
412
my  $updateRmFilesData;
413
sub updateRmFiles
414
{
415
    my $eCount = 0;
416
    #
417
    #   If in use the the autobuilder - which it should be, then
418
    #   modify the user name to access RM with a proxy user. This is the
419
    #   same method used in the 'buildtool'
420
    #
421
    if ($ENV{GBE_ABT} && $ENV{GBE_RM_USERNAME} && $ENV{GBE_RM_USERNAME} !~ m~]$~ )
422
    {
423
        Verbose("Access RM database as proxy user");
424
        $ENV{GBE_RM_USERNAME} = $ENV{GBE_RM_USERNAME} . '[release_manager]'; 
425
    }
426
 
427
    #
428
    #   Read in the release note data base
429
    #
430
    my $rnDataBase = catdir($DPKG_DOC, 'release_note.xml');
431
    if (! -f $rnDataBase )
432
    {
433
        Error("Release Note XML database not found: $!", $rnDataBase);
434
    }
435
    my $xml = XMLin($rnDataBase);
436
 
437
    #
438
    #   Sanity Test the data
439
    #
440
    Error("Sanity Check Failure. PVID") unless($opt_pvid eq $xml->{package}{pvid});
441
    Error("Sanity Check Failure. Name") unless($opt_pname eq $xml->{package}{name});
442
    Error("Sanity Check Failure. Version") unless($opt_pversion eq $xml->{package}{version});
443
    Error("Sanity Check Failure. File Section") unless(exists $xml->{files}{file});
444
    #DebugDumpData("XML DATA", \$xml);
445
 
446
    #
447
    #   Delete any existing entry(s)
448
    #
449
    updateRmFilesDelete();
450
 
451
    #
452
    #   Scan each entry and pump required data into the RM Database
453
    #   The {files}{file} may be either an array or a hash XMLin appears to
454
    #   make some decision as to which it will be, and I can't control it
455
    #
456
    #   When it does happen the hash key is the 'name' element
457
    #
458
    if (ref($xml->{files}{file}) eq 'HASH')
459
    {
460
        Verbose("Convert file hash to an array");
461
        my @nowArray;
462
        foreach my $name ( keys %{$xml->{files}{file}})
463
        {
464
            my $entry = $xml->{files}{file}{$name};
465
            $entry->{name} = $name;
466
            push @nowArray, $entry;
467
        }
468
        $xml->{files}{file} = \@nowArray
469
    }
470
    Error("Sanity Check Failure. File Section not an ARRAY") unless(ref($xml->{files}{file}) eq 'ARRAY');
471
 
472
    #
473
    #   Release Manager Database will barf if there are duplicate entries
474
    #   Maintain a hash of items processed, and only process each once
475
    #
476
    my %fullNameSeen;
477
 
478
    $updateRmFilesData = ""; 
479
    foreach my $entry (@{$xml->{files}{file}})
480
    {
481
        #
482
        #   Ignore 'merge' entries
483
        #
484
        next if ((exists $entry->{type}) && ($entry->{type} eq 'merge'));
485
        #
486
        #   Clean up the data
487
        #
488
        my $fname = $entry->{name} || '';
489
        my $fpath  = $entry->{path} || '';
490
        my $fsize  = $entry->{size} || 0;
491
        my $fmd5  = $entry->{md5sum} || '';
492
        $fpath =~ s~/$~~ unless $fname;
493
 
494
        unless( $fullNameSeen{$fpath}{$fname} )
495
        {
496
            $fullNameSeen{$fpath}{$fname} = 1;
497
 
498
            #
499
            #   Create SQL fragment for the insert
500
            #
501
            $eCount++;
502
            my $entry = " INTO release_manager.release_components ( pv_id, file_name, file_path, byte_size, crc_cksum, crc_modcrc ) " .
503
                        " VALUES ( $opt_pvid, '$fname', '$fpath',$fsize , '$fmd5', '')";
504
            $updateRmFilesData .= $entry;
505
 
506
            # The size of the aggregation is key to performance
507
            # Too big is as bad as too small
508
            if (length($updateRmFilesData) > 10000)
509
            {
510
                updateRmFilesInsert();
511
            }
512
        }
513
        else
514
        {
515
            Warning("Multiple file entries for: $fpath $fname");
516
        }
517
    }
518
    updateRmFilesInsert();
519
    Verbose ("Inserted $eCount entries into Release_Components");
520
}
521
 
522
#-------------------------------------------------------------------------------
523
# Function        : updateRmFilesInsert 
524
#
525
# Description     : Insert entries using the partial SQL statement 
526
#                   Must be called when the partial SQL buffer get large
527
#                   as well as at the end to fluch any outstanding inserts
528
#
529
# Inputs          : 
530
#
531
# Returns         : 
532
#
533
sub updateRmFilesInsert
534
{
535
    if (length($updateRmFilesData) > 0)
536
    {
537
        executeRmQuery(
538
            'updateRmFilesInsert',
539
            'INSERT ALL' .$updateRmFilesData . ' SELECT 1 FROM DUAL'
540
            );
541
 
542
        $updateRmFilesData = ""; 
543
    }
544
}
545
#-------------------------------------------------------------------------------
546
# Function        : updateRmFilesDelete 
547
#
548
# Description     : Delete ALL entires in the 'release_components' table associated
549
#                   with the current pvid
550
#                   
551
#                   Needs to be done before new entries are added.    
552
#
553
# Inputs          : 
554
#
555
# Returns         : 
556
#
557
sub updateRmFilesDelete
558
{
559
    executeRmQuery(
560
        'updateRmFilesDelete',
561
        'delete from release_manager.release_components where pv_id=' . $opt_pvid
562
        );
563
}
564
 
565
#-------------------------------------------------------------------------------
566
# Function        : updateRmNoteInfo 
567
#
568
# Description     : Insert Release Note Info into the Release Manager Database
569
#                   This has the side effect that RM will see the Release Note
570
#                   as being fully generated.
571
#
572
# Inputs          : 
573
#
574
# Returns         : 
575
#
576
sub updateRmNoteInfo
577
{
578
    #
579
    #   Determine the path to the Release Note in a form that is suitable for the Release Manager
580
    #   Database. This expects: /dpkg_archive/PkgName/PkgVer/doc/pkgFileName
581
    #
582
    my $rnPath = join('/', '', 'dpkg_archive', $opt_pname, $opt_pversion, 'doc', $opt_outname);
583
    Verbose("RN_INFO:", $rnPath);
584
 
585
    # Into the database
586
    executeRmQuery (
587
        'updateRmNoteInfo', 
588
        'update release_manager.package_versions set release_notes_info=\'' . $rnPath . '\' where pv_id=' . $opt_pvid
589
        );
590
}
591
 
592
#-------------------------------------------------------------------------------
593
# Function        : executeRmQuery 
594
#
595
# Description     : Execute a simple RM query. One that does not expect any return data
596
#
597
# Inputs          : $fname           - OprName, for error reporting
598
#                   $m_sqlstr        - SQL String
599
#
600
# Returns         : Will exit on error
601
#
602
sub executeRmQuery
603
{
604
    my ($fname, $m_sqlstr) = @_;
605
 
606
    #
607
    #   Connect to the Database - once
608
    #
609
    connectRM(\$RM_DB, 0) unless $RM_DB;
610
 
611
    Verbose2('ExecuteQuery:', $fname);
612
    #
613
    #   Create the full SQL statement
614
    #
615
    my $sth = $RM_DB->prepare($m_sqlstr);
616
    if ( defined($sth) )
617
    {
618
        if ( $sth->execute() )
619
        {
620
            $sth->finish();
621
        }
622
        else
623
        {
624
            Error("$fname: Execute failure: $m_sqlstr", $sth->errstr() );
625
        }
626
    }
627
    else
628
    {
629
        Error("$fname: Prepare failure");
630
    }
631
}
632
 
633
 
634
#-------------------------------------------------------------------------------
635
#   Documentation
636
#
637
 
638
=pod
639
 
640
=for htmltoc    SYSUTIL::
641
 
642
=head1 NAME
643
 
644
jats_gen_releasenote - Generate a Release Note
645
 
646
=head1 SYNOPSIS
647
 
648
 jats jats_gen_releasenote [options]
649
 
650
 Options:
651
    -help              - Brief help message
652
    -help -help        - Detailed help message
653
    -man               - Full documentation
654
    -verbose           - Display additional progress messages
655
    -outfile=name      - [Optional] Name of the output XML file
656
    -archive=name      - [Optional] Name package archive
657
    -releasenote=path  - Path to the Release Note Data
658
    -UpdateRmFiles     - Update the Files list in Release Manager
659
 
660
 
661
=head1 OPTIONS
662
 
663
=over 8
664
 
665
=item B<-help>
666
 
667
Print a brief help message and exits.
668
 
669
=item B<-help -help>
670
 
671
Print a detailed help message with an explanation for each option.
672
 
673
=item B<-man>
674
 
675
Prints the manual page and exits.
676
 
677
=item B<-pvid=nn>
678
 
679
This option provides identifies the PackageVersion to be processed.
680
 
681
This option is mandatory.
682
 
683
=item B<-outfile=name>
684
 
685
This option specifies the output file name.
686
 
687
If not provided by the user the output filename will be created in the current directory
688
and it will be named after the package name and package version.
689
 
690
If the filename does not end in .xml, then .xml will be appended to the file name.
691
 
692
=item B<-UpdateRmFiles>
693
 
694
This option will case the utility to ppdate the Files list in Release Manager.
695
 
696
The existing file list will be replaced by the one within the package.
697
 
698
Note: Write Access to Release Manager is required.
699
 
700
=back
701
 
702
=head1 DESCRIPTION
703
 
704
This utility program is used to extract sufficient information from Release Manager and other
705
associated databases so that a Release Note can be created.
706
 
707
The extracted data is stored in an XML format. The intent is that XSLT will be used to create
708
an HTML based release note.
709
 
710
 
711
=head1 EXAMPLE
712
 
713
=head2 jats jats_gen_releasenote -releasenote=/tmp/myreleasenote.xml
714
 
715
This will process release note data in the specified XML file.
716
 
717
=cut