Completed
Push — master ( d5fdbe...1b7e63 )
by srz
08:59 queued 04:24
created

iuwandbox.listup_options()   C

Complexity

Conditions 9

Size

Total Lines 17
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 16
nop 1
dl 0
loc 17
rs 6.4615
c 0
b 0
f 0
1
#!/usr/bin/env python
2
#
3
# iuwandbox.py
4
#
5
# Copyright (C) 2014-2017, Takazumi Shirayanagi
6
# This software is released under the new BSD License,
7
# see LICENSE
8
#
9
10
import os
11
import sys
12
import re
13
import codecs
14
import argparse
15
16
from time import sleep
17
from argparse import ArgumentParser
18
from wandbox import Wandbox
19
from requests.exceptions import HTTPError
20
from requests.exceptions import ConnectionError
21
22
IUTEST_FUSED_SRC = os.path.normpath(os.path.join(os.path.dirname(__file__), '../../fused-src/iutest.min.hpp'))
23
IUTEST_INCLUDE_PATH = os.path.normpath(os.path.join(os.path.dirname(__file__), '../../include'))
24
IUTEST_INCLUDE_REGEX = re.compile(r'^\s*#\s*include\s*".*(iutest|iutest_switch)\.hpp"')
25
EXPAND_INCLUDE_REGEX = re.compile(r'^\s*#\s*include\s*"(.*?)"')
26
IUTEST_INCG_REGEX = re.compile(r'\s*#\s*define[/\s]*(INCG_IRIS_\S*)\s*')
27
28
iutest_incg_list = []
29
workaround = True
30
api_retries = 3
31
api_retry_wait = 60
32
33
34
# command line option
35
def parse_command_line():
36
    global api_retries
37
    global api_retry_wait
38
39
    parser = ArgumentParser()
40
    parser.add_argument(
41
        '-v',
42
        '--version',
43
        action='version',
44
        version=u'%(prog)s version 5.7'
45
    )
46
    parser.add_argument(
47
        '--list_compiler',
48
        '--list-compiler',
49
        action='store_true',
50
        help='listup compiler.'
51
    )
52
    parser.add_argument(
53
        '--list_options',
54
        '--list-options',
55
        metavar='COMPILER',
56
        help='listup compiler options.'
57
    )
58
    parser.add_argument(
59
        '-c',
60
        '--compiler',
61
        default='gcc-head',
62
        help='compiler select. default: %(default)s'
63
    )
64
    parser.add_argument(
65
        '-x',
66
        '--options',
67
        help='used options for a compiler.'
68
    )
69
    parser.add_argument(
70
        '--default',
71
        action='store_true',
72
        help='it is not work. default options are set by default (deprecated)'
73
    )
74
    parser.add_argument(
75
        '--no-default',
76
        action='store_true',
77
        help='no set default options.'
78
    )
79
    parser.add_argument(
80
        '--std',
81
        metavar='VERSION',
82
        help='set --std options.'
83
    )
84
    parser.add_argument(
85
        '--boost',
86
        metavar='VERSION',
87
        help='set boost options version X.XX or nothing.'
88
    )
89
    parser.add_argument(
90
        '--optimize',
91
        action='store_true',
92
        help='use optimization.'
93
    )
94
    parser.add_argument(
95
        '--cpp-verbose',
96
        action='store_true',
97
        help='use cpp-verbose.'
98
    )
99
    parser.add_argument(
100
        '--sprout',
101
        action='store_true',
102
        help='use sprout.'
103
    )
104
    parser.add_argument(
105
        '--msgpack',
106
        action='store_true',
107
        help='use msgpack.'
108
    )
109
    parser.add_argument(
110
        '--stdin',
111
        help='set stdin.'
112
    )
113
    parser.add_argument(
114
        '-f',
115
        '--compiler_option_raw',
116
        '--compiler-option-raw',
117
        metavar='OPTIONS',
118
        action='append',
119
        default=['-D__WANDBOX__'],
120
        help='compile-time any additional options.'
121
    )
122
    parser.add_argument(
123
        '-r',
124
        '--runtime_option_raw',
125
        '--runtime-option-raw',
126
        metavar='OPTIONS',
127
        action='append',
128
        help='runtime-time any additional options.'
129
    )
