~laney/ubuntu-archive-tools/cm-show-fix-released

« back to all changes in this revision

Viewing changes to component-mismatches

  • Committer: Colin Watson
  • Date: 2014-07-01 12:02:06 UTC
  • Revision ID: cjwatson@canonical.com-20140701120206-qnmfbpwmeu3to2kf
*-mismatches: Optionally produce HTML versions of reports.

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
 
25
25
from __future__ import print_function
26
26
 
 
27
__metaclass__ = type
 
28
 
27
29
import atexit
28
30
from collections import defaultdict
29
31
import copy
30
32
import gzip
 
33
try:
 
34
    from html import escape
 
35
except ImportError:
 
36
    from cgi import escape
31
37
from optparse import OptionParser
32
38
import os
33
39
import shutil
34
40
import sys
35
41
import tempfile
 
42
from textwrap import dedent
36
43
import time
37
44
try:
38
45
    from urllib.parse import quote_plus
58
65
seed_binary = defaultdict(set)
59
66
 
60
67
 
 
68
class MIRLink:
 
69
    def __init__(self, id, status, title):
 
70
        self.id = id
 
71
        self.status = status
 
72
        self.title = title
 
73
 
 
74
    def __str__(self):
 
75
        s = "MIR: #%d (%s)" % (self.id, self.status)
 
76
        # no need to repeat the standard title
 
77
        if not self.title.startswith("[MIR]"):
 
78
            s += " %s" % self.title
 
79
        return s
 
80
 
 
81
    def html(self):
 
82
        h = 'MIR: <a href="https://launchpad.net/bugs/%d">#%d</a> (%s)' % (
 
83
            self.id, self.id, escape(self.status))
 
84
        # no need to repeat the standard title
 
85
        if not self.title.startswith("[MIR]"):
 
86
            h += " %s" % escape(self.title)
 
87
        return h
 
88
 
 
89
 
61
90
def ensure_tempdir():
62
91
    global tempdir
63
92
    if not tempdir:
216
245
 
217
246
 
218
247
def do_reverse(source, binaries, why_d):
219
 
    output = ""
 
248
    output = []
220
249
    depend = {}
221
250
    recommend = {}
222
251
    build_depend = {}
242
271
                keys.append(k)
243
272
        keys.sort()
244
273
        if keys:
245
 
            return "   [Reverse-%s: %s]\n" % (category, ", ".join(keys))
 
274
            return ["[Reverse-%s: %s]" % (category, ", ".join(keys))]
246
275
        else:
247
 
            return ""
 
276
            return []
248
277
 
249
 
    output += do_category(depend, 'Depends')
250
 
    output += do_category(recommend, 'Recommends')
251
 
    output += do_category(build_depend, 'Build-Depends')
252
 
    output += "\n"
 
278
    output.extend(do_category(depend, 'Depends'))
 
279
    output.extend(do_category(recommend, 'Recommends'))
 
280
    output.extend(do_category(build_depend, 'Build-Depends'))
253
281
 
254
282
    return output
255
283
 
377
405
        if b in archive_binary and archive_binary[b][1] == component]
378
406
 
379
407
 
380
 
def print_section(header, body):
 
408
def print_section(options, header, body,
 
409
                  source_and_binary=False, binary_only=False):
381
410
    if body:
382
411
        print(" %s" % header)
383
412
        print(" %s" % ("-" * len(header)))
384
413
        print()
385
 
        print(body.rstrip())
386
 
        print()
 
414
        for entry in body:
 
415
            line = entry[0]
 
416
            if source_and_binary:
 
417
                source = line[0]
 
418
                binaries = line[1:]
 
419
                print(" o %s: %s" % (source, " ".join(binaries)))
 
420
            elif binary_only:
 
421
                binaries = " ".join(line[:-1])
 
422
                source = line[-1]
 
423
                indent_right = 75 - len(binaries) - len(source) - 2
 
424
                print(" o %s%s{%s}" % (binaries, " " * indent_right, source))
 
425
            else:
 
426
                print(" o %s" % line)
 
427
            for line in entry[1:]:
 
428
                print("   %s" % line)
 
429
            if len(entry) != 1:
 
430
                print()
 
431
        if len(body[-1]) == 1:
 
432
            print()
387
433
        print("=" * 70)
388
434
        print()
389
435
 
 
436
    if body and options.html_output is not None:
 
437
        def print_html(*args, **kwargs):
 
438
            print(*args, file=options.html_output, **kwargs)
 
439
 
 
440
        def source_link(source):
 
