Passed
Push — master ( c06322...aaaace )
by srz
03:25
created

wandbox_get_compilerlist()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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