Completed
Push — master ( c39d6a...9ba086 )
by srz
22:40
created

wandbox_api_call()   C

Complexity

Conditions 8

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 8
c 1
b 1
f 0
dl 0
loc 22
rs 5.2631

1 Method

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