130
    parser.add_argument(
131
        '-s',
132
        '--save',
133
        action='store_true',
134
        help='generate permanent link.'
135
    )
136
    parser.add_argument(
137
        '--permlink',
138
        metavar='ID',
139
        help='get permanent link.'
140
    )
141
    parser.add_argument(
142
        '-o',
143
        '--output',
144
        metavar='FILE',
145
        help='output source code.'
146
    )
147
    parser.add_argument(
148
        '--xml',
149
        metavar='FILE',
150
        help='output result xml.'
151
    )
152
    parser.add_argument(
153
        '--junit',
154
        metavar='FILE',
155
        help='output result junit xml.'
156
    )
157
    parser.add_argument(
158
        '--stderr',
159
        action='store_true',
160
        help='output stderr.'
161
    )
162
    parser.add_argument(
163
        '--encoding',
164
        help='set encoding.'
165
    )
166
    parser.add_argument(
167
        '--expand_include',
168
        '--expand-include',
169
        action='store_true',
170
        help='expand include file.'
171
    )
172
    parser.add_argument(
173
        '--make',
174
        action='store_true',
175
        help=argparse.SUPPRESS
176
    )
177
    parser.add_argument(
178
        '--retry-wait',
179
        type=int,
180
        default=api_retry_wait,
181
        metavar='SECONDS',
182
        help='Wait time for retry when HTTPError occurs'
183
    )
184
    parser.add_argument(
185
        '--retry',
186
        type=int,
187
        default=api_retries,
188
        metavar='COUNT',
189
        help='Number of retries when HTTPError occurs'
190
    )
191
    parser.add_argument(
192
        '--check_config',
193
        '--check-config',
194
        action='store_true',
195
        help='check config.'
196
    )
197
    parser.add_argument(
198
        '--verbose',
199
        action='store_true',
200
        help='verbose.'
201
    )
202
    parser.add_argument(
203
        '--dryrun',
204
        action='store_true',
205
        help='dryrun.'
206
    )
207
    parser.add_argument(
208
        'code',
209
        metavar='CODE',
210
        nargs='*',
211
        help='source code file'
212
    )
213
    options = parser.parse_args()
214
    api_retries = options.retry
215
    api_retry_wait = options.retry_wait
216
    return options, parser
217
218
219
# file open
220
def file_open(path, mode, encoding):
221
    if encoding:
222
        file = codecs.open(path, mode, encoding)
223
    else:
224
        file = open(path, mode)
225
    return file
226
227
228
# make include filename
229
def make_include_filename(path, includes, included_files):
230
    if path in included_files:
231
        return included_files[path]
232
    else:
233
        include_dir, include_filename = os.path.split(path)
234
        while include_filename in includes:
235
            include_dir, dirname = os.path.split(include_dir)
236
            include_filename = dirname + '__' + include_filename
237
        included_files[path] = include_filename
238
        return include_filename
239
240
241
def is_iutest_included_file(filepath):
242
    if os.path.abspath(filepath).startswith(IUTEST_INCLUDE_PATH):
243
        incg = 'INCG_IRIS_' + os.path.basename(filepath).upper().replace('.', '_')
244
        for included_incg in iutest_incg_list:
245
            if included_incg.startswith(incg):
246
                return True
247
    return False
248
249
250
# make code
251
def make_code(path, encoding, expand, includes, included_files):
252
    code = ''
253
    file = file_open(path, 'r', encoding)
254
    for line in file:
255
        m = IUTEST_INCLUDE_REGEX.match(line)
256
        if m:
257
            code += '#include "iutest.hpp"\n'
258
            code += '//origin>> ' + line
259
            if 'iutest.hpp' not in includes:
260
                try:
261
                    f = codecs.open(IUTEST_FUSED_SRC, 'r', 'utf-8-sig')
262
                    iutest_src = f.read()
263
                    f.close()
264
                    includes['iutest.hpp'] = iutest_src
265
                    global iutest_incg_list
266
                    iutest_incg_list = IUTEST_INCG_REGEX.findall(iutest_src)
267
                except:
268
                    print('{0} is not found...'.format(IUTEST_FUSED_SRC))
269
                    print('please try \"make fused\"')
270
                    exit(1)
271
        else:
272
            m = EXPAND_INCLUDE_REGEX.match(line)
273
            if m:
274
                include_path = os.path.normpath(os.path.join(os.path.dirname(path), m.group(1)))
275
                if is_iutest_included_file(include_path):
276
                    code += '//origin>> '
277
                elif os.path.exists(include_path):
278
                    if expand:
279
                        expand_include_file_code = make_code(
280
                            include_path, encoding, expand, includes, included_files)
281
                        code += expand_include_file_code
282
                        code += '//origin>> '
283
                    else:
284
                        include_abspath = os.path.abspath(include_path)
285
                        include_filename = make_include_filename(
286
                            include_abspath, includes, included_files)
287
                        if not include_filename == include_path:
288
                            code += '#include "' + include_filename + '"\n'
289
                            code += '//origin>> '
290
                        if include_filename not in includes:
291
                            includes[include_filename] = ''
292
                            expand_include_file_code = make_code(
293
                                include_path, encoding, expand, includes, included_files)
294
                            includes[include_filename] = expand_include_file_code
295
            code += line
296
    file.close()
297
    return code
298
299
300
def print_undefined_option(option_name, compiler):
301
    print('Wandbox is not supported option [{0}] ({1})'.format(option_name, compiler))
302
303
304
# check config
305
def check_config(options):
306
    has_error = False
307
    if not find_compiler(options.compiler):
308
        print('Wandbox is not supported compiler [' + options.compiler + ']')
309
        listup_compiler()
310
        has_error = True
311
    if options.options or options.std:
312
        opt = get_options(options.compiler)
313
        if options.options:
314
            for o in options.options.split(','):
315
                if o not in opt:
316
                    print_undefined_option(o, options.compiler)
317
                    has_error = True
318
        if options.std:
319
            if options.std not in opt:
320
                print_undefined_option(options.std, options.compiler)
321
                has_error = True
322
        if has_error:
323
            listup_options(options.compiler)
324
    if has_error:
325
        sys.exit(1)
326
    if options.default:
327
        print('--default option is not work. default options are set by default (deprecated)')
328
329
330
# setup additional files
331
def add_files(w, fileinfos):
332
    for filename, code in fileinfos.items():
333
        w.add_file(filename, code)
334
335
336
# create opt list
337
def create_option_list(options):
338
    def filterout_cppver(opt):
339
        tmp = list(filter(lambda s: s.find('c++') == -1, opt))
340
        tmp = list(filter(lambda s: s.find('gnu++') == -1, tmp))
341
        return tmp
342
    opt = []
343
    if not options.no_default:
344
        opt = get_default_options(options.compiler)
345
    if options.options:
346
        for o in options.options.split(','):
347
            if o not in opt:
348
                if (o.find('c++') == 0) or (o.find('gnu++') == 0):
349
                    opt = filterout_cppver(opt)
350
                opt.append(o)
351
    # std
352
    if options.std:
353
        opt = filterout_cppver(opt)
354
        opt.append(options.std)
355
    # optimize
356
    if options.optimize and ('optimize' not in opt):
357
        opt.append('optimize')
358
    # cpp-verbose
359
    if options.cpp_verbose and ('cpp-verbose' not in opt):
360
        opt.append('cpp-verbose')
361
    # boost
362
    if workaround:
363
        pass
364
#        if options.compiler in ['clang-3.4', 'clang-3.3']:
365
#            if not options.boost:
366
#                options.boost = 'nothing'
367
    if options.boost:
368
        if options.compiler not in options.boost:
369
            options.boost = options.boost + '-' + options.compiler
370
        opt = list(filter(lambda s: s.find('boost') == -1, opt))
371
        opt.append('boost-' + str(options.boost))
372
    # sprout
373
    if options.sprout and ('sprout' not in opt):
374
        opt.append('sprout')
375
    # msgpack
376
    if options.msgpack and ('msgpack' not in opt):
377
        opt.append('msgpack')
378
    return opt
379
380
381
def expand_wandbox_options(w, compiler, options):
382
    colist = []
383
    defs = {}