441
            return (
 
442
                '<a href="https://launchpad.net/ubuntu/+source/%s">%s</a>' % (
 
443
                    escape(source, quote=True), escape(source)))
 
444
 
 
445
        print_html("<h2>%s</h2>" % escape(header))
 
446
        print_html("<table>")
 
447
        for entry in body:
 
448
            line = entry[0]
 
449
            if source_and_binary:
 
450
                source = line[0]
 
451
                binaries = " ".join(line[1:])
 
452
                print_html(
 
453
                    '<tr><th colspan="2">%s: %s' % (
 
454
                        source_link(source), escape(binaries)))
 
455
            elif binary_only:
 
456
                binaries = " ".join(line[:-1])
 
457
                source = line[-1]
 
458
                print_html('<tr><th>%s</th>' % escape(binaries), end="")
 
459
                print_html(
 
460
                    "<th><small>%s</small></th></tr>" % source_link(source))
 
461
            else:
 
462
                print_html('<tr><th colspan="2">%s</th></tr>' % escape(line))
 
463
            for line in entry[1:]:
 
464
                if isinstance(line, MIRLink):
 
465
                    line = line.html()
 
466
                else:
 
467
                    line = escape(line)
 
468
                print_html(
 
469
                    '<tr><td colspan="2"><span class="note">%s'
 
470
                    '</span></td></tr>' % line)
 
471
        print_html("</table>")
 
472
 
390
473
 
391
474
def do_output(options,
392
475
              orig_source_add, orig_source_remove, binary_add, binary_remove,
393
476
              mir_bugs):
 
477
    if options.html_output is not None:
 
478
        print(dedent("""\
 
479
            <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
 
480
             "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
 
481
            <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 
482
            <head>
 
483
              <meta http-equiv="Content-Type"
 
484
                    content="text/html; charset=utf-8" />
 
485
              <title>Component mismatches for %s</title>
 
486
              <style type="text/css">
 
487
                body { background: #CCCCB0; color: black; }
 
488
                a { text-decoration: none; }
 
489
                table { border-collapse: collapse; border-style: none none;
 
490
                        margin-bottom: 3ex; empty-cells: show; }
 
491
                table th { text-align: left;
 
492
                           border-style: groove none none none;
 
493
                           border-width: 3px; padding-right: 10px;
 
494
                           font-weight: normal; }
 
495
                table td { vertical-align: top; text-align: left;
 
496
                           border-style: none none;
 
497
                           border-width: 1px; padding-right: 10px; }
 
498
                .note { margin-left: 3ex; }
 
499
              </style>
 
500
            </head>
 
501
            <body>
 
502
            <h1>Component mismatches for %s</h1>
 
503
            """) % (escape(options.suite), escape(options.suite)),
 
504
            file=options.html_output)
 
505
 
394
506
    # Additions
395
507
 
396
508
    binary_only = defaultdict(dict)
416
528
        else:
417
529
            continue
418
530
 
419
 
        output = ""
 
531
        output = []
420
532
        for source in filter_source(counterpart, sorted(both)):
421
533
            binaries = sorted(both[source])
422
 
            output += " o %s: %s\n" % (source, " ".join(binaries))
 
534
            entry = [[source] + binaries]
423
535
 
424
536
            for (id, status, title) in mir_bugs.get(source, []):
425
 
                if title.startswith('[MIR]'):
426
 
                    # no need to repeat the standard title
427
 
                    output += '   MIR: #%i (%s)\n' % (id, status)
428
 
                else:
429
 
                    output += '   MIR: #%i (%s) %s\n' % (id, status, title)
430
 
 
431
 
            output += do_reverse(source, binaries, both)
432
 
 
433
 
        print_section("Source and binary movements to %s" % component, output)
434
 
 
435
 
        output = ""
 
537
                entry.append(MIRLink(id, status, title))
 
538
 
 
539
            entry.extend(do_reverse(source, binaries, both))
 
540
            output.append(entry)
 
541
 
 
542
        print_section(
 
543
            options, "Source and binary movements to %s" % component, output,
 
544
            source_and_binary=True)
 
545
 
 
546
        output = []
436
547
        for source in sorted(binary_only):
437
548
            binaries = filter_binary(counterpart, sorted(binary_only[source]))
438
549
 
439
550
            if binaries:
440
 
                what = " o %s" % (" ".join(binaries))
441
 
                indent_right = 78 - len(what) - len(source) - 2
442
 
                output += "%s%s{%s}\n" % (what, " " * indent_right, source)
