Completed
Push — master ( 1b7e63...915a02 )
by srz
05:26
created

iuwandbox.get_default_options()   B

Complexity

Conditions 5

Size

Total Lines 9
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
nop 1
dl 0
loc 9
rs 8.5454
c 0
b 0
f 0
1
#!/usr/bin/env python
2
#
3
# iuwandbox.py
4
#
5
# Copyright (C) 2014-2018, 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.8'
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
        '--iutest-use-main',
199
        action='store_true',
200
        help='define IUTEST_USE_MAIN.'
201
    )
202
    parser.add_argument(
203
        '--verbose',
204
        action='store_true',
205
        help='verbose.'
206
    )
207
    parser.add_argument(
208
        '--dryrun',
209
        action='store_true',
210
        help='dryrun.'
211
    )
212
    parser.add_argument(
213
        'code',
214
        metavar='CODE',
215
        nargs='*',
216
        help='source code file'
217
    )
218
    options = parser.parse_args()
219
    api_retries = options.retry
220
    api_retry_wait = options.retry_wait
221
    return options, parser
222
223
224
# file open
225
def file_open(path, mode, encoding):
226
    if encoding:
227
        file = codecs.open(path, mode, encoding)
228
    else:
229
        file = open(path, mode)
230
    return file
231
232
233
# make include filename
234
def make_include_filename(path, includes, included_files):
235
    if path in included_files:
236
        return included_files[path]
237
    else:
238
        include_dir, include_filename = os.path.split(path)
239
        while include_filename in includes:
240
            include_dir, dirname = os.path.split(include_dir)
241
            include_filename = dirname + '__' + include_filename
242
        included_files[path] = include_filename
243
        return include_filename
244
245
246
def is_iutest_included_file(filepath):
247
    if os.path.abspath(filepath).startswith(IUTEST_INCLUDE_PATH):
248
        incg = 'INCG_IRIS_' + os.path.basename(filepath).upper().replace('.', '_')
249
        for included_incg in iutest_incg_list:
250
            if included_incg.startswith(incg):
251
                return True
252
    return False
253
254
255
# make code
256
def make_code(path, encoding, expand, includes, included_files):
257
    code = ''
258
    file = file_open(path, 'r', encoding)
259
    for line in file:
260
        m = IUTEST_INCLUDE_REGEX.match(line)
261
        if m:
262
            code += '#include "iutest.hpp"\n'
263
            code += '//origin>> ' + line
264
            if 'iutest.hpp' not in includes:
265
                try:
266
                    f = codecs.open(IUTEST_FUSED_SRC, 'r', 'utf-8-sig')
267
                    iutest_src = f.read()
268
                    f.close()
269
                    includes['iutest.hpp'] = iutest_src
270
                    global iutest_incg_list
271
                    iutest_incg_list = IUTEST_INCG_REGEX.findall(iutest_src)
272
                except:
273
                    print('{0} is not found...'.format(IUTEST_FUSED_SRC))
274
                    print('please try \"make fused\"')
275
                    exit(1)
276
        else:
277
            m = EXPAND_INCLUDE_REGEX.match(line)
278
            if m:
279
                include_path = os.path.normpath(os.path.join(os.path.dirname(path), m.group(1)))
280
                if is_iutest_included_file(include_path):
281
                    code += '//origin>> '
282
                elif os.path.exists(include_path):
283
                    if expand:
284
                        expand_include_file_code = make_code(
285
                            include_path, encoding, expand, includes, included_files)
286
                        code += expand_include_file_code
287
                        code += '//origin>> '
288
                    else:
289
                        include_abspath = os.path.abspath(include_path)
290
                        include_filename = make_include_filename(
291
                            include_abspath, includes, included_files)
292
                        if not include_filename == include_path:
293
                            code += '#include "' + include_filename + '"\n'
294
                            code += '//origin>> '
295
                        if include_filename not in includes:
296
                            includes[include_filename] = ''