384
    for d in w.get_compiler_list():
385
        if d['name'] == compiler:
386
            if 'switches' in d:
387
                switches = d['switches']
388
                for s in switches:
389
                    if ('name' in s) and ('display-flags' in s):
390
                        defs[s['name']] = s['display-flags']
391
                    elif 'options' in s:
392
                        for o in s['options']:
393
                            if ('name' in o) and ('display-flags' in o):
394
                                defs[o['name']] = o['display-flags']
395
    for opt in options:
396
        if opt in defs:
397
            colist.extend(defs[opt].split())
398
    return colist
399
400
401
def wandbox_api_call(callback, retries, retry_wait):
402
    try:
403
        return callback()
404
    except (HTTPError, ConnectionError) as e:
405
406
        def is_retry(e):
407
            if not e.response:
408
                return True
409
            return e.response.status_code in [504]
410
411
        if is_retry(e) and retries > 0:
412
            try:
413
                print(e.message)
414
            except:
415
                pass
416
            print('wait {0}sec...'.format(retry_wait))
417
            sleep(retry_wait)
418
            return wandbox_api_call(callback, retries - 1, retry_wait)
419
        else:
420
            raise
421
    except:
422
        raise
423
424
425
def wandbox_get_compilerlist():
426
    return wandbox_api_call(Wandbox.GetCompilerList, api_retries, api_retry_wait)
427
428
429
def run_wandbox_impl(w, options):
430
    if options.dryrun:
431
        sys.exit(0)
432
    retries = options.retry
433
434
    def run():
435
        return w.run()
436
    return wandbox_api_call(run, retries, options.retry_wait)
437
438
439
def create_compiler_raw_option_list(options):
440
    colist = []
441
    if options.compiler_option_raw:
442
        raw_options = options.compiler_option_raw
443
        for x in raw_options:
444
            colist.extend(re.split('\s(?=-)', x.strip('"')))
445
    return colist
446
447
448
# run wandbox (makefile)
449
def run_wandbox_make(main_filepath, code, includes, impliments, options):
450
    with Wandbox() as w:
451
        w.compiler('bash')
452
        woptions = create_option_list(options)
453
        if options.stdin:
454
            w.stdin(options.stdin)
455
        impliments[os.path.basename(main_filepath)] = code
456
457
        colist = create_compiler_raw_option_list(options)
458
        colist.extend(expand_wandbox_options(w, options.compiler, woptions))
459
460
        rolist = []
461
        if options.runtime_option_raw:
462
            for opt in options.runtime_option_raw:
463
                rolist.extend(opt.split())
464
465
        makefile = '#!/bin/make\n# generate makefile by iuwandbox.py\n'
466
        makefile += '\nCXXFLAGS+='
467
        for opt in colist:
468
            makefile += opt + ' '
469
        makefile += '\nOBJS='
470
        for filename in impliments.keys():
471
            makefile += os.path.splitext(filename)[0] + '.o '
472
473
        makefile += '\n\
474
prog: $(OBJS)\n\
475
\t$(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS)\n\
476
'
477
478
        impliments['Makefile'] = makefile
479
480
        bashscript = 'make -j 4\n'
481
        bashscript += './prog '
482
        for opt in rolist:
483
            bashscript += opt + ' '
484
        bashscript += '\n'
485
        w.code(bashscript)
486
487
        if options.save:
488
            w.permanent_link(options.save)
489
        if options.verbose:
490
            w.dump()
491
        add_files(w, impliments)
492
        add_files(w, includes)
493
494
        return run_wandbox_impl(w, options)
495
496
497
# run wandbox (cxx)
498
def run_wandbox_cxx(code, includes, impliments, options):
499
    with Wandbox() as w:
500
        w.compiler(options.compiler)
501
        w.options(','.join(create_option_list(options)))
502
        if options.stdin:
503
            w.stdin(options.stdin)
504
        colist = create_compiler_raw_option_list(options)
505
506
        if workaround:
507
            if options.compiler in ['clang-3.2']:
508
                colist.append('-ftemplate-depth=1024')
509
    #        if options.compiler in ['clang-3.4']:
510
    #            colist.append('-DIUTEST_HAS_HDR_CXXABI=0')
