xref: /aosp_15_r20/external/libxkbcommon/test/xkeyboard-config-test.py.in (revision 2b949d0487e80d67f1fda82db69e101e761f8064)
1*2b949d04SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*2b949d04SAndroid Build Coastguard Workerimport argparse
3*2b949d04SAndroid Build Coastguard Workerimport multiprocessing
4*2b949d04SAndroid Build Coastguard Workerimport sys
5*2b949d04SAndroid Build Coastguard Workerimport subprocess
6*2b949d04SAndroid Build Coastguard Workerimport os
7*2b949d04SAndroid Build Coastguard Workerimport xml.etree.ElementTree as ET
8*2b949d04SAndroid Build Coastguard Workerfrom pathlib import Path
9*2b949d04SAndroid Build Coastguard Worker
10*2b949d04SAndroid Build Coastguard Worker
11*2b949d04SAndroid Build Coastguard Workerverbose = False
12*2b949d04SAndroid Build Coastguard Worker
13*2b949d04SAndroid Build Coastguard WorkerDEFAULT_RULES_XML = '@XKB_CONFIG_ROOT@/rules/evdev.xml'
14*2b949d04SAndroid Build Coastguard Worker
15*2b949d04SAndroid Build Coastguard Worker# Meson needs to fill this in so we can call the tool in the buildir.
16*2b949d04SAndroid Build Coastguard WorkerEXTRA_PATH = '@MESON_BUILD_ROOT@'
17*2b949d04SAndroid Build Coastguard Workeros.environ['PATH'] = ':'.join([EXTRA_PATH, os.getenv('PATH')])
18*2b949d04SAndroid Build Coastguard Worker
19*2b949d04SAndroid Build Coastguard Worker
20*2b949d04SAndroid Build Coastguard Workerdef escape(s):
21*2b949d04SAndroid Build Coastguard Worker    return s.replace('"', '\\"')
22*2b949d04SAndroid Build Coastguard Worker
23*2b949d04SAndroid Build Coastguard Worker
24*2b949d04SAndroid Build Coastguard Worker# The function generating the progress bar (if any).
25*2b949d04SAndroid Build Coastguard Workerdef create_progress_bar(verbose):
26*2b949d04SAndroid Build Coastguard Worker    def noop_progress_bar(x, total, file=None):
27*2b949d04SAndroid Build Coastguard Worker        return x
28*2b949d04SAndroid Build Coastguard Worker
29*2b949d04SAndroid Build Coastguard Worker    progress_bar = noop_progress_bar
30*2b949d04SAndroid Build Coastguard Worker    if not verbose and os.isatty(sys.stdout.fileno()):
31*2b949d04SAndroid Build Coastguard Worker        try:
32*2b949d04SAndroid Build Coastguard Worker            from tqdm import tqdm
33*2b949d04SAndroid Build Coastguard Worker            progress_bar = tqdm
34*2b949d04SAndroid Build Coastguard Worker        except ImportError:
35*2b949d04SAndroid Build Coastguard Worker            pass
36*2b949d04SAndroid Build Coastguard Worker
37*2b949d04SAndroid Build Coastguard Worker    return progress_bar
38*2b949d04SAndroid Build Coastguard Worker
39*2b949d04SAndroid Build Coastguard Worker
40*2b949d04SAndroid Build Coastguard Workerclass Invocation:
41*2b949d04SAndroid Build Coastguard Worker    def __init__(self, r, m, l, v, o):
42*2b949d04SAndroid Build Coastguard Worker        self.command = ""
43*2b949d04SAndroid Build Coastguard Worker        self.rules = r
44*2b949d04SAndroid Build Coastguard Worker        self.model = m
45*2b949d04SAndroid Build Coastguard Worker        self.layout = l
46*2b949d04SAndroid Build Coastguard Worker        self.variant = v
47*2b949d04SAndroid Build Coastguard Worker        self.option = o
48*2b949d04SAndroid Build Coastguard Worker        self.exitstatus = 77  # default to skipped
49*2b949d04SAndroid Build Coastguard Worker        self.error = None
50*2b949d04SAndroid Build Coastguard Worker        self.keymap = None  # The fully compiled keymap
51*2b949d04SAndroid Build Coastguard Worker
52*2b949d04SAndroid Build Coastguard Worker    @property
53*2b949d04SAndroid Build Coastguard Worker    def rmlvo(self):
54*2b949d04SAndroid Build Coastguard Worker        return self.rules, self.model, self.layout, self.variant, self.option
55*2b949d04SAndroid Build Coastguard Worker
56*2b949d04SAndroid Build Coastguard Worker    def __str__(self):
57*2b949d04SAndroid Build Coastguard Worker        s = []
58*2b949d04SAndroid Build Coastguard Worker        rmlvo = [x or "" for x in self.rmlvo]
59*2b949d04SAndroid Build Coastguard Worker        rmlvo = ', '.join([f'"{x}"' for x in rmlvo])
60*2b949d04SAndroid Build Coastguard Worker        s.append(f'- rmlvo: [{rmlvo}]')
61*2b949d04SAndroid Build Coastguard Worker        s.append(f'  cmd: "{escape(self.command)}"')
62*2b949d04SAndroid Build Coastguard Worker        s.append(f'  status: {self.exitstatus}')
63*2b949d04SAndroid Build Coastguard Worker        if self.error:
64*2b949d04SAndroid Build Coastguard Worker            s.append(f'  error: "{escape(self.error.strip())}"')
65*2b949d04SAndroid Build Coastguard Worker        return '\n'.join(s)
66*2b949d04SAndroid Build Coastguard Worker
67*2b949d04SAndroid Build Coastguard Worker    def run(self):
68*2b949d04SAndroid Build Coastguard Worker        raise NotImplementedError
69*2b949d04SAndroid Build Coastguard Worker
70*2b949d04SAndroid Build Coastguard Worker
71*2b949d04SAndroid Build Coastguard Workerclass XkbCompInvocation(Invocation):
72*2b949d04SAndroid Build Coastguard Worker    def run(self):
73*2b949d04SAndroid Build Coastguard Worker        r, m, l, v, o = self.rmlvo
74*2b949d04SAndroid Build Coastguard Worker        args = ['setxkbmap', '-print']
75*2b949d04SAndroid Build Coastguard Worker        if r is not None:
76*2b949d04SAndroid Build Coastguard Worker            args.append('-rules')
77*2b949d04SAndroid Build Coastguard Worker            args.append('{}'.format(r))
78*2b949d04SAndroid Build Coastguard Worker        if m is not None:
79*2b949d04SAndroid Build Coastguard Worker            args.append('-model')
80*2b949d04SAndroid Build Coastguard Worker            args.append('{}'.format(m))
81*2b949d04SAndroid Build Coastguard Worker        if l is not None:
82*2b949d04SAndroid Build Coastguard Worker            args.append('-layout')
83*2b949d04SAndroid Build Coastguard Worker            args.append('{}'.format(l))
84*2b949d04SAndroid Build Coastguard Worker        if v is not None:
85*2b949d04SAndroid Build Coastguard Worker            args.append('-variant')
86*2b949d04SAndroid Build Coastguard Worker            args.append('{}'.format(v))
87*2b949d04SAndroid Build Coastguard Worker        if o is not None:
88*2b949d04SAndroid Build Coastguard Worker            args.append('-option')
89*2b949d04SAndroid Build Coastguard Worker            args.append('{}'.format(o))
90*2b949d04SAndroid Build Coastguard Worker
91*2b949d04SAndroid Build Coastguard Worker        xkbcomp_args = ['xkbcomp', '-xkb', '-', '-']
92*2b949d04SAndroid Build Coastguard Worker
93*2b949d04SAndroid Build Coastguard Worker        self.command = " ".join(args + ["|"] + xkbcomp_args)
94*2b949d04SAndroid Build Coastguard Worker
95*2b949d04SAndroid Build Coastguard Worker        setxkbmap = subprocess.Popen(args, stdout=subprocess.PIPE,
96*2b949d04SAndroid Build Coastguard Worker                                     stderr=subprocess.PIPE, universal_newlines=True)
97*2b949d04SAndroid Build Coastguard Worker        stdout, stderr = setxkbmap.communicate()
98*2b949d04SAndroid Build Coastguard Worker        if "Cannot open display" in stderr:
99*2b949d04SAndroid Build Coastguard Worker            self.error = stderr
100*2b949d04SAndroid Build Coastguard Worker            self.exitstatus = 90
101*2b949d04SAndroid Build Coastguard Worker        else:
102*2b949d04SAndroid Build Coastguard Worker            xkbcomp = subprocess.Popen(xkbcomp_args, stdin=subprocess.PIPE,
103*2b949d04SAndroid Build Coastguard Worker                                       stdout=subprocess.PIPE, stderr=subprocess.PIPE,
104*2b949d04SAndroid Build Coastguard Worker                                       universal_newlines=True)
105*2b949d04SAndroid Build Coastguard Worker            stdout, stderr = xkbcomp.communicate(stdout)
106*2b949d04SAndroid Build Coastguard Worker            if xkbcomp.returncode != 0:
107*2b949d04SAndroid Build Coastguard Worker                self.error = "failed to compile keymap"
108*2b949d04SAndroid Build Coastguard Worker                self.exitstatus = xkbcomp.returncode
109*2b949d04SAndroid Build Coastguard Worker            else:
110*2b949d04SAndroid Build Coastguard Worker                self.keymap = stdout
111*2b949d04SAndroid Build Coastguard Worker                self.exitstatus = 0
112*2b949d04SAndroid Build Coastguard Worker
113*2b949d04SAndroid Build Coastguard Worker
114*2b949d04SAndroid Build Coastguard Workerclass XkbcommonInvocation(Invocation):
115*2b949d04SAndroid Build Coastguard Worker    def run(self):
116*2b949d04SAndroid Build Coastguard Worker        r, m, l, v, o = self.rmlvo
117*2b949d04SAndroid Build Coastguard Worker        args = [
118*2b949d04SAndroid Build Coastguard Worker            'xkbcli-compile-keymap',  # this is run in the builddir
119*2b949d04SAndroid Build Coastguard Worker            '--verbose',
120*2b949d04SAndroid Build Coastguard Worker            '--rules', r,
121*2b949d04SAndroid Build Coastguard Worker            '--model', m,
122*2b949d04SAndroid Build Coastguard Worker            '--layout', l,
123*2b949d04SAndroid Build Coastguard Worker        ]
124*2b949d04SAndroid Build Coastguard Worker        if v is not None:
125*2b949d04SAndroid Build Coastguard Worker            args += ['--variant', v]
126*2b949d04SAndroid Build Coastguard Worker        if o is not None:
127*2b949d04SAndroid Build Coastguard Worker            args += ['--options', o]
128*2b949d04SAndroid Build Coastguard Worker
129*2b949d04SAndroid Build Coastguard Worker        self.command = " ".join(args)
130*2b949d04SAndroid Build Coastguard Worker        try:
131*2b949d04SAndroid Build Coastguard Worker            output = subprocess.check_output(args, stderr=subprocess.STDOUT,
132*2b949d04SAndroid Build Coastguard Worker                                             universal_newlines=True)
133*2b949d04SAndroid Build Coastguard Worker            if "unrecognized keysym" in output:
134*2b949d04SAndroid Build Coastguard Worker                for line in output.split('\n'):
135*2b949d04SAndroid Build Coastguard Worker                    if "unrecognized keysym" in line:
136*2b949d04SAndroid Build Coastguard Worker                        self.error = line
137*2b949d04SAndroid Build Coastguard Worker                self.exitstatus = 99  # tool doesn't generate this one
138*2b949d04SAndroid Build Coastguard Worker            else:
139*2b949d04SAndroid Build Coastguard Worker                self.exitstatus = 0
140*2b949d04SAndroid Build Coastguard Worker                self.keymap = output
141*2b949d04SAndroid Build Coastguard Worker        except subprocess.CalledProcessError as err:
142*2b949d04SAndroid Build Coastguard Worker            self.error = "failed to compile keymap"
143*2b949d04SAndroid Build Coastguard Worker            self.exitstatus = err.returncode
144*2b949d04SAndroid Build Coastguard Worker
145*2b949d04SAndroid Build Coastguard Worker
146*2b949d04SAndroid Build Coastguard Workerdef xkbcommontool(rmlvo):
147*2b949d04SAndroid Build Coastguard Worker    try:
148*2b949d04SAndroid Build Coastguard Worker        r = rmlvo.get('r', 'evdev')
149*2b949d04SAndroid Build Coastguard Worker        m = rmlvo.get('m', 'pc105')
150*2b949d04SAndroid Build Coastguard Worker        l = rmlvo.get('l', 'us')
151*2b949d04SAndroid Build Coastguard Worker        v = rmlvo.get('v', None)
152*2b949d04SAndroid Build Coastguard Worker        o = rmlvo.get('o', None)
153*2b949d04SAndroid Build Coastguard Worker        tool = XkbcommonInvocation(r, m, l, v, o)
154*2b949d04SAndroid Build Coastguard Worker        tool.run()
155*2b949d04SAndroid Build Coastguard Worker        return tool
156*2b949d04SAndroid Build Coastguard Worker    except KeyboardInterrupt:
157*2b949d04SAndroid Build Coastguard Worker        pass
158*2b949d04SAndroid Build Coastguard Worker
159*2b949d04SAndroid Build Coastguard Worker
160*2b949d04SAndroid Build Coastguard Workerdef xkbcomp(rmlvo):
161*2b949d04SAndroid Build Coastguard Worker    try:
162*2b949d04SAndroid Build Coastguard Worker        r = rmlvo.get('r', 'evdev')
163*2b949d04SAndroid Build Coastguard Worker        m = rmlvo.get('m', 'pc105')
164*2b949d04SAndroid Build Coastguard Worker        l = rmlvo.get('l', 'us')
165*2b949d04SAndroid Build Coastguard Worker        v = rmlvo.get('v', None)
166*2b949d04SAndroid Build Coastguard Worker        o = rmlvo.get('o', None)
167*2b949d04SAndroid Build Coastguard Worker        tool = XkbCompInvocation(r, m, l, v, o)
168*2b949d04SAndroid Build Coastguard Worker        tool.run()
169*2b949d04SAndroid Build Coastguard Worker        return tool
170*2b949d04SAndroid Build Coastguard Worker    except KeyboardInterrupt:
171*2b949d04SAndroid Build Coastguard Worker        pass
172*2b949d04SAndroid Build Coastguard Worker
173*2b949d04SAndroid Build Coastguard Worker
174*2b949d04SAndroid Build Coastguard Workerdef parse(path):
175*2b949d04SAndroid Build Coastguard Worker    root = ET.fromstring(open(path).read())
176*2b949d04SAndroid Build Coastguard Worker    layouts = root.findall('layoutList/layout')
177*2b949d04SAndroid Build Coastguard Worker
178*2b949d04SAndroid Build Coastguard Worker    options = [
179*2b949d04SAndroid Build Coastguard Worker        e.text
180*2b949d04SAndroid Build Coastguard Worker        for e in root.findall('optionList/group/option/configItem/name')
181*2b949d04SAndroid Build Coastguard Worker    ]
182*2b949d04SAndroid Build Coastguard Worker
183*2b949d04SAndroid Build Coastguard Worker    combos = []
184*2b949d04SAndroid Build Coastguard Worker    for l in layouts:
185*2b949d04SAndroid Build Coastguard Worker        layout = l.find('configItem/name').text
186*2b949d04SAndroid Build Coastguard Worker        combos.append({'l': layout})
187*2b949d04SAndroid Build Coastguard Worker
188*2b949d04SAndroid Build Coastguard Worker        variants = l.findall('variantList/variant')
189*2b949d04SAndroid Build Coastguard Worker        for v in variants:
190*2b949d04SAndroid Build Coastguard Worker            variant = v.find('configItem/name').text
191*2b949d04SAndroid Build Coastguard Worker
192*2b949d04SAndroid Build Coastguard Worker            combos.append({'l': layout, 'v': variant})
193*2b949d04SAndroid Build Coastguard Worker            for option in options:
194*2b949d04SAndroid Build Coastguard Worker                combos.append({'l': layout, 'v': variant, 'o': option})
195*2b949d04SAndroid Build Coastguard Worker
196*2b949d04SAndroid Build Coastguard Worker    return combos
197*2b949d04SAndroid Build Coastguard Worker
198*2b949d04SAndroid Build Coastguard Worker
199*2b949d04SAndroid Build Coastguard Workerdef run(combos, tool, njobs, keymap_output_dir):
200*2b949d04SAndroid Build Coastguard Worker    if keymap_output_dir:
201*2b949d04SAndroid Build Coastguard Worker        keymap_output_dir = Path(keymap_output_dir)
202*2b949d04SAndroid Build Coastguard Worker        try:
203*2b949d04SAndroid Build Coastguard Worker            keymap_output_dir.mkdir()
204*2b949d04SAndroid Build Coastguard Worker        except FileExistsError as e:
205*2b949d04SAndroid Build Coastguard Worker            print(e, file=sys.stderr)
206*2b949d04SAndroid Build Coastguard Worker            return False
207*2b949d04SAndroid Build Coastguard Worker
208*2b949d04SAndroid Build Coastguard Worker    keymap_file = None
209*2b949d04SAndroid Build Coastguard Worker    keymap_file_fd = None
210*2b949d04SAndroid Build Coastguard Worker
211*2b949d04SAndroid Build Coastguard Worker    failed = False
212*2b949d04SAndroid Build Coastguard Worker    with multiprocessing.Pool(njobs) as p:
213*2b949d04SAndroid Build Coastguard Worker        results = p.imap_unordered(tool, combos)
214*2b949d04SAndroid Build Coastguard Worker        for invocation in progress_bar(results, total=len(combos), file=sys.stdout):
215*2b949d04SAndroid Build Coastguard Worker            if invocation.exitstatus != 0:
216*2b949d04SAndroid Build Coastguard Worker                failed = True
217*2b949d04SAndroid Build Coastguard Worker                target = sys.stderr
218*2b949d04SAndroid Build Coastguard Worker            else:
219*2b949d04SAndroid Build Coastguard Worker                target = sys.stdout if verbose else None
220*2b949d04SAndroid Build Coastguard Worker
221*2b949d04SAndroid Build Coastguard Worker            if target:
222*2b949d04SAndroid Build Coastguard Worker                print(invocation, file=target)
223*2b949d04SAndroid Build Coastguard Worker
224*2b949d04SAndroid Build Coastguard Worker            if keymap_output_dir:
225*2b949d04SAndroid Build Coastguard Worker                # we're running through the layouts in a somewhat sorted manner,
226*2b949d04SAndroid Build Coastguard Worker                # so let's keep the fd open until we switch layouts
227*2b949d04SAndroid Build Coastguard Worker                layout = invocation.layout
228*2b949d04SAndroid Build Coastguard Worker                if invocation.variant:
229*2b949d04SAndroid Build Coastguard Worker                    layout += f"({invocation.variant})"
230*2b949d04SAndroid Build Coastguard Worker                fname = keymap_output_dir / layout
231*2b949d04SAndroid Build Coastguard Worker                if fname != keymap_file:
232*2b949d04SAndroid Build Coastguard Worker                    keymap_file = fname
233*2b949d04SAndroid Build Coastguard Worker                    if keymap_file_fd:
234*2b949d04SAndroid Build Coastguard Worker                        keymap_file_fd.close()
235*2b949d04SAndroid Build Coastguard Worker                    keymap_file_fd = open(keymap_file, 'a')
236*2b949d04SAndroid Build Coastguard Worker
237*2b949d04SAndroid Build Coastguard Worker                rmlvo = ', '.join([x or '' for x in invocation.rmlvo])
238*2b949d04SAndroid Build Coastguard Worker                print(f"// {rmlvo}", file=keymap_file_fd)
239*2b949d04SAndroid Build Coastguard Worker                print(invocation.keymap, file=keymap_file_fd)
240*2b949d04SAndroid Build Coastguard Worker                keymap_file_fd.flush()
241*2b949d04SAndroid Build Coastguard Worker
242*2b949d04SAndroid Build Coastguard Worker    return failed
243*2b949d04SAndroid Build Coastguard Worker
244*2b949d04SAndroid Build Coastguard Worker
245*2b949d04SAndroid Build Coastguard Workerdef main(args):
246*2b949d04SAndroid Build Coastguard Worker    global progress_bar
247*2b949d04SAndroid Build Coastguard Worker    global verbose
248*2b949d04SAndroid Build Coastguard Worker
249*2b949d04SAndroid Build Coastguard Worker    tools = {
250*2b949d04SAndroid Build Coastguard Worker        'libxkbcommon': xkbcommontool,
251*2b949d04SAndroid Build Coastguard Worker        'xkbcomp': xkbcomp,
252*2b949d04SAndroid Build Coastguard Worker    }
253*2b949d04SAndroid Build Coastguard Worker
254*2b949d04SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(
255*2b949d04SAndroid Build Coastguard Worker        description='''
256*2b949d04SAndroid Build Coastguard Worker                    This tool compiles a keymap for each layout, variant and
257*2b949d04SAndroid Build Coastguard Worker                    options combination in the given rules XML file. The output
258*2b949d04SAndroid Build Coastguard Worker                    of this tool is YAML, use your favorite YAML parser to
259*2b949d04SAndroid Build Coastguard Worker                    extract error messages. Errors are printed to stderr.
260*2b949d04SAndroid Build Coastguard Worker                    '''
261*2b949d04SAndroid Build Coastguard Worker    )
262*2b949d04SAndroid Build Coastguard Worker    parser.add_argument('path', metavar='/path/to/evdev.xml',
263*2b949d04SAndroid Build Coastguard Worker                        nargs='?', type=str,
264*2b949d04SAndroid Build Coastguard Worker                        default=DEFAULT_RULES_XML,
265*2b949d04SAndroid Build Coastguard Worker                        help='Path to xkeyboard-config\'s evdev.xml')
266*2b949d04SAndroid Build Coastguard Worker    parser.add_argument('--tool', choices=tools.keys(),
267*2b949d04SAndroid Build Coastguard Worker                        type=str, default='libxkbcommon',
268*2b949d04SAndroid Build Coastguard Worker                        help='parsing tool to use')
269*2b949d04SAndroid Build Coastguard Worker    parser.add_argument('--jobs', '-j', type=int,
270*2b949d04SAndroid Build Coastguard Worker                        default=os.cpu_count() * 4,
271*2b949d04SAndroid Build Coastguard Worker                        help='number of processes to use')
272*2b949d04SAndroid Build Coastguard Worker    parser.add_argument('--verbose', '-v', default=False, action="store_true")
273*2b949d04SAndroid Build Coastguard Worker    parser.add_argument('--keymap-output-dir', default=None, type=str,
274*2b949d04SAndroid Build Coastguard Worker                        help='Directory to print compiled keymaps to')
275*2b949d04SAndroid Build Coastguard Worker    parser.add_argument('--layout', default=None, type=str,
276*2b949d04SAndroid Build Coastguard Worker                        help='Only test the given layout')
277*2b949d04SAndroid Build Coastguard Worker    parser.add_argument('--variant', default=None, type=str,
278*2b949d04SAndroid Build Coastguard Worker                        help='Only test the given variant')
279*2b949d04SAndroid Build Coastguard Worker    parser.add_argument('--option', default=None, type=str,
280*2b949d04SAndroid Build Coastguard Worker                        help='Only test the given option')
281*2b949d04SAndroid Build Coastguard Worker
282*2b949d04SAndroid Build Coastguard Worker    args = parser.parse_args()
283*2b949d04SAndroid Build Coastguard Worker
284*2b949d04SAndroid Build Coastguard Worker    verbose = args.verbose
285*2b949d04SAndroid Build Coastguard Worker    keymapdir = args.keymap_output_dir
286*2b949d04SAndroid Build Coastguard Worker    progress_bar = create_progress_bar(verbose)
287*2b949d04SAndroid Build Coastguard Worker
288*2b949d04SAndroid Build Coastguard Worker    tool = tools[args.tool]
289*2b949d04SAndroid Build Coastguard Worker
290*2b949d04SAndroid Build Coastguard Worker    if any([args.layout, args.variant, args.option]):
291*2b949d04SAndroid Build Coastguard Worker        combos = [{
292*2b949d04SAndroid Build Coastguard Worker            'l': args.layout,
293*2b949d04SAndroid Build Coastguard Worker            'v': args.variant,
294*2b949d04SAndroid Build Coastguard Worker            'o': args.option,
295*2b949d04SAndroid Build Coastguard Worker        }]
296*2b949d04SAndroid Build Coastguard Worker    else:
297*2b949d04SAndroid Build Coastguard Worker        combos = parse(args.path)
298*2b949d04SAndroid Build Coastguard Worker    failed = run(combos, tool, args.jobs, keymapdir)
299*2b949d04SAndroid Build Coastguard Worker    sys.exit(failed)
300*2b949d04SAndroid Build Coastguard Worker
301*2b949d04SAndroid Build Coastguard Worker
302*2b949d04SAndroid Build Coastguard Workerif __name__ == '__main__':
303*2b949d04SAndroid Build Coastguard Worker    try:
304*2b949d04SAndroid Build Coastguard Worker        main(sys.argv)
305*2b949d04SAndroid Build Coastguard Worker    except KeyboardInterrupt:
306*2b949d04SAndroid Build Coastguard Worker        print('# Exiting after Ctrl+C')
307