297
                            expand_include_file_code = make_code(
298
                                include_path, encoding, expand, includes, included_files)
299
                            includes[include_filename] = expand_include_file_code
300
            code += line
301
    file.close()
302
    return code
303
304
305
def print_undefined_option(option_name, compiler):
306
    print('Wandbox is not supported option [{0}] ({1})'.format(option_name, compiler))
307
308
309
# check config
310
def check_config(options):
311
    has_error = False
312
    if not find_compiler(options.compiler):
313
        print('Wandbox is not supported compiler [' + options.compiler + ']')
314
        listup_compiler()
315
        has_error = True
316
    if options.options or options.std:
317
        opt = get_options(options.compiler)
318
        if options.options:
319
            for o in options.options.split(','):
320
                if o not in opt:
321
                    print_undefined_option(o, options.compiler)
322
                    has_error = True
323
        if options.std:
324
            if options.std not in opt:
325
                print_undefined_option(options.std, options.compiler)
326
                has_error = True
327
        if has_error:
328
            listup_options(options.compiler)
329
    if has_error:
330
        sys.exit(1)
331
    if options.default:
332
        print('--default option is not work. default options are set by default (deprecated)')
333
334
335
# setup additional files
336
def add_files(w, fileinfos):
337
    for filename, code in fileinfos.items():
338
        w.add_file(filename, code)
339
340
341
# create opt list
342
def create_option_list(options):
343
    def filterout_cppver(opt):
344
        tmp = list(filter(lambda s: s.find('c++') == -1, opt))
345
        tmp = list(filter(lambda s: s.find('gnu++') == -1, tmp))
346
        return tmp
347
    opt = []
348
    if not options.no_default:
349
        opt = get_default_options(options.compiler)
350
    if options.options:
351
        for o in options.options.split(','):
352
            if o not in opt:
353
                if (o.find('c++') == 0) or (o.find('gnu++') == 0):
354
                    opt = filterout_cppver(opt)
355
                opt.append(o)
356
    # std
357
    if options.std:
358
        opt = filterout_cppver(opt)
359
        opt.append(options.std)
360
    # optimize
361
    if options.optimize and ('optimize' not in opt):
362
        opt.append('optimize')
363
    # cpp-verbose
364
    if options.cpp_verbose and ('cpp-verbose' not in opt):
365
        opt.append('cpp-verbose')
366
    # boost
367
    if workaround:
368
        pass
369
#        if options.compiler in ['clang-3.4', 'clang-3.3']:
370
#            if not options.boost:
371
#                options.boost = 'nothing'
372
    if options.boost:
373
        if options.compiler not in options.boost:
374
            options.boost = options.boost + '-' + options.compiler
375
        opt = list(filter(lambda s: s.find('boost') == -1, opt))
376
        opt.append('boost-' + str(options.boost))
377
    # sprout
378
    if options.sprout and ('sprout' not in opt):
379
        opt.append('sprout')
380
    # msgpack
381
    if options.msgpack and ('msgpack' not in opt):
382
        opt.append('msgpack')
383
    return opt
384
385
386
def expand_wandbox_options(w, compiler, options):
387
    colist = []
388
    defs = {}
389
    for d in w.get_compiler_list():
390
        if d['name'] == compiler:
391
            if 'switches' in d:
392
                switches = d['switches']
393
                for s in switches:
394
                    if ('name' in s) and ('display-flags' in s):
395
                        defs[s['name']] = s['display-flags']
396
                    elif 'options' in s:
397
                        for o in s['options']:
398
                            if ('name' in o) and ('display-flags' in o):
399
                                defs[o['name']] = o['display-flags']
400
    for opt in options:
401
        if opt in defs:
402
            colist.extend(defs[opt].split())
403
    return colist
404
405
406
def wandbox_api_call(callback, retries, retry_wait):
407
    try:
408
        return callback()
409
    except (HTTPError, ConnectionError) as e:
410
411
        def is_retry(e):
412
            if not e.response:
413
                return True
414
            return e.response.status_code in [504]
