summaryrefslogtreecommitdiffstats
path: root/bitbake/bin/bitbake-diffsigs
blob: 5523b3385954c72d68af8660efcaeaaf3b2a64fa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#!/usr/bin/env python3

# bitbake-diffsigs / bitbake-dumpsig
# BitBake task signature data dump and comparison utility
#
# Copyright (C) 2012-2013, 2017 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
#

import os
import sys
import warnings
import argparse
import logging
import pickle

sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib'))

import bb.tinfoil
import bb.siggen
import bb.msg

myname = os.path.basename(sys.argv[0])
logger = bb.msg.logger_create(myname)

is_dump = myname == 'bitbake-dumpsig'

def find_siginfo(tinfoil, pn, taskname, sigs=None):
    result = None
    tinfoil.set_event_mask(['bb.event.FindSigInfoResult',
                            'logging.LogRecord',
                            'bb.command.CommandCompleted',
                            'bb.command.CommandFailed'])
    ret = tinfoil.run_command('findSigInfo', pn, taskname, sigs)
    if ret:
        while True:
            event = tinfoil.wait_event(1)
            if event:
                if isinstance(event, bb.command.CommandCompleted):
                    break
                elif isinstance(event, bb.command.CommandFailed):
                    logger.error(str(event))
                    sys.exit(2)
                elif isinstance(event, bb.event.FindSigInfoResult):
                    result = event.result
                elif isinstance(event, logging.LogRecord):
                    logger.handle(event)
    else:
        logger.error('No result returned from findSigInfo command')
        sys.exit(2)
    return result

def find_siginfo_task(bbhandler, pn, taskname, sig1=None, sig2=None):
    """ Find the most recent signature files for the specified PN/task """

    if not taskname.startswith('do_'):
        taskname = 'do_%s' % taskname

    if sig1 and sig2:
        sigfiles = find_siginfo(bbhandler, pn, taskname, [sig1, sig2])
        if len(sigfiles) == 0:
            logger.error('No sigdata files found matching %s %s matching either %s or %s' % (pn, taskname, sig1, sig2))
            sys.exit(1)
        elif not sig1 in sigfiles:
            logger.error('No sigdata files found matching %s %s with signature %s' % (pn, taskname, sig1))
            sys.exit(1)
        elif not sig2 in sigfiles:
            logger.error('No sigdata files found matching %s %s with signature %s' % (pn, taskname, sig2))
            sys.exit(1)
        latestfiles = [sigfiles[sig1], sigfiles[sig2]]
    else:
        filedates = find_siginfo(bbhandler, pn, taskname)
        latestfiles = sorted(filedates.keys(), key=lambda f: filedates[f])[-2:]
        if not latestfiles:
            logger.error('No sigdata files found matching %s %s' % (pn, taskname))
            sys.exit(1)

    return latestfiles


# Define recursion callback
def recursecb(key, hash1, hash2):
    hashes = [hash1, hash2]
    hashfiles = find_siginfo(tinfoil, key, None, hashes)

    recout = []
    if len(hashfiles) == 0:
        recout.append("Unable to find matching sigdata for %s with hashes %s or %s" % (key, hash1, hash2))
    elif not hash1 in hashfiles:
        recout.append("Unable to find matching sigdata for %s with hash %s" % (key, hash1))
    elif not hash2 in hashfiles:
        recout.append("Unable to find matching sigdata for %s with hash %s" % (key, hash2))
    else:
        out2 = bb.siggen.compare_sigfiles(hashfiles[hash1], hashfiles[hash2], recursecb, color=color)
        for change in out2:
            for line in change.splitlines():
                recout.append('  ' + line)

    return recout


parser = argparse.ArgumentParser(
    description=("Dumps" if is_dump else "Compares") + " siginfo/sigdata files written out by BitBake")

parser.add_argument('-D', '--debug',
                    help='Enable debug output',
                    action='store_true')

if is_dump:
    parser.add_argument("-t", "--task",
            help="find the signature data file for the last run of the specified task",
            action="store", dest="taskargs", nargs=2, metavar=('recipename', 'taskname'))

    parser.add_argument("sigdatafile1",
            help="Signature file to dump. Not used when using -t/--task.",
            action="store", nargs='?', metavar="sigdatafile")
else:
    parser.add_argument('-c', '--color',
            help='Colorize the output (where %(metavar)s is %(choices)s)',
            choices=['auto', 'always', 'never'], default='auto', metavar='color')

    parser.add_argument('-d', '--dump',
            help='Dump the last signature data instead of comparing (equivalent to using bitbake-dumpsig)',
            action='store_true')

    parser.add_argument("-t", "--task",
            help="find the signature data files for the last two runs of the specified task and compare them",
            action="store", dest="taskargs", nargs=2, metavar=('recipename', 'taskname'))

    parser.add_argument("-s", "--signature",
            help="With -t/--task, specify the signatures to look for instead of taking the last two",
            action="store", dest="sigargs", nargs=2, metavar=('fromsig', 'tosig'))

    parser.add_argument("sigdatafile1",
            help="First signature file to compare (or signature file to dump, if second not specified). Not used when using -t/--task.",
            action="store", nargs='?')

    parser.add_argument("sigdatafile2",
            help="Second signature file to compare",
            action="store", nargs='?')

options = parser.parse_args()
if is_dump:
    options.color = 'never'
    options.dump = True
    options.sigdatafile2 = None
    options.sigargs = None

if options.debug:
    logger.setLevel(logging.DEBUG)

color = (options.color == 'always' or (options.color == 'auto' and sys.stdout.isatty()))

if options.taskargs:
    with bb.tinfoil.Tinfoil() as tinfoil:
        tinfoil.prepare(config_only=True)
        if not options.dump and options.sigargs:
            files = find_siginfo_task(tinfoil, options.taskargs[0], options.taskargs[1], options.sigargs[0], options.sigargs[1])
        else:
            files = find_siginfo_task(tinfoil, options.taskargs[0], options.taskargs[1])

        if options.dump:
            logger.debug("Signature file: %s" % files[-1])
            output = bb.siggen.dump_sigfile(files[-1])
        else:
            if len(files) < 2:
                logger.error('Only one matching sigdata file found for the specified task (%s %s)' % (options.taskargs[0], options.taskargs[1]))
                sys.exit(1)

            # Recurse into signature comparison
            logger.debug("Signature file (previous): %s" % files[-2])
            logger.debug("Signature file (latest): %s" % files[-1])
            output = bb.siggen.compare_sigfiles(files[-2], files[-1], recursecb, color=color)
else:
    if options.sigargs:
        logger.error('-s/--signature can only be used together with -t/--task')
        sys.exit(1)
    try:
        if not options.dump and options.sigdatafile1 and options.sigdatafile2:
            with bb.tinfoil.Tinfoil() as tinfoil:
                tinfoil.prepare(config_only=True)
                output = bb.siggen.compare_sigfiles(options.sigdatafile1, options.sigdatafile2, recursecb, color=color)
        elif options.sigdatafile1:
            output = bb.siggen.dump_sigfile(options.sigdatafile1)
        else:
            logger.error('Must specify signature file(s) or -t/--task')
            parser.print_help()
            sys.exit(1)
    except IOError as e:
        logger.error(str(e))
        sys.exit(1)
    except (pickle.UnpicklingError, EOFError):
        logger.error('Invalid signature data - ensure you are specifying sigdata/siginfo files')
        sys.exit(1)

if output:
    print('\n'.join(output))