511
    #        if options.compiler in ['clang-3.3', 'clang-3.2', 'clang-3.1', 'clang-3.0']:
512
    #            colist.append('-Qunused-arguments')
513
    #        if options.compiler in ['clang-3.4', 'clang-3.3']:
514
    #            colist.append('-fno-exceptions')
515
    #            colist.append('-fno-rtti')
516
            pass
517
        if len(colist) > 0:
518
            co = '\n'.join(colist)
519
            co = co.replace('\\n', '\n')
520
            w.compiler_options(co)
521
        if options.runtime_option_raw:
522
            rolist = []
523
            for opt in options.runtime_option_raw:
524
                rolist.extend(opt.split())
525
            ro = '\n'.join(rolist)
526
            ro = ro.replace('\\n', '\n')
527
            w.runtime_options(ro)
528
        if options.save:
529
            w.permanent_link(options.save)
530
        for filename in impliments.keys():
531
            w.add_compiler_options(filename)
532
        if options.verbose:
533
            w.dump()
534
        w.code(code)
535
        add_files(w, impliments)
536
        add_files(w, includes)
537
538
        return run_wandbox_impl(w, options)
539
540
541
# run wandbox
542
def run_wandbox(main_filepath, code, includes, impliments, options):
543
    if options.make:
544
        return run_wandbox_make(main_filepath, code, includes, impliments, options)
545
    else:
546
        return run_wandbox_cxx(code, includes, impliments, options)
547
548
549
def wandbox_hint(r):
550
    if 'compiler_error' in r:
551
        if 'undefined reference to `init_unit_test_suite' in r['compiler_error']:
552
            print('hint:')
553
            print('  If you do not use boost test, please specify the file with the main function first.')
554
555
556
def text_transform(value):
557
    try:
558
        if isinstance(value, str):
559
            return value.decode()
560
        elif isinstance(value, unicode):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable unicode does not seem to be defined.
Loading history...
561
            return value.encode('utf_8')
562
    except:
563
        pass
564
    return value
565
566
567
# show result
568
def show_result(r, options):
569
    if 'error' in r:
570
        print(r['error'])
571
        sys.exit(1)
572
    if options.stderr:
573
        if 'compiler_output' in r:
574
            print('compiler_output:')
575
            print(text_transform(r['compiler_output']))
576
        if 'compiler_error' in r:
577
            sys.stderr.write(text_transform(r['compiler_error']))
578
        if 'program_output' in r:
579
            print('program_output:')
580
            print(text_transform(r['program_output']))
581
        if options.xml is None and options.junit is None and 'program_error' in r:
582
            sys.stderr.write(text_transform(r['program_error']))
583
    else:
584
        if 'compiler_message' in r:
585
            print('compiler_message:')
586
            print(text_transform(r['compiler_message']))
587
        if 'program_message' in r:
588
            print('program_message:')
589
            print(text_transform(r['program_message']))
590
    if 'url' in r:
591
        print('permlink: ' + r['permlink'])
592
        print('url: ' + r['url'])
593
    if 'signal' in r:
594
        print('signal: ' + r['signal'])
595
    wandbox_hint(r)
596
597
    if 'status' in r:
598
        return int(r['status'])
599
    return 1
600
601
602
# show parameter
603
def show_parameter(r):
604
    if 'compiler' in r:
605
        print('compiler:' + r['compiler'])
606
    if 'options' in r:
607
        print('options:' + r['options'])
608
    if 'compiler-option-raw' in r:
609
        print('compiler-option-raw:' + r['compiler-option-raw'])
610
    if 'runtime-option-raw' in r:
611
        print('runtime-option-raw' + r['runtime-option-raw'])
612
    if 'created-at' in r:
613
        print(r['created-at'])
614
615
616
def set_output_xml(options, t, xml):
617
    options.stderr = True
618
    if options.runtime_option_raw:
619
        options.runtime_option_raw.append('--iutest_output=' + t + ':' + xml)
620
    else:
621
        options.runtime_option_raw = ['--iutest_output=' + t + ':' + xml]
622
623
624
def run(options):
625
    main_filepath = options.code[0].strip()