415
416
        if is_retry(e) and retries > 0:
417
            try:
418
                print(e.message)
419
            except:
420
                pass
421
            print('wait {0}sec...'.format(retry_wait))
422
            sleep(retry_wait)
423
            return wandbox_api_call(callback, retries - 1, retry_wait)
424
        else:
425
            raise
426
    except:
427
        raise
428
429
430
def wandbox_get_compilerlist():
431
    return wandbox_api_call(Wandbox.GetCompilerList, api_retries, api_retry_wait)
432
433
434
def wandbox_get_compilerswitches(compiler):
435
    for d in wandbox_get_compilerlist():
436
        if d['name'] == compiler:
437
            if 'switches' in d:
438
                return d['switches']
439
    return []
440
441
442
def run_wandbox_impl(w, options):
443
    if options.dryrun:
444
        sys.exit(0)
445
    retries = options.retry
446
447
    def run():
448
        return w.run()
449
    return wandbox_api_call(run, retries, options.retry_wait)
450
451
452
def create_compiler_raw_option_list(options):
453
    colist = []
454
    if options.compiler_option_raw:
455
        raw_options = options.compiler_option_raw
456
        for x in raw_options:
457
            colist.extend(re.split('\s(?=-)', x.strip('"')))
458
    if options.iutest_use_main:
459
        colist.append('-DIUTEST_USE_MAIN')
460
    return colist
461
462
463
# run wandbox (makefile)
464
def run_wandbox_make(main_filepath, code, includes, impliments, options):
465
    with Wandbox() as w:
466
        w.compiler('bash')
467
        woptions = create_option_list(options)
468
        if options.stdin:
469
            w.stdin(options.stdin)
470
        impliments[os.path.basename(main_filepath)] = code
471
472
        colist = create_compiler_raw_option_list(options)
473
        colist.extend(expand_wandbox_options(w, options.compiler, woptions))
474
475
        rolist = []
476
        if options.runtime_option_raw:
477
            for opt in options.runtime_option_raw:
478
                rolist.extend(opt.split())
479
480
        makefile = '#!/bin/make\n# generate makefile by iuwandbox.py\n'
481
        makefile += '\nCXXFLAGS+='
482
        for opt in colist:
483
            makefile += opt + ' '
484
        makefile += '\nOBJS='
485
        for filename in impliments.keys():
486
            makefile += os.path.splitext(filename)[0] + '.o '
487
488
        makefile += '\n\
489
prog: $(OBJS)\n\
490
\t$(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS)\n\
491
'
492
493
        impliments['Makefile'] = makefile
494
495
        bashscript = 'make -j 4\n'
496
        bashscript += './prog '
497
        for opt in rolist:
498
            bashscript += opt + ' '
499
        bashscript += '\n'
500
        w.code(bashscript)
501
502
        if options.save:
503
            w.permanent_link(options.save)
504
        if options.verbose:
505
            w.dump()
506
        add_files(w, impliments)
507
        add_files(w, includes)
508
509
        return run_wandbox_impl(w, options)
510
511
512
# run wandbox (cxx)
513
def run_wandbox_cxx(code, includes, impliments, options):
514
    with Wandbox() as w:
515
        w.compiler(options.compiler)
516
        w.options(','.join(create_option_list(options)))
517
        if options.stdin:
518
            w.stdin(options.stdin)
519
        colist = create_compiler_raw_option_list(options)
520
521
        if workaround:
522
            if options.compiler in ['clang-3.2']:
523
                colist.append('-ftemplate-depth=1024')
524
    #        if options.compiler in ['clang-3.4']:
525
    #            colist.append('-DIUTEST_HAS_HDR_CXXABI=0')
526
    #        if options.compiler in ['clang-3.3', 'clang-3.2', 'clang-3.1', 'clang-3.0']:
527
    #            colist.append('-Qunused-arguments')
528
    #        if options.compiler in ['clang-3.4', 'clang-3.3']:
529
    #            colist.append('-fno-exceptions')