443
 
 
444
 
                output += do_reverse(source, binaries, binary_only)
445
 
 
446
 
        print_section("Binary only movements to %s" % component, output)
447
 
 
448
 
        output = ""
 
551
                entry = [binaries + [source]]
 
552
                entry.extend(do_reverse(source, binaries, binary_only))
 
553
                output.append(entry)
 
554
 
 
555
        print_section(
 
556
            options, "Binary only movements to %s" % component, output,
 
557
            binary_only=True)
 
558
 
 
559
        output = []
449
560
        for source in filter_source(counterpart, sorted(source_add)):
450
 
            output += " o %s\n" % (source)
 
561
            output.append([source])
451
562
 
452
 
        print_section("Source only movements to %s" % component, output)
 
563
        print_section(
 
564
            options, "Source only movements to %s" % component, output)
453
565
 
454
566
    if options.dot:
455
567
        with open(options.dot, 'w') as f:
476
588
        else:
477
589
            continue
478
590
 
479
 
        output = ""
 
591
        output = []
480
592
        for source in filter_source(component, sorted(both)):
481
593
            binaries = sorted(both[source])
482
 
            output += " o %s: %s\n" % (source, " ".join(binaries))
 
594
            output.append([[source] + binaries])
483
595
 
484
596
        print_section(
485
 
            "Source and binary movements to %s" % counterpart, output)
 
597
            options, "Source and binary movements to %s" % counterpart, output,
 
598
            source_and_binary=True)
486
599
 
487
 
        output = ""
 
600
        output = []
488
601
        for source in sorted(binary_only):
489
602
            binaries = filter_binary(component, sorted(binary_only[source]))
490
603
 
491
604
            if binaries:
492
 
                what = " o %s" % (" ".join(binaries))
493
 
                indent_right = 78 - len(what) - len(source) - 2
494
 
                output += "%s%s{%s}\n" % (what, " " * indent_right, source)
495
 
 
496
 
        print_section("Binary only movements to %s" % counterpart, output)
497
 
 
498
 
        output = ""
 
605
                output.append([binaries + [source]])
 
606
 
 
607
        print_section(
 
608
            options, "Binary only movements to %s" % counterpart, output,
 
609
            binary_only=True)
 
610
 
 
611
        output = []
499
612
        for source in filter_source(component, sorted(source_remove)):
500
 
            output += " o %s\n" % (source)
501
 
 
502
 
        print_section("Source only movements to %s" % counterpart, output)
 
613
            output.append([source])
 
614
 
 
615
        print_section(
 
616
            options, "Source only movements to %s" % counterpart, output)
 
617
 
 
618
    if options.html_output is not None:
 
619
        print(
 
620
            "<p><small>Generated: %s</small></p>" % escape(options.timestamp),
 
621
            file=options.html_output)
 
622
        print("</body></html>", file=options.html_output)
503
623
 
504
624
 
505
625
def do_source_diff(options):
552
672
    parser.add_option(
553
673
        "-l", "--launchpad", dest="launchpad_instance", default="production")
554
674
    parser.add_option('-o', '--output-file', help='output to this file')
 
675
    parser.add_option('--html-output-file', help='output HTML to this file')
555
676
    parser.add_option('-s', '--suite', help='check this suite')
556
677
    parser.add_option('-f', '--flavours', default='ubuntu',
557
678
                      help='check these flavours (comma-separated)')
596
717
 
597
718
    if options.output_file is not None:
598
719
        sys.stdout = open('%s.new' % options.output_file, 'w')
 
720
    if options.html_output_file is not None:
 
721
        options.html_output = open('%s.new' % options.html_output_file, 'w')
 
722
    else:
 
723
        options.html_output = None
599
724
 
600
 
    print('Generated: %s' % time.strftime('%a %b %e %H:%M:%S %Z %Y'))
 
725
    options.timestamp = time.strftime('%a %b %e %H:%M:%S %Z %Y')
 
726
    print('Generated: %s' % options.timestamp)
601
727
    print()
602
728
 
603
729
    read_germinate(options)
610
736
        options, source_add, source_remove, binary_add, binary_remove,
611
737
        mir_bugs)
612
738
 
 
739
    if options.html_output_file is not None:
 
740
        options.html_output.close()
 
741
        os.rename(
 
742
            '%s.new' % options.html_output_file, options.html_output_file)
613
743
    if options.output_file is not None:
614
744
        sys.stdout.close()
615
745
        os.rename('%s.new' % options.output_file, options.output_file)