626
    if not os.path.exists(main_filepath):
627
        sys.exit(1)
628
    includes = {}
629
    included_files = {}
630
    impliments = {}
631
    code = make_code(main_filepath, options.encoding, options.expand_include, includes, included_files)
632
633
    for filepath_ in options.code[1:]:
634
        filepath = filepath_.strip()
635
        impliments[os.path.basename(filepath)] = make_code(filepath, options.encoding, options.expand_include, includes, included_files)
636
637
    if options.output:
638
        f = file_open(options.output, 'w', options.encoding)
639
        f.write(code)
640
        f.close()
641
    xml = None
642
    if options.xml:
643
        xml = options.xml
644
        set_output_xml(options, 'xml', xml)
645
    if options.junit:
646
        xml = options.junit
647
        set_output_xml(options, 'junit', xml)
648
    r = run_wandbox(main_filepath, code, includes, impliments, options)
649
    b = show_result(r, options)
650
    if xml and 'program_error' in r:
651
        f = file_open(xml, 'w', options.encoding)
652
        f.write(r['program_error'])
653
        f.close()
654
    sys.exit(b)
655
656
657
# listup compiler
658
def listup_compiler(verbose):
659
    r = wandbox_get_compilerlist()
660
    for d in r:
661
        if d['language'] == 'C++':
662
            if verbose:
663
                print(d['name'] + ' (' + d['version'] + ')')
664
            else:
665
                print(d['name'])
666
667
668
# find compiler
669
def find_compiler(c):
670
    r = wandbox_get_compilerlist()
671
    for d in r:
672
        if d['language'] == 'C++' and d['name'] == c:
673
            return True
674
    return False
675
676
677
# listup options
678
def listup_options(compiler):
679
    r = wandbox_get_compilerlist()
680
    for d in r:
681
        if d['name'] == compiler:
682
            print('# ' + compiler)
683
            if 'switches' in d:
684
                switches = d['switches']
685
                for s in switches:
686
                    if 'name' in s:
687
                        if s['default']:
688
                            print(s['name'] + ' (default)')
689
                        else:
690
                            print(s['name'])
691
                    elif 'options' in s:
692
                        print(s['default'] + ' (default)')
693
                        for o in s['options']:
694
                            print('  ' + o['name'])
695
696
697 View Code Duplication
def get_options(compiler):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
698
    r = wandbox_get_compilerlist()
699
    opt = []
700
    for d in r:
701
        if d['name'] == compiler:
702
            if 'switches' in d:
703
                switches = d['switches']
704
                for s in switches:
705
                    if 'name' in s:
706
                        opt.append(s['name'])
707
                    elif 'options' in s:
708
                        opt.append(s['default'])
709
                        for o in s['options']:
710
                            opt.append(o['name'])
711
    return opt
712
713
714
# get default options
715 View Code Duplication
def get_default_options(compiler):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
716
    r = wandbox_get_compilerlist()
717
    opt = []
718
    for d in r:
719
        if d['name'] == compiler:
720
            if 'switches' in d:
721
                switches = d['switches']
722
                for s in switches:
723
                    if 'name' in s:
724
                        if s['default']:
725
                            opt.append(s['name'])
726
                    elif 'options' in s:
727
                        opt.append(s['default'])
728
    return opt
729
730
731
# get permlink
732
def get_permlink(options):
733
    r = Wandbox.GetPermlink(options.permlink)
734
    p = r['parameter']
735
    show_parameter(p)
736
    print('result:')
737
    b = show_result(r['result'], options)
738
    if options.output:
739
        f = open(options.output, 'w')
740
        f.write(p['code'])
741
        f.close()
742
    sys.exit(b)
743
744
745
def main():
746
    options, parser = parse_command_line()
747
    if options.list_compiler:
748
        listup_compiler(options.verbose)
749
    elif options.list_options:
750
        listup_options(options.list_options)
751
    elif options.permlink:
752
        get_permlink(options)
753
    else:
754
        if options.check_config:
755
            check_config(options)
756
        elif len(options.code) == 0:
757
            parser.print_help()
758
            sys.exit(1)
759
        run(options)
760
761
762
if __name__ == '__main__':
763
    main()
764