530
    #            colist.append('-fno-rtti')
531
            pass
532
        if len(colist) > 0:
533
            co = '\n'.join(colist)
534
            co = co.replace('\\n', '\n')
535
            w.compiler_options(co)
536
        if options.runtime_option_raw:
537
            rolist = []
538
            for opt in options.runtime_option_raw:
539
                rolist.extend(opt.split())
540
            ro = '\n'.join(rolist)
541
            ro = ro.replace('\\n', '\n')
542
            w.runtime_options(ro)
543
        if options.save:
544
            w.permanent_link(options.save)
545
        for filename in impliments.keys():
546
            w.add_compiler_options(filename)
547
        if options.verbose:
548
            w.dump()
549
        w.code(code)
550
        add_files(w, impliments)
551
        add_files(w, includes)
552
553
        return run_wandbox_impl(w, options)
554
555
556
# run wandbox
557
def run_wandbox(main_filepath, code, includes, impliments, options):
558
    if options.make:
559
        return run_wandbox_make(main_filepath, code, includes, impliments, options)
560
    else:
561
        return run_wandbox_cxx(code, includes, impliments, options)
562
563
564
def wandbox_hint(r):
565
    if 'compiler_error' in r:
566
        if 'undefined reference to `main' in r['compiler_error']:
567
            print('hint:')
568
            print('  In "iutest" you can omit the definition of the main function, please define IUTEST_USE_MAIN. (--iutest-use-main or -f"-DIUTEST_USE_MAIN")')
569
        elif 'undefined reference to `init_unit_test_suite' in r['compiler_error']:
570
            print('hint:')
571
            print('  If you do not use boost test, please specify the file with the main function first.')
572
573
574
def text_transform(value):
575
    try:
576
        if isinstance(value, str):
577
            return value.decode()
578
        elif isinstance(value, unicode):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable unicode does not seem to be defined.
Loading history...
579
            return value.encode('utf_8')
580
    except:
581
        pass
582
    return value
583
584
585
# show result
586
def show_result(r, options):
587
    if 'error' in r:
588
        print(r['error'])
589
        sys.exit(1)
590
    if options.stderr:
591
        if 'compiler_output' in r:
592
            print('compiler_output:')
593
            print(text_transform(r['compiler_output']))
594
        if 'compiler_error' in r:
595
            sys.stderr.write(text_transform(r['compiler_error']))
596
        if 'program_output' in r:
597
            print('program_output:')
598
            print(text_transform(r['program_output']))
599
        if options.xml is None and options.junit is None and 'program_error' in r:
600
            sys.stderr.write(text_transform(r['program_error']))
601
    else:
602
        if 'compiler_message' in r:
603
            print('compiler_message:')
604
            print(text_transform(r['compiler_message']))
605
        if 'program_message' in r:
606
            print('program_message:')
607
            print(text_transform(r['program_message']))
608
    if 'url' in r:
609
        print('permlink: ' + r['permlink'])
610
        print('url: ' + r['url'])
611
    if 'signal' in r:
612
        print('signal: ' + r['signal'])
613
    wandbox_hint(r)
614
615
    if 'status' in r:
616
        return int(r['status'])
617
    return 1
618
619
620
# show parameter
621
def show_parameter(r):
622
    if 'compiler' in r:
623
        print('compiler:' + r['compiler'])
624
    if 'options' in r:
625
        print('options:' + r['options'])
626
    if 'compiler-option-raw' in r:
627
        print('compiler-option-raw:' + r['compiler-option-raw'])
628
    if 'runtime-option-raw' in r:
629
        print('runtime-option-raw' + r['runtime-option-raw'])
630
    if 'created-at' in r:
631
        print(r['created-at'])
632
633
634
def set_output_xml(options, t, xml):
635
    options.stderr = True
636
    if options.runtime_option_raw:
637
        options.runtime_option_raw.append('--iutest_output=' + t + ':' + xml)
638
    else:
639
        options.runtime_option_raw = ['--iutest_output=' + t + ':' + xml]
640
641
642
def run(options):
643
    main_filepath = options.code[0].strip()
644
    if not os.path.exists(main_filepath):
645
        sys.exit(1)
646
    includes = {}
647
    included_files = {}
648
    impliments = {}
649
    code = make_code(main_filepath, options.encoding, options.expand_include, includes, included_files)
650
651
    for filepath_ in options.code[1:]:
652
        filepath = filepath_.strip()
653
        impliments[os.path.basename(filepath)] = make_code(filepath, options.encoding, options.expand_include, includes, included_files)
654
655
    if options.output:
656
        f = file_open(options.output, 'w', options.encoding)
657
        f.write(code)
658
        f.close()
659
    xml = None
660
    if options.xml:
661
        xml = options.xml
662
        set_output_xml(options, 'xml', xml)
663
    if options.junit:
664
        xml = options.junit
665
        set_output_xml(options, 'junit', xml)
666
    r = run_wandbox(main_filepath, code, includes, impliments, options)
667
    b = show_result(r, options)
668
    if xml and 'program_error' in r:
669
        f = file_open(xml, 'w', options.encoding)
670
        f.write(r['program_error'])
671
        f.close()
672
    sys.exit(b)
673
674
675
# listup compiler
676
def listup_compiler(verbose):
677
    r = wandbox_get_compilerlist()
678
    for d in r:
679
        if d['language'] == 'C++':
680
            if verbose:
681
                print(d['name'] + ' (' + d['version'] + ')')
682
            else:
683
                print(d['name'])
684
685
686
# find compiler
687
def find_compiler(c):
688
    r = wandbox_get_compilerlist()
689
    for d in r:
690
        if d['language'] == 'C++' and d['name'] == c:
691
            return True
692
    return False
693
694
695
# listup options
696
def listup_options(compiler):
697
    r = wandbox_get_compilerlist()
698
    for d in r:
699
        if d['name'] == compiler:
700
            print('# ' + compiler)
701
            if 'switches' in d:
702
                switches = d['switches']
703
                for s in switches:
704
                    if 'name' in s:
705
                        if s['default']:
706
                            print(s['name'] + ' (default)')
707
                        else:
708
                            print(s['name'])
709
                    elif 'options' in s:
710
                        print(s['default'] + ' (default)')
711
                        for o in s['options']:
712
                            print('  ' + o['name'])
713
714
715
def get_options(compiler):
716
    opt = []
717
    for s in wandbox_get_compilerswitches(compiler):
718
        if 'name' in s:
719
            opt.append(s['name'])
720
        elif 'options' in s:
721
            opt.append(s['default'])
722
            for o in s['options']:
723
                opt.append(o['name'])
724
    return opt
725
726
727
# get default options
728
def get_default_options(compiler):
729
    opt = []
730
    for s in wandbox_get_compilerswitches(compiler):
731
        if 'name' in s:
732
            if s['default']:
733
                opt.append(s['name'])
734
        elif 'options' in s:
735
            opt.append(s['default'])
736
    return opt
737
738
739
# get permlink
740
def get_permlink(options):
741
    r = Wandbox.GetPermlink(options.permlink)
742
    p = r['parameter']
743
    show_parameter(p)
744
    print('result:')
745
    b = show_result(r['result'], options)
746
    if options.output:
747
        f = open(options.output, 'w')
748
        f.write(p['code'])
749
        f.close()
750
    sys.exit(b)
751
752
753
def main():
754
    options, parser = parse_command_line()
755
    if options.list_compiler:
756
        listup_compiler(options.verbose)
757
    elif options.list_options:
758
        listup_options(options.list_options)
759
    elif options.permlink:
760
        get_permlink(options)
761
    else:
762
        if options.check_config:
763
            check_config(options)
764
        elif len(options.code) == 0:
765
            parser.print_help()
766
            sys.exit(1)
767
        run(options)
768
769
770
if __name__ == '__main__':
771
    main()
772