aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/mesonlib.py
blob: 98c2366d6a486426356679ff729c84b366730778 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
# Copyright 2012-2015 The Meson development team

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""A library of random helper functionality."""

import functools
import sys
import stat
import time
import platform, subprocess, operator, os, shutil, re
import collections
from enum import Enum
from functools import lru_cache

from mesonbuild import mlog

have_fcntl = False
have_msvcrt = False
# {subproject: project_meson_version}
project_meson_versions = {}

try:
    import fcntl
    have_fcntl = True
except Exception:
    pass

try:
    import msvcrt
    have_msvcrt = True
except Exception:
    pass

from glob import glob

if os.path.basename(sys.executable) == 'meson.exe':
    # In Windows and using the MSI installed executable.
    python_command = [sys.executable, 'runpython']
else:
    python_command = [sys.executable]
meson_command = None

def set_meson_command(mainfile):
    global python_command
    global meson_command
    # On UNIX-like systems `meson` is a Python script
    # On Windows `meson` and `meson.exe` are wrapper exes
    if not mainfile.endswith('.py'):
        meson_command = [mainfile]
    elif os.path.isabs(mainfile) and mainfile.endswith('mesonmain.py'):
        # Can't actually run meson with an absolute path to mesonmain.py, it must be run as -m mesonbuild.mesonmain
        meson_command = python_command + ['-m', 'mesonbuild.mesonmain']
    else:
        # Either run uninstalled, or full path to meson-script.py
        meson_command = python_command + [mainfile]
    # We print this value for unit tests.
    if 'MESON_COMMAND_TESTS' in os.environ:
        mlog.log('meson_command is {!r}'.format(meson_command))

def is_ascii_string(astring):
    try:
        if isinstance(astring, str):
            astring.encode('ascii')
        if isinstance(astring, bytes):
            astring.decode('ascii')
    except UnicodeDecodeError:
        return False
    return True

def check_direntry_issues(direntry_array):
    import locale
    # Warn if the locale is not UTF-8. This can cause various unfixable issues
    # such as os.stat not being able to decode filenames with unicode in them.
    # There is no way to reset both the preferred encoding and the filesystem
    # encoding, so we can just warn about it.
    e = locale.getpreferredencoding()
    if e.upper() != 'UTF-8' and not is_windows():
        if not isinstance(direntry_array, list):
            direntry_array = [direntry_array]
        for de in direntry_array:
            if is_ascii_string(de):
                continue
            mlog.warning('''You are using {!r} which is not a Unicode-compatible '
locale but you are trying to access a file system entry called {!r} which is
not pure ASCII. This may cause problems.
'''.format(e, de), file=sys.stderr)

# Put this in objects that should not get dumped to pickle files
# by accident.
import threading
an_unpicklable_object = threading.Lock()

class MesonException(Exception):
    '''Exceptions thrown by Meson'''

    def get_msg_with_context(self):
        s = ''
        if hasattr(self, 'lineno') and hasattr(self, 'file'):
            s = get_error_location_string(self.file, self.lineno) + ' '
        s += str(self)
        return s

class EnvironmentException(MesonException):
    '''Exceptions thrown while processing and creating the build environment'''

class FileMode:
    # The first triad is for owner permissions, the second for group permissions,
    # and the third for others (everyone else).
    # For the 1st character:
    #  'r' means can read
    #  '-' means not allowed
    # For the 2nd character:
    #  'w' means can write
    #  '-' means not allowed
    # For the 3rd character:
    #  'x' means can execute
    #  's' means can execute and setuid/setgid is set (owner/group triads only)
    #  'S' means cannot execute and setuid/setgid is set (owner/group triads only)
    #  't' means can execute and sticky bit is set ("others" triads only)
    #  'T' means cannot execute and sticky bit is set ("others" triads only)
    #  '-' means none of these are allowed
    #
    # The meanings of 'rwx' perms is not obvious for directories; see:
    # https://www.hackinglinuxexposed.com/articles/20030424.html
    #
    # For information on this notation such as setuid/setgid/sticky bits, see:
    # https://en.wikipedia.org/wiki/File_system_permissions#Symbolic_notation
    symbolic_perms_regex = re.compile('[r-][w-][xsS-]' # Owner perms
                                      '[r-][w-][xsS-]' # Group perms
                                      '[r-][w-][xtT-]') # Others perms

    def __init__(self, perms=None, owner=None, group=None):
        self.perms_s = perms
        self.perms = self.perms_s_to_bits(perms)
        self.owner = owner
        self.group = group

    def __repr__(self):
        ret = '<FileMode: {!r} owner={} group={}'
        return ret.format(self.perms_s, self.owner, self.group)

    @classmethod
    def perms_s_to_bits(cls, perms_s):
        '''
        Does the opposite of stat.filemode(), converts strings of the form
        'rwxr-xr-x' to st_mode enums which can be passed to os.chmod()
        '''
        if perms_s is None:
            # No perms specified, we will not touch the permissions
            return -1
        eg = 'rwxr-xr-x'
        if not isinstance(perms_s, str):
            msg = 'Install perms must be a string. For example, {!r}'
            raise MesonException(msg.format(eg))
        if len(perms_s) != 9 or not cls.symbolic_perms_regex.match(perms_s):
            msg = 'File perms {!r} must be exactly 9 chars. For example, {!r}'
            raise MesonException(msg.format(perms_s, eg))
        perms = 0
        # Owner perms
        if perms_s[0] == 'r':
            perms |= stat.S_IRUSR
        if perms_s[1] == 'w':
            perms |= stat.S_IWUSR
        if perms_s[2] == 'x':
            perms |= stat.S_IXUSR
        elif perms_s[2] == 'S':
            perms |= stat.S_ISUID
        elif perms_s[2] == 's':
            perms |= stat.S_IXUSR
            perms |= stat.S_ISUID
        # Group perms
        if perms_s[3] == 'r':
            perms |= stat.S_IRGRP
        if perms_s[4] == 'w':
            perms |= stat.S_IWGRP
        if perms_s[5] == 'x':
            perms |= stat.S_IXGRP
        elif perms_s[5] == 'S':
            perms |= stat.S_ISGID
        elif perms_s[5] == 's':
            perms |= stat.S_IXGRP
            perms |= stat.S_ISGID
        # Others perms
        if perms_s[6] == 'r':
            perms |= stat.S_IROTH
        if perms_s[7] == 'w':
            perms |= stat.S_IWOTH
        if perms_s[8] == 'x':
            perms |= stat.S_IXOTH
        elif perms_s[8] == 'T':
            perms |= stat.S_ISVTX
        elif perms_s[8] == 't':
            perms |= stat.S_IXOTH
            perms |= stat.S_ISVTX
        return perms

class File:
    def __init__(self, is_built, subdir, fname):
        self.is_built = is_built
        self.subdir = subdir
        self.fname = fname
        assert(isinstance(self.subdir, str))
        assert(isinstance(self.fname, str))

    def __str__(self):
        return self.relative_name()

    def __repr__(self):
        ret = '<File: {0}'
        if not self.is_built:
            ret += ' (not built)'
        ret += '>'
        return ret.format(self.relative_name())

    @staticmethod
    @lru_cache(maxsize=None)
    def from_source_file(source_root, subdir, fname):
        if not os.path.isfile(os.path.join(source_root, subdir, fname)):
            raise MesonException('File %s does not exist.' % fname)
        return File(False, subdir, fname)

    @staticmethod
    def from_built_file(subdir, fname):
        return File(True, subdir, fname)

    @staticmethod
    def from_absolute_file(fname):
        return File(False, '', fname)

    @lru_cache(maxsize=None)
    def rel_to_builddir(self, build_to_src):
        if self.is_built:
            return self.relative_name()
        else:
            return os.path.join(build_to_src, self.subdir, self.fname)

    @lru_cache(maxsize=None)
    def absolute_path(self, srcdir, builddir):
        absdir = srcdir
        if self.is_built:
            absdir = builddir
        return os.path.join(absdir, self.relative_name())

    def endswith(self, ending):
        return self.fname.endswith(ending)

    def split(self, s):
        return self.fname.split(s)

    def __eq__(self, other):
        return (self.fname, self.subdir, self.is_built) == (other.fname, other.subdir, other.is_built)

    def __hash__(self):
        return hash((self.fname, self.subdir, self.is_built))

    @lru_cache(maxsize=None)
    def relative_name(self):
        return os.path.join(self.subdir, self.fname)

def get_compiler_for_source(compilers, src):
    for comp in compilers:
        if comp.can_compile(src):
            return comp
    raise MesonException('No specified compiler can handle file {!s}'.format(src))

def classify_unity_sources(compilers, sources):
    compsrclist = {}
    for src in sources:
        comp = get_compiler_for_source(compilers, src)
        if comp not in compsrclist:
            compsrclist[comp] = [src]
        else:
            compsrclist[comp].append(src)
    return compsrclist

class OrderedEnum(Enum):
    """
    An Enum which additionally offers homogeneous ordered comparison.
    """
    def __ge__(self, other):
        if self.__class__ is other.__class__:
            return self.value >= other.value
        return NotImplemented

    def __gt__(self, other):
        if self.__class__ is other.__class__:
            return self.value > other.value
        return NotImplemented

    def __le__(self, other):
        if self.__class__ is other.__class__:
            return self.value <= other.value
        return NotImplemented

    def __lt__(self, other):
        if self.__class__ is other.__class__:
            return self.value < other.value
        return NotImplemented

MachineChoice = OrderedEnum('MachineChoice', ['BUILD', 'HOST', 'TARGET'])

class PerMachine:
    def __init__(self, build, host, target):
        self.build = build
        self.host = host
        self.target = target

    def __getitem__(self, machine: MachineChoice):
        return {
            MachineChoice.BUILD:  self.build,
            MachineChoice.HOST:   self.host,
            MachineChoice.TARGET: self.target
        }[machine]

    def __setitem__(self, machine: MachineChoice, val):
        key = {
            MachineChoice.BUILD:  'build',
            MachineChoice.HOST:   'host',
            MachineChoice.TARGET: 'target'
        }[machine]
        setattr(self, key, val)

def is_osx():
    return platform.system().lower() == 'darwin'

def is_linux():
    return platform.system().lower() == 'linux'

def is_android():
    return platform.system().lower() == 'android'

def is_haiku():
    return platform.system().lower() == 'haiku'

def is_openbsd():
    return platform.system().lower() == 'openbsd'

def is_windows():
    platname = platform.system().lower()
    return platname == 'windows' or 'mingw' in platname

def is_cygwin():
    platname = platform.system().lower()
    return platname.startswith('cygwin')

def is_debianlike():
    return os.path.isfile('/etc/debian_version')

def is_dragonflybsd():
    return platform.system().lower() == 'dragonfly'

def is_freebsd():
    return platform.system().lower() == 'freebsd'

def _get_machine_is_cross(env, is_cross):
    """
    This is not morally correct, but works for now. For cross builds the build
    and host machines differ. `is_cross == true` means the host machine, while
    `is_cross == false` means the build machine. Both are used in practice,
    even though the documentation refers to the host machine implying we should
    hard-code it. For non-cross builds `is_cross == false` is passed but the
    host and build machines are identical so it doesn't matter.

    Users for `for_*` should instead specify up front which machine they want
    and query that like:

        env.machines[MachineChoice.HOST].is_haiku()

    """
    for_machine = MachineChoice.HOST if is_cross else MachineChoice.BUILD
    return env.machines[for_machine]

def for_windows(is_cross, env):
    """
    Host machine is windows?

    Deprecated: Please use `env.machines[for_machine].is_windows()`.

    Note: 'host' is the machine on which compiled binaries will run
    """
    return _get_machine_is_cross(env, is_cross).is_windows()

def for_cygwin(is_cross, env):
    """
    Host machine is cygwin?

    Deprecated: Please use `env.machines[for_machine].is_cygwin()`.

    Note: 'host' is the machine on which compiled binaries will run
    """
    return _get_machine_is_cross(env, is_cross).is_cygwin()

def for_linux(is_cross, env):
    """
    Host machine is linux?

    Deprecated: Please use `env.machines[for_machine].is_linux()`.

    Note: 'host' is the machine on which compiled binaries will run
    """
    return _get_machine_is_cross(env, is_cross).is_linux()

def for_darwin(is_cross, env):
    """
    Host machine is Darwin (iOS/OS X)?

    Deprecated: Please use `env.machines[for_machine].is_darwin()`.

    Note: 'host' is the machine on which compiled binaries will run
    """
    return _get_machine_is_cross(env, is_cross).is_darwin()

def for_android(is_cross, env):
    """
    Host machine is Android?

    Deprecated: Please use `env.machines[for_machine].is_android()`.

    Note: 'host' is the machine on which compiled binaries will run
    """
    return _get_machine_is_cross(env, is_cross).is_android()

def for_haiku(is_cross, env):
    """
    Host machine is Haiku?

    Deprecated: Please use `env.machines[for_machine].is_haiku()`.

    Note: 'host' is the machine on which compiled binaries will run
    """
    return _get_machine_is_cross(env, is_cross).is_haiku()

def for_openbsd(is_cross, env):
    """
    Host machine is OpenBSD?

    Deprecated: Please use `env.machines[for_machine].is_openbsd()`.

    Note: 'host' is the machine on which compiled binaries will run
    """
    return _get_machine_is_cross(env, is_cross).is_openbsd()

def exe_exists(arglist):
    try:
        p = subprocess.Popen(arglist, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        p.communicate()
        if p.returncode == 0:
            return True
    except FileNotFoundError:
        pass
    return False

def detect_vcs(source_dir):
    vcs_systems = [
        dict(name = 'git',        cmd = 'git', repo_dir = '.git', get_rev = 'git describe --dirty=+', rev_regex = '(.*)', dep = '.git/logs/HEAD'),
        dict(name = 'mercurial',  cmd = 'hg',  repo_dir = '.hg',  get_rev = 'hg id -i',               rev_regex = '(.*)', dep = '.hg/dirstate'),
        dict(name = 'subversion', cmd = 'svn', repo_dir = '.svn', get_rev = 'svn info',               rev_regex = 'Revision: (.*)', dep = '.svn/wc.db'),
        dict(name = 'bazaar',     cmd = 'bzr', repo_dir = '.bzr', get_rev = 'bzr revno',              rev_regex = '(.*)', dep = '.bzr'),
    ]

    segs = source_dir.replace('\\', '/').split('/')
    for i in range(len(segs), -1, -1):
        curdir = '/'.join(segs[:i])
        for vcs in vcs_systems:
            if os.path.isdir(os.path.join(curdir, vcs['repo_dir'])) and shutil.which(vcs['cmd']):
                vcs['wc_dir'] = curdir
                return vcs
    return None

# a helper class which implements the same version ordering as RPM
@functools.total_ordering
class Version:
    def __init__(self, s):
        self._s = s

        # split into numeric, alphabetic and non-alphanumeric sequences
        sequences = re.finditer(r'(\d+|[a-zA-Z]+|[^a-zA-Z\d]+)', s)
        # non-alphanumeric separators are discarded
        sequences = [m for m in sequences if not re.match(r'[^a-zA-Z\d]+', m.group(1))]
        # numeric sequences have leading zeroes discarded
        sequences = [re.sub(r'^0+(\d)', r'\1', m.group(1), 1) for m in sequences]

        self._v = sequences

    def __str__(self):
        return '%s (V=%s)' % (self._s, str(self._v))

    def __lt__(self, other):
        return self.__cmp__(other) == -1

    def __eq__(self, other):
        return self.__cmp__(other) == 0

    def __cmp__(self, other):
        def cmp(a, b):
            return (a > b) - (a < b)

        # compare each sequence in order
        for i in range(0, min(len(self._v), len(other._v))):
            # sort a non-digit sequence before a digit sequence
            if self._v[i].isdigit() != other._v[i].isdigit():
                return 1 if self._v[i].isdigit() else -1

            # compare as numbers
            if self._v[i].isdigit():
                # because leading zeros have already been removed, if one number
                # has more digits, it is greater
                c = cmp(len(self._v[i]), len(other._v[i]))
                if c != 0:
                    return c
                # fallthrough

            # compare lexicographically
            c = cmp(self._v[i], other._v[i])
            if c != 0:
                return c

        # if equal length, all components have matched, so equal
        # otherwise, the version with a suffix remaining is greater
        return cmp(len(self._v), len(other._v))

def _version_extract_cmpop(vstr2):
    if vstr2.startswith('>='):
        cmpop = operator.ge
        vstr2 = vstr2[2:]
    elif vstr2.startswith('<='):
        cmpop = operator.le
        vstr2 = vstr2[2:]
    elif vstr2.startswith('!='):
        cmpop = operator.ne
        vstr2 = vstr2[2:]
    elif vstr2.startswith('=='):
        cmpop = operator.eq
        vstr2 = vstr2[2:]
    elif vstr2.startswith('='):
        cmpop = operator.eq
        vstr2 = vstr2[1:]
    elif vstr2.startswith('>'):
        cmpop = operator.gt
        vstr2 = vstr2[1:]
    elif vstr2.startswith('<'):
        cmpop = operator.lt
        vstr2 = vstr2[1:]
    else:
        cmpop = operator.eq

    return (cmpop, vstr2)

def version_compare(vstr1, vstr2):
    (cmpop, vstr2) = _version_extract_cmpop(vstr2)
    return cmpop(Version(vstr1), Version(vstr2))

def version_compare_many(vstr1, conditions):
    if not isinstance(conditions, (list, tuple, frozenset)):
        conditions = [conditions]
    found = []
    not_found = []
    for req in conditions:
        if not version_compare(vstr1, req):
            not_found.append(req)
        else:
            found.append(req)
    return not_found == [], not_found, found

# determine if the minimum version satisfying the condition |condition| exceeds
# the minimum version for a feature |minimum|
def version_compare_condition_with_min(condition, minimum):
    if condition.startswith('>='):
        cmpop = operator.le
        condition = condition[2:]
    elif condition.startswith('<='):
        return False
    elif condition.startswith('!='):
        return False
    elif condition.startswith('=='):
        cmpop = operator.le
        condition = condition[2:]
    elif condition.startswith('='):
        cmpop = operator.le
        condition = condition[1:]
    elif condition.startswith('>'):
        cmpop = operator.lt
        condition = condition[1:]
    elif condition.startswith('<'):
        return False
    else:
        cmpop = operator.le

    # Declaring a project(meson_version: '>=0.46') and then using features in
    # 0.46.0 is valid, because (knowing the meson versioning scheme) '0.46.0' is
    # the lowest version which satisfies the constraint '>=0.46'.
    #
    # But this will fail here, because the minimum version required by the
    # version constraint ('0.46') is strictly less (in our version comparison)
    # than the minimum version needed for the feature ('0.46.0').
    #
    # Map versions in the constraint of the form '0.46' to '0.46.0', to embed
    # this knowledge of the meson versioning scheme.
    condition = condition.strip()
    if re.match('^\d+.\d+$', condition):
        condition += '.0'

    return cmpop(Version(minimum), Version(condition))

def default_libdir():
    if is_debianlike():
        try:
            pc = subprocess.Popen(['dpkg-architecture', '-qDEB_HOST_MULTIARCH'],
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.DEVNULL)
            (stdo, _) = pc.communicate()
            if pc.returncode == 0:
                archpath = stdo.decode().strip()
                return 'lib/' + archpath
        except Exception:
            pass
    if os.path.isdir('/usr/lib64') and not os.path.islink('/usr/lib64'):
        return 'lib64'
    return 'lib'

def default_libexecdir():
    # There is no way to auto-detect this, so it must be set at build time
    return 'libexec'

def default_prefix():
    return 'c:/' if is_windows() else '/usr/local'

def get_library_dirs():
    if is_windows():
        return ['C:/mingw/lib'] # Fixme
    if is_osx():
        return ['/usr/lib'] # Fix me as well.
    # The following is probably Debian/Ubuntu specific.
    # /usr/local/lib is first because it contains stuff
    # installed by the sysadmin and is probably more up-to-date
    # than /usr/lib. If you feel that this search order is
    # problematic, please raise the issue on the mailing list.
    unixdirs = ['/usr/local/lib', '/usr/lib', '/lib']
    plat = subprocess.check_output(['uname', '-m']).decode().strip()
    # This is a terrible hack. I admit it and I'm really sorry.
    # I just don't know what the correct solution is.
    if plat == 'i686':
        plat = 'i386'
    if plat.startswith('arm'):
        plat = 'arm'
    unixdirs += glob('/usr/lib/' + plat + '*')
    if os.path.exists('/usr/lib64'):
        unixdirs.append('/usr/lib64')
    unixdirs += glob('/lib/' + plat + '*')
    if os.path.exists('/lib64'):
        unixdirs.append('/lib64')
    unixdirs += glob('/lib/' + plat + '*')
    return unixdirs

def has_path_sep(name, sep='/\\'):
    'Checks if any of the specified @sep path separators are in @name'
    for each in sep:
        if each in name:
            return True
    return False

def do_replacement(regex, line, format, confdata):
    missing_variables = set()
    start_tag = '@'
    backslash_tag = '\\@'
    if format == 'cmake':
        start_tag = '${'
        backslash_tag = '\\${'

    def variable_replace(match):
        # Pairs of escape characters before '@' or '\@'
        if match.group(0).endswith('\\'):
            num_escapes = match.end(0) - match.start(0)
            return '\\' * (num_escapes // 2)
        # Single escape character and '@'
        elif match.group(0) == backslash_tag:
            return start_tag
        # Template variable to be replaced
        else:
            varname = match.group(1)
            if varname in confdata:
                (var, desc) = confdata.get(varname)
                if isinstance(var, str):
                    pass
                elif isinstance(var, int):
                    var = str(var)
                else:
                    msg = 'Tried to replace variable {!r} value with ' \
                          'something other than a string or int: {!r}'
                    raise MesonException(msg.format(varname, var))
            else:
                missing_variables.add(varname)
                var = ''
            return var
    return re.sub(regex, variable_replace, line), missing_variables

def do_mesondefine(line, confdata):
    arr = line.split()
    if len(arr) != 2:
        raise MesonException('#mesondefine does not contain exactly two tokens: %s' % line.strip())
    varname = arr[1]
    try:
        (v, desc) = confdata.get(varname)
    except KeyError:
        return '/* #undef %s */\n' % varname
    if isinstance(v, bool):
        if v:
            return '#define %s\n' % varname
        else:
            return '#undef %s\n' % varname
    elif isinstance(v, int):
        return '#define %s %d\n' % (varname, v)
    elif isinstance(v, str):
        return '#define %s %s\n' % (varname, v)
    else:
        raise MesonException('#mesondefine argument "%s" is of unknown type.' % varname)


def do_conf_file(src, dst, confdata, format, encoding='utf-8'):
    try:
        with open(src, encoding=encoding) as f:
            data = f.readlines()
    except Exception as e:
        raise MesonException('Could not read input file %s: %s' % (src, str(e)))
    # Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define
    # Also allow escaping '@' with '\@'
    if format in ['meson', 'cmake@']:
        regex = re.compile(r'(?:\\\\)+(?=\\?@)|\\@|@([-a-zA-Z0-9_]+)@')
    elif format == 'cmake':
        regex = re.compile(r'(?:\\\\)+(?=\\?\$)|\\\${|\${([-a-zA-Z0-9_]+)}')
    else:
        raise MesonException('Format "{}" not handled'.format(format))

    search_token = '#mesondefine'
    if format != 'meson':
        search_token = '#cmakedefine'

    result = []
    missing_variables = set()
    # Detect when the configuration data is empty and no tokens were found
    # during substitution so we can warn the user to use the `copy:` kwarg.
    confdata_useless = not confdata.keys()
    for line in data:
        if line.startswith(search_token):
            confdata_useless = False
            line = do_mesondefine(line, confdata)
        else:
            line, missing = do_replacement(regex, line, format, confdata)
            missing_variables.update(missing)
            if missing:
                confdata_useless = False
        result.append(line)
    dst_tmp = dst + '~'
    try:
        with open(dst_tmp, 'w', encoding=encoding) as f:
            f.writelines(result)
    except Exception as e:
        raise MesonException('Could not write output file %s: %s' % (dst, str(e)))
    shutil.copymode(src, dst_tmp)
    replace_if_different(dst, dst_tmp)
    return missing_variables, confdata_useless

CONF_C_PRELUDE = '''/*
 * Autogenerated by the Meson build system.
 * Do not edit, your changes will be lost.
 */

#pragma once

'''

CONF_NASM_PRELUDE = '''; Autogenerated by the Meson build system.
; Do not edit, your changes will be lost.

'''

def dump_conf_header(ofilename, cdata, output_format):
    if output_format == 'c':
        prelude = CONF_C_PRELUDE
        prefix = '#'
    elif output_format == 'nasm':
        prelude = CONF_NASM_PRELUDE
        prefix = '%'

    ofilename_tmp = ofilename + '~'
    with open(ofilename_tmp, 'w', encoding='utf-8') as ofile:
        ofile.write(prelude)
        for k in sorted(cdata.keys()):
            (v, desc) = cdata.get(k)
            if desc:
                if output_format == 'c':
                    ofile.write('/* %s */\n' % desc)
                elif output_format == 'nasm':
                    for line in desc.split('\n'):
                        ofile.write('; %s\n' % line)
            if isinstance(v, bool):
                if v:
                    ofile.write('%sdefine %s\n\n' % (prefix, k))
                else:
                    ofile.write('%sundef %s\n\n' % (prefix, k))
            elif isinstance(v, (int, str)):
                ofile.write('%sdefine %s %s\n\n' % (prefix, k, v))
            else:
                raise MesonException('Unknown data type in configuration file entry: ' + k)
    replace_if_different(ofilename, ofilename_tmp)

def replace_if_different(dst, dst_tmp):
    # If contents are identical, don't touch the file to prevent
    # unnecessary rebuilds.
    different = True
    try:
        with open(dst, 'rb') as f1, open(dst_tmp, 'rb') as f2:
            if f1.read() == f2.read():
                different = False
    except FileNotFoundError:
        pass
    if different:
        os.replace(dst_tmp, dst)
    else:
        os.unlink(dst_tmp)

def listify(item, flatten=True, unholder=False):
    '''
    Returns a list with all args embedded in a list if they are not a list.
    This function preserves order.
    @flatten: Convert lists of lists to a flat list
    @unholder: Replace each item with the object it holds, if required

    Note: unholding only works recursively when flattening
    '''
    if not isinstance(item, list):
        if unholder and hasattr(item, 'held_object'):
            item = item.held_object
        return [item]
    result = []
    for i in item:
        if unholder and hasattr(i, 'held_object'):
            i = i.held_object
        if flatten and isinstance(i, list):
            result += listify(i, flatten=True, unholder=unholder)
        else:
            result.append(i)
    return result


def extract_as_list(dict_object, *keys, pop=False, **kwargs):
    '''
    Extracts all values from given dict_object and listifies them.
    '''
    result = []
    fetch = dict_object.get
    if pop:
        fetch = dict_object.pop
    # If there's only one key, we don't return a list with one element
    if len(keys) == 1:
        return listify(fetch(keys[0], []), **kwargs)
    # Return a list of values corresponding to *keys
    for key in keys:
        result.append(listify(fetch(key, []), **kwargs))
    return result


def typeslistify(item, types):
    '''
    Ensure that type(@item) is one of @types or a
    list of items all of which are of type @types
    '''
    if isinstance(item, types):
        item = [item]
    if not isinstance(item, list):
        raise MesonException('Item must be a list or one of {!r}'.format(types))
    for i in item:
        if i is not None and not isinstance(i, types):
            raise MesonException('List item must be one of {!r}'.format(types))
    return item

def stringlistify(item):
    return typeslistify(item, str)

def expand_arguments(args):
    expended_args = []
    for arg in args:
        if not arg.startswith('@'):
            expended_args.append(arg)
            continue

        args_file = arg[1:]
        try:
            with open(args_file) as f:
                extended_args = f.read().split()
            expended_args += extended_args
        except Exception as e:
            print('Error expanding command line arguments, %s not found' % args_file)
            print(e)
            return None
    return expended_args

def Popen_safe(args, write=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs):
    import locale
    encoding = locale.getpreferredencoding()
    if sys.version_info < (3, 6) or not sys.stdout.encoding or encoding.upper() != 'UTF-8':
        return Popen_safe_legacy(args, write=write, stdout=stdout, stderr=stderr, **kwargs)
    p = subprocess.Popen(args, universal_newlines=True, close_fds=False,
                         stdout=stdout, stderr=stderr, **kwargs)
    o, e = p.communicate(write)
    return p, o, e

def Popen_safe_legacy(args, write=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs):
    p = subprocess.Popen(args, universal_newlines=False,
                         stdout=stdout, stderr=stderr, **kwargs)
    if write is not None:
        write = write.encode('utf-8')
    o, e = p.communicate(write)
    if o is not None:
        if sys.stdout.encoding:
            o = o.decode(encoding=sys.stdout.encoding, errors='replace').replace('\r\n', '\n')
        else:
            o = o.decode(errors='replace').replace('\r\n', '\n')
    if e is not None:
        if sys.stderr.encoding:
            e = e.decode(encoding=sys.stderr.encoding, errors='replace').replace('\r\n', '\n')
        else:
            e = e.decode(errors='replace').replace('\r\n', '\n')
    return p, o, e

def iter_regexin_iter(regexiter, initer):
    '''
    Takes each regular expression in @regexiter and tries to search for it in
    every item in @initer. If there is a match, returns that match.
    Else returns False.
    '''
    for regex in regexiter:
        for ii in initer:
            if not isinstance(ii, str):
                continue
            match = re.search(regex, ii)
            if match:
                return match.group()
    return False

def _substitute_values_check_errors(command, values):
    # Error checking
    inregex = ('@INPUT([0-9]+)?@', '@PLAINNAME@', '@BASENAME@')
    outregex = ('@OUTPUT([0-9]+)?@', '@OUTDIR@')
    if '@INPUT@' not in values:
        # Error out if any input-derived templates are present in the command
        match = iter_regexin_iter(inregex, command)
        if match:
            m = 'Command cannot have {!r}, since no input files were specified'
            raise MesonException(m.format(match))
    else:
        if len(values['@INPUT@']) > 1:
            # Error out if @PLAINNAME@ or @BASENAME@ is present in the command
            match = iter_regexin_iter(inregex[1:], command)
            if match:
                raise MesonException('Command cannot have {!r} when there is '
                                     'more than one input file'.format(match))
        # Error out if an invalid @INPUTnn@ template was specified
        for each in command:
            if not isinstance(each, str):
                continue
            match = re.search(inregex[0], each)
            if match and match.group() not in values:
                m = 'Command cannot have {!r} since there are only {!r} inputs'
                raise MesonException(m.format(match.group(), len(values['@INPUT@'])))
    if '@OUTPUT@' not in values:
        # Error out if any output-derived templates are present in the command
        match = iter_regexin_iter(outregex, command)
        if match:
            m = 'Command cannot have {!r} since there are no outputs'
            raise MesonException(m.format(match))
    else:
        # Error out if an invalid @OUTPUTnn@ template was specified
        for each in command:
            if not isinstance(each, str):
                continue
            match = re.search(outregex[0], each)
            if match and match.group() not in values:
                m = 'Command cannot have {!r} since there are only {!r} outputs'
                raise MesonException(m.format(match.group(), len(values['@OUTPUT@'])))

def substitute_values(command, values):
    '''
    Substitute the template strings in the @values dict into the list of
    strings @command and return a new list. For a full list of the templates,
    see get_filenames_templates_dict()

    If multiple inputs/outputs are given in the @values dictionary, we
    substitute @INPUT@ and @OUTPUT@ only if they are the entire string, not
    just a part of it, and in that case we substitute *all* of them.
    '''
    # Error checking
    _substitute_values_check_errors(command, values)
    # Substitution
    outcmd = []
    rx_keys = [re.escape(key) for key in values if key not in ('@INPUT@', '@OUTPUT@')]
    value_rx = re.compile('|'.join(rx_keys)) if rx_keys else None
    for vv in command:
        if not isinstance(vv, str):
            outcmd.append(vv)
        elif '@INPUT@' in vv:
            inputs = values['@INPUT@']
            if vv == '@INPUT@':
                outcmd += inputs
            elif len(inputs) == 1:
                outcmd.append(vv.replace('@INPUT@', inputs[0]))
            else:
                raise MesonException("Command has '@INPUT@' as part of a "
                                     "string and more than one input file")
        elif '@OUTPUT@' in vv:
            outputs = values['@OUTPUT@']
            if vv == '@OUTPUT@':
                outcmd += outputs
            elif len(outputs) == 1:
                outcmd.append(vv.replace('@OUTPUT@', outputs[0]))
            else:
                raise MesonException("Command has '@OUTPUT@' as part of a "
                                     "string and more than one output file")
        # Append values that are exactly a template string.
        # This is faster than a string replace.
        elif vv in values:
            outcmd.append(values[vv])
        # Substitute everything else with replacement
        elif value_rx:
            outcmd.append(value_rx.sub(lambda m: values[m.group(0)], vv))
        else:
            outcmd.append(vv)
    return outcmd

def get_filenames_templates_dict(inputs, outputs):
    '''
    Create a dictionary with template strings as keys and values as values for
    the following templates:

    @INPUT@  - the full path to one or more input files, from @inputs
    @OUTPUT@ - the full path to one or more output files, from @outputs
    @OUTDIR@ - the full path to the directory containing the output files

    If there is only one input file, the following keys are also created:

    @PLAINNAME@ - the filename of the input file
    @BASENAME@ - the filename of the input file with the extension removed

    If there is more than one input file, the following keys are also created:

    @INPUT0@, @INPUT1@, ... one for each input file

    If there is more than one output file, the following keys are also created:

    @OUTPUT0@, @OUTPUT1@, ... one for each output file
    '''
    values = {}
    # Gather values derived from the input
    if inputs:
        # We want to substitute all the inputs.
        values['@INPUT@'] = inputs
        for (ii, vv) in enumerate(inputs):
            # Write out @INPUT0@, @INPUT1@, ...
            values['@INPUT{}@'.format(ii)] = vv
        if len(inputs) == 1:
            # Just one value, substitute @PLAINNAME@ and @BASENAME@
            values['@PLAINNAME@'] = plain = os.path.basename(inputs[0])
            values['@BASENAME@'] = os.path.splitext(plain)[0]
    if outputs:
        # Gather values derived from the outputs, similar to above.
        values['@OUTPUT@'] = outputs
        for (ii, vv) in enumerate(outputs):
            values['@OUTPUT{}@'.format(ii)] = vv
        # Outdir should be the same for all outputs
        values['@OUTDIR@'] = os.path.dirname(outputs[0])
        # Many external programs fail on empty arguments.
        if values['@OUTDIR@'] == '':
            values['@OUTDIR@'] = '.'
    return values


def _make_tree_writable(topdir):
    # Ensure all files and directories under topdir are writable
    # (and readable) by owner.
    for d, _, files in os.walk(topdir):
        os.chmod(d, os.stat(d).st_mode | stat.S_IWRITE | stat.S_IREAD)
        for fname in files:
            fpath = os.path.join(d, fname)
            if os.path.isfile(fpath):
                os.chmod(fpath, os.stat(fpath).st_mode | stat.S_IWRITE | stat.S_IREAD)


def windows_proof_rmtree(f):
    # On Windows if anyone is holding a file open you can't
    # delete it. As an example an anti virus scanner might
    # be scanning files you are trying to delete. The only
    # way to fix this is to try again and again.
    delays = [0.1, 0.1, 0.2, 0.2, 0.2, 0.5, 0.5, 1, 1, 1, 1, 2]
    # Start by making the tree wriable.
    _make_tree_writable(f)
    for d in delays:
        try:
            shutil.rmtree(f)
            return
        except FileNotFoundError:
            return
        except (OSError, PermissionError):
            time.sleep(d)
    # Try one last time and throw if it fails.
    shutil.rmtree(f)


def windows_proof_rm(fpath):
    """Like windows_proof_rmtree, but for a single file."""
    if os.path.isfile(fpath):
        os.chmod(fpath, os.stat(fpath).st_mode | stat.S_IWRITE | stat.S_IREAD)
    delays = [0.1, 0.1, 0.2, 0.2, 0.2, 0.5, 0.5, 1, 1, 1, 1, 2]
    for d in delays:
        try:
            os.unlink(fpath)
            return
        except FileNotFoundError:
            return
        except (OSError, PermissionError):
            time.sleep(d)
    os.unlink(fpath)


def detect_subprojects(spdir_name, current_dir='', result=None):
    if result is None:
        result = {}
    spdir = os.path.join(current_dir, spdir_name)
    if not os.path.exists(spdir):
        return result
    for trial in glob(os.path.join(spdir, '*')):
        basename = os.path.basename(trial)
        if trial == 'packagecache':
            continue
        append_this = True
        if os.path.isdir(trial):
            detect_subprojects(spdir_name, trial, result)
        elif trial.endswith('.wrap') and os.path.isfile(trial):
            basename = os.path.splitext(basename)[0]
        else:
            append_this = False
        if append_this:
            if basename in result:
                result[basename].append(trial)
            else:
                result[basename] = [trial]
    return result

def get_error_location_string(fname, lineno):
    return '{}:{}:'.format(fname, lineno)

def substring_is_in_list(substr, strlist):
    for s in strlist:
        if substr in s:
            return True
    return False

class OrderedSet(collections.abc.MutableSet):
    """A set that preserves the order in which items are added, by first
    insertion.
    """
    def __init__(self, iterable=None):
        self.__container = collections.OrderedDict()
        if iterable:
            self.update(iterable)

    def __contains__(self, value):
        return value in self.__container

    def __iter__(self):
        return iter(self.__container.keys())

    def __len__(self):
        return len(self.__container)

    def __repr__(self):
        # Don't print 'OrderedSet("")' for an empty set.
        if self.__container:
            return 'OrderedSet("{}")'.format(
                '", "'.join(repr(e) for e in self.__container.keys()))
        return 'OrderedSet()'

    def __reversed__(self):
        return reversed(self.__container)

    def add(self, value):
        self.__container[value] = None

    def discard(self, value):
        if value in self.__container:
            del self.__container[value]

    def update(self, iterable):
        for item in iterable:
            self.__container[item] = None

    def difference(self, set_):
        return type(self)(e for e in self if e not in set_)

class BuildDirLock:

    def __init__(self, builddir):
        self.lockfilename = os.path.join(builddir, 'meson-private/meson.lock')

    def __enter__(self):
        self.lockfile = open(self.lockfilename, 'w')
        try:
            if have_fcntl:
                fcntl.flock(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
            elif have_msvcrt:
                msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_NBLCK, 1)
        except (BlockingIOError, PermissionError):
            self.lockfile.close()
            raise MesonException('Some other Meson process is already using this build directory. Exiting.')

    def __exit__(self, *args):
        if have_fcntl:
            fcntl.flock(self.lockfile, fcntl.LOCK_UN)
        elif have_msvcrt:
            msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_UNLCK, 1)
        self.lockfile.close()

def relpath(path, start):
    # On Windows a relative path can't be evaluated for paths on two different
    # drives (i.e. c:\foo and f:\bar).  The only thing left to do is to use the
    # original absolute path.
    try:
        return os.path.relpath(path, start)
    except ValueError:
        return path
lass="hl kwa">def get_depfile_suffix(self): return 'd' def get_exelist(self): return self.exelist[:] def get_linker_exelist(self): return self.exelist[:] def get_preprocess_only_args(self): return ['-E'] def get_compile_only_args(self): return ['-c'] def get_no_optimization_args(self): return ['-O0'] def get_compiler_check_args(self): ''' Get arguments useful for compiler checks such as being permissive in the code quality and not doing any optimization. ''' return self.get_no_optimization_args() def get_output_args(self, target): return ['-o', target] def get_linker_output_args(self, outputname): return ['-o', outputname] def get_coverage_args(self): return ['--coverage'] def get_coverage_link_args(self): return ['--coverage'] def get_werror_args(self): return ['-Werror'] def get_std_exe_link_args(self): return [] def get_include_args(self, path, is_system): if path == '': path = '.' if is_system: return ['-isystem', path] return ['-I' + path] def get_std_shared_lib_link_args(self): return ['-shared'] def get_library_dirs(self): stdo = Popen_safe(self.exelist + ['--print-search-dirs'])[1] for line in stdo.split('\n'): if line.startswith('libraries:'): libstr = line.split('=', 1)[1] return libstr.split(':') return [] def get_pic_args(self): return ['-fPIC'] def name_string(self): return ' '.join(self.exelist) def get_pch_use_args(self, pch_dir, header): return ['-include', os.path.split(header)[-1]] def get_pch_name(self, header_name): return os.path.split(header_name)[-1] + '.' + self.get_pch_suffix() def get_linker_search_args(self, dirname): return ['-L' + dirname] def gen_import_library_args(self, implibname): """ The name of the outputted import library This implementation is used only on Windows by compilers that use GNU ld """ return ['-Wl,--out-implib=' + implibname] def sanity_check_impl(self, work_dir, environment, sname, code): mlog.debug('Sanity testing ' + self.language + ' compiler:', ' '.join(self.exelist)) mlog.debug('Is cross compiler: %s.' % str(self.is_cross)) extra_flags = CompilerArgs(self) source_name = os.path.join(work_dir, sname) binname = sname.rsplit('.', 1)[0] if self.is_cross: binname += '_cross' if self.exe_wrapper is None: # Linking cross built apps is painful. You can't really # tell if you should use -nostdlib or not and for example # on OSX the compiler binary is the same but you need # a ton of compiler flags to differentiate between # arm and x86_64. So just compile. extra_flags += self.get_cross_extra_flags(environment, compile=True, link=False) extra_flags += self.get_compile_only_args() else: extra_flags += self.get_cross_extra_flags(environment, compile=True, link=True) # Is a valid executable output for all toolchains and platforms binname += '.exe' # Write binary check source binary_name = os.path.join(work_dir, binname) with open(source_name, 'w') as ofile: ofile.write(code) # Compile sanity check cmdlist = self.exelist + extra_flags + [source_name] + self.get_output_args(binary_name) pc, stdo, stde = Popen_safe(cmdlist, cwd=work_dir) mlog.debug('Sanity check compiler command line:', ' '.join(cmdlist)) mlog.debug('Sanity check compile stdout:') mlog.debug(stdo) mlog.debug('-----\nSanity check compile stderr:') mlog.debug(stde) mlog.debug('-----') if pc.returncode != 0: raise EnvironmentException('Compiler {0} can not compile programs.'.format(self.name_string())) # Run sanity check if self.is_cross: if self.exe_wrapper is None: # Can't check if the binaries run so we have to assume they do return cmdlist = self.exe_wrapper + [binary_name] else: cmdlist = [binary_name] mlog.debug('Running test binary command: ' + ' '.join(cmdlist)) pe = subprocess.Popen(cmdlist) pe.wait() if pe.returncode != 0: raise EnvironmentException('Executables created by {0} compiler {1} are not runnable.'.format(self.language, self.name_string())) def sanity_check(self, work_dir, environment): code = 'int main(int argc, char **argv) { int class=0; return class; }\n' return self.sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) def has_header(self, hname, prefix, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] fargs = {'prefix': prefix, 'header': hname} code = '''{prefix} #ifdef __has_include #if !__has_include(<{header}>) #error "Header '{header}' could not be found" #endif #else #include<{header}> #endif''' return self.compiles(code.format(**fargs), env, extra_args, dependencies, 'preprocess') def has_header_symbol(self, hname, symbol, prefix, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol} t = '''{prefix} #include <{header}> int main () {{ /* If it's not defined as a macro, try to use as a symbol */ #ifndef {symbol} {symbol}; #endif }}''' return self.compiles(t.format(**fargs), env, extra_args, dependencies) def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'): if extra_args is None: extra_args = [] elif isinstance(extra_args, str): extra_args = [extra_args] if dependencies is None: dependencies = [] elif not isinstance(dependencies, list): dependencies = [dependencies] # Add compile flags needed by dependencies args = CompilerArgs(self) for d in dependencies: args += d.get_compile_args() # Read c_args/cpp_args/etc from the cross-info file (if needed) args += self.get_cross_extra_flags(env, compile=True, link=False) # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env # We assume that the user has ensured these are compiler-specific args += env.coredata.external_args[self.language] args += self.get_compiler_check_args() # extra_args must override all other arguments, so we add them last args += extra_args # We only want to compile; not link with self.compile(code, args.to_native(), mode) as p: return p.returncode == 0 def _links_wrapper(self, code, env, extra_args, dependencies): "Shares common code between self.links and self.run" if extra_args is None: extra_args = [] elif isinstance(extra_args, str): extra_args = [extra_args] if dependencies is None: dependencies = [] elif not isinstance(dependencies, list): dependencies = [dependencies] # Add compile and link flags needed by dependencies args = CompilerArgs(self) for d in dependencies: args += d.get_compile_args() args += d.get_link_args() # Select a CRT if needed since we're linking args += self.get_linker_debug_crt_args() # Read c_args/c_link_args/cpp_args/cpp_link_args/etc from the # cross-info file (if needed) args += self.get_cross_extra_flags(env, compile=True, link=True) # Add LDFLAGS from the env. We assume that the user has ensured these # are compiler-specific args += env.coredata.external_link_args[self.language] # Add compiler check args such that they override args += self.get_compiler_check_args() # extra_args must override all other arguments, so we add them last args += extra_args return self.compile(code, args.to_native()) def links(self, code, env, extra_args=None, dependencies=None): with self._links_wrapper(code, env, extra_args, dependencies) as p: return p.returncode == 0 def run(self, code, env, extra_args=None, dependencies=None): if self.is_cross and self.exe_wrapper is None: raise CrossNoRunException('Can not run test applications in this cross environment.') with self._links_wrapper(code, env, extra_args, dependencies) as p: if p.returncode != 0: mlog.debug('Could not compile test file %s: %d\n' % ( p.input_name, p.returncode)) return RunResult(False) if self.is_cross: cmdlist = self.exe_wrapper + [p.output_name] else: cmdlist = p.output_name try: pe, so, se = Popen_safe(cmdlist) except Exception as e: mlog.debug('Could not run: %s (error: %s)\n' % (cmdlist, e)) return RunResult(False) mlog.debug('Program stdout:\n') mlog.debug(so) mlog.debug('Program stderr:\n') mlog.debug(se) return RunResult(True, pe.returncode, so, se) def _bisect_compiles(self, t, fargs, env, extra_args, dependencies): # FIXME: Does not actually do bisection right now for i in range(1, 1024): fargs['size'] = i if self.compiles(t.format(**fargs), env, extra_args, dependencies): if self.id == 'msvc': # MSVC refuses to construct an array of zero size, so # the test only succeeds when i is sizeof(element) + 1 return i - 1 return i raise EnvironmentException('Cross-compile check overflowed') def cross_sizeof(self, element, prefix, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] fargs = {'prefix': prefix, 'name': element} t = '''#include <stdio.h> {prefix} int main(int argc, char **argv) {{ {name} something; }}''' if not self.compiles(t.format(**fargs), env, extra_args, dependencies): return -1 t = '''#include <stdio.h> {prefix} int temparray[{size}-sizeof({name})];''' return self._bisect_compiles(t, fargs, env, extra_args, dependencies) def sizeof(self, element, prefix, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] fargs = {'prefix': prefix, 'name': element} if self.is_cross: return self.cross_sizeof(element, prefix, env, extra_args, dependencies) t = '''#include<stdio.h> {prefix} int main(int argc, char **argv) {{ printf("%ld\\n", (long)(sizeof({name}))); return 0; }};''' res = self.run(t.format(**fargs), env, extra_args, dependencies) if not res.compiled: return -1 if res.returncode != 0: raise EnvironmentException('Could not run sizeof test binary.') return int(res.stdout) def cross_alignment(self, typename, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] fargs = {'type': typename} t = '''#include <stdio.h> int main(int argc, char **argv) {{ {type} something; }}''' if not self.compiles(t.format(**fargs), env, extra_args, dependencies): return -1 t = '''#include <stddef.h> struct tmp {{ char c; {type} target; }}; int testarray[{size}-offsetof(struct tmp, target)];''' return self._bisect_compiles(t, fargs, env, extra_args, dependencies) def alignment(self, typename, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] if self.is_cross: return self.cross_alignment(typename, env, extra_args, dependencies) fargs = {'type': typename} t = '''#include <stdio.h> #include <stddef.h> struct tmp {{ char c; {type} target; }}; int main(int argc, char **argv) {{ printf("%d", (int)offsetof(struct tmp, target)); return 0; }}''' res = self.run(t.format(**fargs), env, extra_args, dependencies) if not res.compiled: raise EnvironmentException('Could not compile alignment test.') if res.returncode != 0: raise EnvironmentException('Could not run alignment test binary.') align = int(res.stdout) if align == 0: raise EnvironmentException('Could not determine alignment of %s. Sorry. You might want to file a bug.' % typename) return align @staticmethod def _no_prototype_templ(): """ Try to find the function without a prototype from a header by defining our own dummy prototype and trying to link with the C library (and whatever else the compiler links in by default). This is very similar to the check performed by Autoconf for AC_CHECK_FUNCS. """ # Define the symbol to something else since it is defined by the # includes or defines listed by the user or by the compiler. This may # include, for instance _GNU_SOURCE which must be defined before # limits.h, which includes features.h # Then, undef the symbol to get rid of it completely. head = ''' #define {func} meson_disable_define_of_{func} {prefix} #include <limits.h> #undef {func} ''' # Override any GCC internal prototype and declare our own definition for # the symbol. Use char because that's unlikely to be an actual return # value for a function which ensures that we override the definition. head += ''' #ifdef __cplusplus extern "C" #endif char {func} (); ''' # The actual function call main = ''' int main () {{ return {func} (); }}''' return head, main @staticmethod def _have_prototype_templ(): """ Returns a head-er and main() call that uses the headers listed by the user for the function prototype while checking if a function exists. """ # Add the 'prefix', aka defines, includes, etc that the user provides # This may include, for instance _GNU_SOURCE which must be defined # before limits.h, which includes features.h head = '{prefix}\n#include <limits.h>\n' # We don't know what the function takes or returns, so return it as an int. # Just taking the address or comparing it to void is not enough because # compilers are smart enough to optimize it away. The resulting binary # is not run so we don't care what the return value is. main = '''\nint main() {{ void *a = (void*) &{func}; long b = (long) a; return (int) b; }}''' return head, main def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None): """ First, this function looks for the symbol in the default libraries provided by the compiler (stdlib + a few others usually). If that fails, it checks if any of the headers specified in the prefix provide an implementation of the function, and if that fails, it checks if it's implemented as a compiler-builtin. """ if extra_args is None: extra_args = [] # Short-circuit if the check is already provided by the cross-info file varname = 'has function ' + funcname varname = varname.replace(' ', '_') if self.is_cross: val = env.cross_info.config['properties'].get(varname, None) if val is not None: if isinstance(val, bool): return val raise EnvironmentException('Cross variable {0} is not a boolean.'.format(varname)) fargs = {'prefix': prefix, 'func': funcname} # glibc defines functions that are not available on Linux as stubs that # fail with ENOSYS (such as e.g. lchmod). In this case we want to fail # instead of detecting the stub as a valid symbol. # We already included limits.h earlier to ensure that these are defined # for stub functions. stubs_fail = ''' #if defined __stub_{func} || defined __stub___{func} fail fail fail this function is not going to work #endif ''' # If we have any includes in the prefix supplied by the user, assume # that the user wants us to use the symbol prototype defined in those # includes. If not, then try to do the Autoconf-style check with # a dummy prototype definition of our own. # This is needed when the linker determines symbol availability from an # SDK based on the prototype in the header provided by the SDK. # Ignoring this prototype would result in the symbol always being # marked as available. if '#include' in prefix: head, main = self._have_prototype_templ() else: head, main = self._no_prototype_templ() templ = head + stubs_fail + main if self.links(templ.format(**fargs), env, extra_args, dependencies): return True # MSVC does not have compiler __builtin_-s. if self.get_id() == 'msvc': return False # Detect function as a built-in # # Some functions like alloca() are defined as compiler built-ins which # are inlined by the compiler and you can't take their address, so we # need to look for them differently. On nice compilers like clang, we # can just directly use the __has_builtin() macro. fargs['no_includes'] = '#include' not in prefix t = '''{prefix} int main() {{ #ifdef __has_builtin #if !__has_builtin(__builtin_{func}) #error "__builtin_{func} not found" #endif #elif ! defined({func}) /* Check for __builtin_{func} only if no includes were added to the * prefix above, which means no definition of {func} can be found. * We would always check for this, but we get false positives on * MSYS2 if we do. Their toolchain is broken, but we can at least * give them a workaround. */ #if {no_includes:d} __builtin_{func}; #else #error "No definition for __builtin_{func} found in the prefix" #endif #endif }}''' return self.links(t.format(**fargs), env, extra_args, dependencies) def has_members(self, typename, membernames, prefix, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] fargs = {'prefix': prefix, 'type': typename, 'name': 'foo'} # Create code that accesses all members members = '' for member in membernames: members += '{}.{};\n'.format(fargs['name'], member) fargs['members'] = members t = '''{prefix} void bar() {{ {type} {name}; {members} }};''' return self.compiles(t.format(**fargs), env, extra_args, dependencies) def has_type(self, typename, prefix, env, extra_args, dependencies=None): fargs = {'prefix': prefix, 'type': typename} t = '''{prefix} void bar() {{ sizeof({type}); }};''' return self.compiles(t.format(**fargs), env, extra_args, dependencies) def symbols_have_underscore_prefix(self, env): ''' Check if the compiler prefixes an underscore to global C symbols ''' symbol_name = b'meson_uscore_prefix' code = '''#ifdef __cplusplus extern "C" { #endif void ''' + symbol_name.decode() + ''' () {} #ifdef __cplusplus } #endif ''' args = self.get_cross_extra_flags(env, compile=True, link=False) args += self.get_compiler_check_args() n = 'symbols_have_underscore_prefix' with self.compile(code, args, 'compile') as p: if p.returncode != 0: m = 'BUG: Unable to compile {!r} check: {}' raise RuntimeError(m.format(n, p.stdo)) if not os.path.isfile(p.output_name): m = 'BUG: Can\'t find compiled test code for {!r} check' raise RuntimeError(m.format(n)) with open(p.output_name, 'rb') as o: for line in o: # Check if the underscore form of the symbol is somewhere # in the output file. if b'_' + symbol_name in line: return True # Else, check if the non-underscored form is present elif symbol_name in line: return False raise RuntimeError('BUG: {!r} check failed unexpectedly'.format(n)) def find_library(self, libname, env, extra_dirs): # First try if we can just add the library as -l. code = '''int main(int argc, char **argv) { return 0; } ''' if extra_dirs and isinstance(extra_dirs, str): extra_dirs = [extra_dirs] # Gcc + co seem to prefer builtin lib dirs to -L dirs. # Only try to find std libs if no extra dirs specified. if len(extra_dirs) == 0: args = ['-l' + libname] if self.links(code, env, extra_args=args): return args # Not found? Try to find the library file itself. extra_dirs += self.get_library_dirs() suffixes = ['so', 'dylib', 'lib', 'dll', 'a'] for d in extra_dirs: for suffix in suffixes: trial = os.path.join(d, 'lib' + libname + '.' + suffix) if os.path.isfile(trial): return trial trial2 = os.path.join(d, libname + '.' + suffix) if os.path.isfile(trial2): return trial2 return None def thread_flags(self): return ['-pthread'] def thread_link_flags(self): return ['-pthread'] def has_multi_arguments(self, args, env): return self.compiles('int i;\n', env, extra_args=args) class CPPCompiler(CCompiler): def __init__(self, exelist, version, is_cross, exe_wrap): # If a child ObjCPP class has already set it, don't set it ourselves if not hasattr(self, 'language'): self.language = 'cpp' CCompiler.__init__(self, exelist, version, is_cross, exe_wrap) def get_no_stdinc_args(self): return ['-nostdinc++'] def sanity_check(self, work_dir, environment): code = 'class breakCCompiler;int main(int argc, char **argv) { return 0; }\n' return self.sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code) def get_compiler_check_args(self): # -fpermissive allows non-conforming code to compile which is necessary # for many C++ checks. Particularly, the has_header_symbol check is # too strict without this and always fails. return super().get_compiler_check_args() + ['-fpermissive'] def has_header_symbol(self, hname, symbol, prefix, env, extra_args=None, dependencies=None): # Check if it's a C-like symbol if super().has_header_symbol(hname, symbol, prefix, env, extra_args, dependencies): return True # Check if it's a class or a template if extra_args is None: extra_args = [] fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol} t = '''{prefix} #include <{header}> using {symbol}; int main () {{ return 0; }}''' return self.compiles(t.format(**fargs), env, extra_args, dependencies) class ObjCCompiler(CCompiler): def __init__(self, exelist, version, is_cross, exe_wrap): self.language = 'objc' CCompiler.__init__(self, exelist, version, is_cross, exe_wrap) def sanity_check(self, work_dir, environment): # TODO try to use sanity_check_impl instead of duplicated code source_name = os.path.join(work_dir, 'sanitycheckobjc.m') binary_name = os.path.join(work_dir, 'sanitycheckobjc') extra_flags = self.get_cross_extra_flags(environment, compile=True, link=False) if self.is_cross: extra_flags += self.get_compile_only_args() with open(source_name, 'w') as ofile: ofile.write('#import<stdio.h>\n' 'int main(int argc, char **argv) { return 0; }\n') pc = subprocess.Popen(self.exelist + extra_flags + [source_name, '-o', binary_name]) pc.wait() if pc.returncode != 0: raise EnvironmentException('ObjC compiler %s can not compile programs.' % self.name_string()) if self.is_cross: # Can't check if the binaries run so we have to assume they do return pe = subprocess.Popen(binary_name) pe.wait() if pe.returncode != 0: raise EnvironmentException('Executables created by ObjC compiler %s are not runnable.' % self.name_string()) class ObjCPPCompiler(CPPCompiler): def __init__(self, exelist, version, is_cross, exe_wrap): self.language = 'objcpp' CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap) def sanity_check(self, work_dir, environment): # TODO try to use sanity_check_impl instead of duplicated code source_name = os.path.join(work_dir, 'sanitycheckobjcpp.mm') binary_name = os.path.join(work_dir, 'sanitycheckobjcpp') extra_flags = self.get_cross_extra_flags(environment, compile=True, link=False) if self.is_cross: extra_flags += self.get_compile_only_args() with open(source_name, 'w') as ofile: ofile.write('#import<stdio.h>\n' 'class MyClass;' 'int main(int argc, char **argv) { return 0; }\n') pc = subprocess.Popen(self.exelist + extra_flags + [source_name, '-o', binary_name]) pc.wait() if pc.returncode != 0: raise EnvironmentException('ObjC++ compiler %s can not compile programs.' % self.name_string()) if self.is_cross: # Can't check if the binaries run so we have to assume they do return pe = subprocess.Popen(binary_name) pe.wait() if pe.returncode != 0: raise EnvironmentException('Executables created by ObjC++ compiler %s are not runnable.' % self.name_string()) class MonoCompiler(Compiler): def __init__(self, exelist, version): self.language = 'cs' super().__init__(exelist, version) self.id = 'mono' self.monorunner = 'mono' def get_output_args(self, fname): return ['-out:' + fname] def get_link_args(self, fname): return ['-r:' + fname] def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): return [] def get_werror_args(self): return ['-warnaserror'] def split_shlib_to_parts(self, fname): return None, fname def build_rpath_args(self, build_dir, rpath_paths, install_rpath): return [] def get_dependency_gen_args(self, outtarget, outfile): return [] def get_linker_exelist(self): return self.exelist[:] def get_compile_only_args(self): return [] def get_linker_output_args(self, outputname): return [] def get_coverage_args(self): return [] def get_coverage_link_args(self): return [] def get_std_exe_link_args(self): return [] def get_include_args(self, path): return [] def get_pic_args(self): return [] def name_string(self): return ' '.join(self.exelist) def get_pch_use_args(self, pch_dir, header): return [] def get_pch_name(self, header_name): return '' def sanity_check(self, work_dir, environment): src = 'sanity.cs' obj = 'sanity.exe' source_name = os.path.join(work_dir, src) with open(source_name, 'w') as ofile: ofile.write('''public class Sanity { static public void Main () { } } ''') pc = subprocess.Popen(self.exelist + [src], cwd=work_dir) pc.wait() if pc.returncode != 0: raise EnvironmentException('Mono compiler %s can not compile programs.' % self.name_string()) cmdlist = [self.monorunner, obj] pe = subprocess.Popen(cmdlist, cwd=work_dir) pe.wait() if pe.returncode != 0: raise EnvironmentException('Executables created by Mono compiler %s are not runnable.' % self.name_string()) def needs_static_linker(self): return False def get_buildtype_args(self, buildtype): return mono_buildtype_args[buildtype] class JavaCompiler(Compiler): def __init__(self, exelist, version): self.language = 'java' super().__init__(exelist, version) self.id = 'unknown' self.javarunner = 'java' def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): return [] def get_werror_args(self): return ['-Werror'] def split_shlib_to_parts(self, fname): return None, fname def build_rpath_args(self, build_dir, rpath_paths, install_rpath): return [] def get_dependency_gen_args(self, outtarget, outfile): return [] def get_linker_exelist(self): return self.exelist[:] def get_compile_only_args(self): return [] def get_output_args(self, subdir): if subdir == '': subdir = './' return ['-d', subdir, '-s', subdir] def get_linker_output_args(self, outputname): return [] def get_coverage_args(self): return [] def get_coverage_link_args(self): return [] def get_std_exe_link_args(self): return [] def get_include_args(self, path): return [] def get_pic_args(self): return [] def name_string(self): return ' '.join(self.exelist) def get_pch_use_args(self, pch_dir, header): return [] def get_pch_name(self, header_name): return '' def get_buildtype_args(self, buildtype): return java_buildtype_args[buildtype] def sanity_check(self, work_dir, environment): src = 'SanityCheck.java' obj = 'SanityCheck' source_name = os.path.join(work_dir, src) with open(source_name, 'w') as ofile: ofile.write('''class SanityCheck { public static void main(String[] args) { int i; } } ''') pc = subprocess.Popen(self.exelist + [src], cwd=work_dir) pc.wait() if pc.returncode != 0: raise EnvironmentException('Java compiler %s can not compile programs.' % self.name_string()) runner = shutil.which(self.javarunner) if runner: cmdlist = [runner, obj] pe = subprocess.Popen(cmdlist, cwd=work_dir) pe.wait() if pe.returncode != 0: raise EnvironmentException('Executables created by Java compiler %s are not runnable.' % self.name_string()) else: m = "Java Virtual Machine wasn't found, but it's needed by Meson. " \ "Please install a JRE.\nIf you have specific needs where this " \ "requirement doesn't make sense, please open a bug at " \ "https://github.com/mesonbuild/meson/issues/new and tell us " \ "all about it." raise EnvironmentException(m) def needs_static_linker(self): return False class ValaCompiler(Compiler): def __init__(self, exelist, version): self.language = 'vala' super().__init__(exelist, version) self.version = version self.id = 'valac' self.is_cross = False def name_string(self): return ' '.join(self.exelist) def needs_static_linker(self): return False # Because compiles into C. def get_output_args(self, target): return ['-o', target] def get_compile_only_args(self): return ['-C'] def get_werror_args(self): return ['--fatal-warnings'] def sanity_check(self, work_dir, environment): code = 'class MesonSanityCheck : Object { }' args = self.get_cross_extra_flags(environment, compile=True, link=False) with self.compile(code, args, 'compile') as p: if p.returncode != 0: msg = 'Vala compiler {!r} can not compile programs' \ ''.format(self.name_string()) raise EnvironmentException(msg) def get_buildtype_args(self, buildtype): if buildtype == 'debug' or buildtype == 'debugoptimized' or buildtype == 'minsize': return ['--debug'] return [] def find_library(self, libname, env, extra_dirs): if extra_dirs and isinstance(extra_dirs, str): extra_dirs = [extra_dirs] # Valac always looks in the default vapi dir, so only search there if # no extra dirs are specified. if len(extra_dirs) == 0: code = 'class MesonFindLibrary : Object { }' vapi_args = ['--pkg', libname] args = self.get_cross_extra_flags(env, compile=True, link=False) args += vapi_args with self.compile(code, args, 'compile') as p: if p.returncode == 0: return vapi_args # Not found? Try to find the vapi file itself. for d in extra_dirs: vapi = os.path.join(d, libname + '.vapi') if os.path.isfile(vapi): return vapi mlog.debug('Searched {!r} and {!r} wasn\'t found'.format(extra_dirs, libname)) return None class RustCompiler(Compiler): def __init__(self, exelist, version): self.language = 'rust' super().__init__(exelist, version) self.id = 'rustc' def needs_static_linker(self): return False def name_string(self): return ' '.join(self.exelist) def sanity_check(self, work_dir, environment): source_name = os.path.join(work_dir, 'sanity.rs') output_name = os.path.join(work_dir, 'rusttest') with open(source_name, 'w') as ofile: ofile.write('''fn main() { } ''') pc = subprocess.Popen(self.exelist + ['-o', output_name, source_name], cwd=work_dir) pc.wait() if pc.returncode != 0: raise EnvironmentException('Rust compiler %s can not compile programs.' % self.name_string()) if subprocess.call(output_name) != 0: raise EnvironmentException('Executables created by Rust compiler %s are not runnable.' % self.name_string()) def get_dependency_gen_args(self, outfile): return ['--dep-info', outfile] def get_buildtype_args(self, buildtype): return rust_buildtype_args[buildtype] class SwiftCompiler(Compiler): def __init__(self, exelist, version): self.language = 'swift' super().__init__(exelist, version) self.version = version self.id = 'llvm' self.is_cross = False def get_linker_exelist(self): return self.exelist[:] def name_string(self): return ' '.join(self.exelist) def needs_static_linker(self): return True def get_werror_args(self): return ['--fatal-warnings'] def get_dependency_gen_args(self, outtarget, outfile): return ['-emit-dependencies'] def depfile_for_object(self, objfile): return os.path.splitext(objfile)[0] + '.' + self.get_depfile_suffix() def get_depfile_suffix(self): return 'd' def get_output_args(self, target): return ['-o', target] def get_linker_output_args(self, target): return ['-o', target] def get_header_import_args(self, headername): return ['-import-objc-header', headername] def get_warn_args(self, level): return [] def get_buildtype_args(self, buildtype): return swift_buildtype_args[buildtype] def get_buildtype_linker_args(self, buildtype): return [] def get_std_exe_link_args(self): return ['-emit-executable'] def get_module_args(self, modname): return ['-module-name', modname] def get_mod_gen_args(self): return ['-emit-module'] def build_rpath_args(self, *args): return [] # FIXME def get_include_args(self, dirname): return ['-I' + dirname] def get_compile_only_args(self): return ['-c'] def sanity_check(self, work_dir, environment): src = 'swifttest.swift' source_name = os.path.join(work_dir, src) output_name = os.path.join(work_dir, 'swifttest') with open(source_name, 'w') as ofile: ofile.write('''1 + 2 ''') extra_flags = self.get_cross_extra_flags(environment, compile=True, link=True) pc = subprocess.Popen(self.exelist + extra_flags + ['-emit-executable', '-o', output_name, src], cwd=work_dir) pc.wait() if pc.returncode != 0: raise EnvironmentException('Swift compiler %s can not compile programs.' % self.name_string()) if subprocess.call(output_name) != 0: raise EnvironmentException('Executables created by Swift compiler %s are not runnable.' % self.name_string()) class DCompiler(Compiler): def __init__(self, exelist, version, is_cross): self.language = 'd' super().__init__(exelist, version) self.id = 'unknown' self.is_cross = is_cross def sanity_check(self, work_dir, environment): source_name = os.path.join(work_dir, 'sanity.d') output_name = os.path.join(work_dir, 'dtest') with open(source_name, 'w') as ofile: ofile.write('''void main() { } ''') pc = subprocess.Popen(self.exelist + self.get_output_args(output_name) + [source_name], cwd=work_dir) pc.wait() if pc.returncode != 0: raise EnvironmentException('D compiler %s can not compile programs.' % self.name_string()) if subprocess.call(output_name) != 0: raise EnvironmentException('Executables created by D compiler %s are not runnable.' % self.name_string()) def needs_static_linker(self): return True def name_string(self): return ' '.join(self.exelist) def get_linker_exelist(self): return self.exelist[:] def get_preprocess_only_args(self): return ['-E'] def get_compile_only_args(self): return ['-c'] def depfile_for_object(self, objfile): return objfile + '.' + self.get_depfile_suffix() def get_depfile_suffix(self): return 'dep' def get_pic_args(self): return ['-fPIC'] def get_std_shared_lib_link_args(self): return ['-shared'] def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): # FIXME: Make this work for Windows, MacOS and cross-compiling return get_gcc_soname_args(GCC_STANDARD, prefix, shlib_name, suffix, path, soversion, is_shared_module) def get_unittest_args(self): return ['-unittest'] def get_buildtype_linker_args(self, buildtype): return [] def get_std_exe_link_args(self): return [] def build_rpath_args(self, build_dir, rpath_paths, install_rpath): # This method is to be used by LDC and DMD. # GDC can deal with the verbatim flags. if len(rpath_paths) == 0 and len(install_rpath) == 0: return [] paths = ':'.join([os.path.join(build_dir, p) for p in rpath_paths]) if len(paths) < len(install_rpath): padding = 'X' * (len(install_rpath) - len(paths)) if len(paths) == 0: paths = padding else: paths = paths + ':' + padding return ['-L-rpath={}'.format(paths)] @classmethod def translate_args_to_nongnu(cls, args): dcargs = [] # Translate common arguments to flags the LDC/DMD compilers # can understand. # The flags might have been added by pkg-config files, # and are therefore out of the user's control. for arg in args: if arg == '-pthread': continue if arg.startswith('-Wl,'): linkargs = arg[arg.index(',') + 1:].split(',') for la in linkargs: dcargs.append('-L' + la.strip()) continue elif arg.startswith('-l'): # translate library link flag dcargs.append('-L' + arg) continue dcargs.append(arg) return dcargs class GnuDCompiler(DCompiler): def __init__(self, exelist, version, is_cross): DCompiler.__init__(self, exelist, version, is_cross) self.id = 'gcc' default_warn_args = ['-Wall', '-Wdeprecated'] self.warn_args = {'1': default_warn_args, '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} self.base_options = ['b_colorout', 'b_sanitize', 'b_staticpic'] def get_colorout_args(self, colortype): if mesonlib.version_compare(self.version, '>=4.9.0'): return gnu_color_args[colortype][:] return [] def get_dependency_gen_args(self, outtarget, outfile): return ['-fmake-deps=' + outfile] def get_output_args(self, target): return ['-o', target] def get_linker_output_args(self, target): return ['-o', target] def get_include_args(self, path, is_system): return ['-I' + path] def get_warn_args(self, level): return self.warn_args[level] def get_werror_args(self): return ['-Werror'] def get_linker_search_args(self, dirname): return ['-L' + dirname] def get_buildtype_args(self, buildtype): return d_gdc_buildtype_args[buildtype] def build_rpath_args(self, build_dir, rpath_paths, install_rpath): return build_unix_rpath_args(build_dir, rpath_paths, install_rpath) def get_unittest_args(self): return ['-funittest'] class LLVMDCompiler(DCompiler): def __init__(self, exelist, version, is_cross): DCompiler.__init__(self, exelist, version, is_cross) self.id = 'llvm' self.base_options = ['b_coverage', 'b_colorout'] def get_colorout_args(self, colortype): if colortype == 'always': return ['-enable-color'] return [] def get_dependency_gen_args(self, outtarget, outfile): # LDC using the -deps flag returns a non-Makefile dependency-info file, which # the backends can not use. So we disable this feature for now. return [] def get_output_args(self, target): return ['-of', target] def get_linker_output_args(self, target): return ['-of', target] def get_include_args(self, path, is_system): return ['-I' + path] def get_warn_args(self, level): if level == '2' or level == '3': return ['-wi', '-dw'] else: return ['-wi'] def get_werror_args(self): return ['-w'] def get_coverage_args(self): return ['-cov'] def get_buildtype_args(self, buildtype): return d_ldc_buildtype_args[buildtype] def get_pic_args(self): return ['-relocation-model=pic'] def get_linker_search_args(self, dirname): # -L is recognized as "add this to the search path" by the linker, # while the compiler recognizes it as "pass to linker". So, the first # -L is for the compiler, telling it to pass the second -L to the linker. return ['-L-L' + dirname] @classmethod def unix_args_to_native(cls, args): return cls.translate_args_to_nongnu(args) class DmdDCompiler(DCompiler): def __init__(self, exelist, version, is_cross): DCompiler.__init__(self, exelist, version, is_cross) self.id = 'dmd' self.base_options = ['b_coverage', 'b_colorout'] def get_colorout_args(self, colortype): if colortype == 'always': return ['-color=on'] return [] def get_dependency_gen_args(self, outtarget, outfile): # LDC using the -deps flag returns a non-Makefile dependency-info file, which # the backends can not use. So we disable this feature for now. return [] def get_output_args(self, target): return ['-of' + target] def get_werror_args(self): return ['-w'] def get_linker_output_args(self, target): return ['-of' + target] def get_include_args(self, path, is_system): return ['-I' + path] def get_warn_args(self, level): return ['-wi'] def get_coverage_args(self): return ['-cov'] def get_linker_search_args(self, dirname): # -L is recognized as "add this to the search path" by the linker, # while the compiler recognizes it as "pass to linker". So, the first # -L is for the compiler, telling it to pass the second -L to the linker. return ['-L-L' + dirname] def get_buildtype_args(self, buildtype): return d_dmd_buildtype_args[buildtype] def get_std_shared_lib_link_args(self): return ['-shared', '-defaultlib=libphobos2.so'] @classmethod def unix_args_to_native(cls, args): return cls.translate_args_to_nongnu(args) class VisualStudioCCompiler(CCompiler): std_warn_args = ['/W3'] std_opt_args = ['/O2'] def __init__(self, exelist, version, is_cross, exe_wrap): CCompiler.__init__(self, exelist, version, is_cross, exe_wrap) self.id = 'msvc' # /showIncludes is needed for build dependency tracking in Ninja # See: https://ninja-build.org/manual.html#_deps self.always_args = ['/nologo', '/showIncludes'] self.warn_args = {'1': ['/W2'], '2': ['/W3'], '3': ['/W4']} self.base_options = ['b_pch'] # FIXME add lto, pgo and the like def get_always_args(self): return self.always_args def get_linker_debug_crt_args(self): """ Arguments needed to select a debug crt for the linker Sometimes we need to manually select the CRT (C runtime) to use with MSVC. One example is when trying to link with static libraries since MSVC won't auto-select a CRT for us in that case and will error out asking us to select one. """ return ['/MDd'] def get_buildtype_args(self, buildtype): return msvc_buildtype_args[buildtype] def get_buildtype_linker_args(self, buildtype): return msvc_buildtype_linker_args[buildtype] def get_pch_suffix(self): return 'pch' def get_pch_name(self, header): chopped = os.path.split(header)[-1].split('.')[:-1] chopped.append(self.get_pch_suffix()) pchname = '.'.join(chopped) return pchname def get_pch_use_args(self, pch_dir, header): base = os.path.split(header)[-1] pchname = self.get_pch_name(header) return ['/FI' + base, '/Yu' + base, '/Fp' + os.path.join(pch_dir, pchname)] def get_preprocess_only_args(self): return ['/E'] def get_compile_only_args(self): return ['/c'] def get_no_optimization_args(self): return ['/Od'] def get_output_args(self, target): if target.endswith('.exe'): return ['/Fe' + target] return ['/Fo' + target] def get_dependency_gen_args(self, outtarget, outfile): return [] def get_linker_exelist(self): return ['link'] # FIXME, should have same path as compiler. def get_linker_always_args(self): return ['/nologo'] def get_linker_output_args(self, outputname): return ['/OUT:' + outputname] def get_linker_search_args(self, dirname): return ['/LIBPATH:' + dirname] def get_pic_args(self): return [] # PIC is handled by the loader on Windows def get_std_shared_lib_link_args(self): return ['/DLL'] def gen_vs_module_defs_args(self, defsfile): if not isinstance(defsfile, str): raise RuntimeError('Module definitions file should be str') # With MSVC, DLLs only export symbols that are explicitly exported, # so if a module defs file is specified, we use that to export symbols return ['/DEF:' + defsfile] def gen_pch_args(self, header, source, pchname): objname = os.path.splitext(pchname)[0] + '.obj' return objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname] def gen_import_library_args(self, implibname): "The name of the outputted import library" return ['/IMPLIB:' + implibname] def build_rpath_args(self, build_dir, rpath_paths, install_rpath): return [] # FIXME, no idea what these should be. def thread_flags(self): return [] def thread_link_flags(self): return [] def get_options(self): return {'c_winlibs': coredata.UserStringArrayOption('c_winlibs', 'Windows libs to link against.', msvc_winlibs) } def get_option_link_args(self, options): return options['c_winlibs'].value[:] @classmethod def unix_args_to_native(cls, args): result = [] for i in args: # -mms-bitfields is specific to MinGW-GCC # -pthread is only valid for GCC if i in ('-mms-bitfields', '-pthread'): continue if i.startswith('-L'): i = '/LIBPATH:' + i[2:] # Translate GNU-style -lfoo library name to the import library elif i.startswith('-l'): name = i[2:] if name in ('m', 'c', 'pthread'): # With MSVC, these are provided by the C runtime which is # linked in by default continue else: i = name + '.lib' # -pthread in link flags is only used on Linux elif i == '-pthread': continue result.append(i) return result def get_werror_args(self): return ['/WX'] def get_include_args(self, path, is_system): if path == '': path = '.' # msvc does not have a concept of system header dirs. return ['-I' + path] # Visual Studio is special. It ignores some arguments it does not # understand and you can't tell it to error out on those. # http://stackoverflow.com/questions/15259720/how-can-i-make-the-microsoft-c-compiler-treat-unknown-flags-as-errors-rather-t def has_multi_arguments(self, args, env): warning_text = '9002' code = 'int i;\n' (fd, srcname) = tempfile.mkstemp(suffix='.' + self.default_suffix) os.close(fd) with open(srcname, 'w') as ofile: ofile.write(code) # Read c_args/cpp_args/etc from the cross-info file (if needed) extra_args = self.get_cross_extra_flags(env, compile=True, link=False) extra_args += self.get_compile_only_args() commands = self.exelist + args + extra_args + [srcname] mlog.debug('Running VS compile:') mlog.debug('Command line: ', ' '.join(commands)) mlog.debug('Code:\n', code) p, stdo, stde = Popen_safe(commands, cwd=os.path.split(srcname)[0]) if p.returncode != 0: return False return not(warning_text in stde or warning_text in stdo) def get_compile_debugfile_args(self, rel_obj, pch=False): pdbarr = rel_obj.split('.')[:-1] pdbarr += ['pdb'] args = ['/Fd' + '.'.join(pdbarr)] # When generating a PDB file with PCH, all compile commands write # to the same PDB file. Hence, we need to serialize the PDB # writes using /FS since we do parallel builds. This slows down the # build obviously, which is why we only do this when PCH is on. # This was added in Visual Studio 2013 (MSVC 18.0). Before that it was # always on: https://msdn.microsoft.com/en-us/library/dn502518.aspx if pch and mesonlib.version_compare(self.version, '>=18.0'): args = ['/FS'] + args return args def get_link_debugfile_args(self, targetfile): pdbarr = targetfile.split('.')[:-1] pdbarr += ['pdb'] return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)] class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): def __init__(self, exelist, version, is_cross, exe_wrap): self.language = 'cpp' CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap) VisualStudioCCompiler.__init__(self, exelist, version, is_cross, exe_wrap) self.base_options = ['b_pch'] # FIXME add lto, pgo and the like def get_options(self): return {'cpp_eh': coredata.UserComboOption('cpp_eh', 'C++ exception handling type.', ['none', 'a', 's', 'sc'], 'sc'), 'cpp_winlibs': coredata.UserStringArrayOption('cpp_winlibs', 'Windows libs to link against.', msvc_winlibs) } def get_option_compile_args(self, options): args = [] std = options['cpp_eh'] if std.value != 'none': args.append('/EH' + std.value) return args def get_option_link_args(self, options): return options['cpp_winlibs'].value[:] def get_compiler_check_args(self): # Visual Studio C++ compiler doesn't support -fpermissive, # so just use the plain C args. return super(VisualStudioCCompiler, self).get_compiler_check_args() GCC_STANDARD = 0 GCC_OSX = 1 GCC_MINGW = 2 CLANG_STANDARD = 0 CLANG_OSX = 1 CLANG_WIN = 2 # Possibly clang-cl? ICC_STANDARD = 0 ICC_OSX = 1 ICC_WIN = 2 def get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module): if soversion is None: sostr = '' else: sostr = '.' + soversion if gcc_type == GCC_STANDARD or gcc_type == GCC_MINGW: # Might not be correct for mingw but seems to work. return ['-Wl,-soname,%s%s.%s%s' % (prefix, shlib_name, suffix, sostr)] elif gcc_type == GCC_OSX: if is_shared_module: return [] return ['-install_name', os.path.join(path, 'lib' + shlib_name + '.dylib')] else: raise RuntimeError('Not implemented yet.') class GnuCompiler: # Functionality that is common to all GNU family compilers. def __init__(self, gcc_type, defines): self.id = 'gcc' self.gcc_type = gcc_type self.defines = defines or {} self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage', 'b_colorout', 'b_ndebug', 'b_staticpic'] if self.gcc_type != GCC_OSX: self.base_options.append('b_lundef') self.base_options.append('b_asneeded') # All GCC backends can do assembly self.can_compile_suffixes.add('s') def get_colorout_args(self, colortype): if mesonlib.version_compare(self.version, '>=4.9.0'): return gnu_color_args[colortype][:] return [] def get_warn_args(self, level): args = super().get_warn_args(level) if mesonlib.version_compare(self.version, '<4.8.0') and '-Wpedantic' in args: # -Wpedantic was added in 4.8.0 # https://gcc.gnu.org/gcc-4.8/changes.html args[args.index('-Wpedantic')] = '-pedantic' return args def has_define(self, define): return define in self.defines def get_define(self, define): if define in self.defines: return self.defines[define] def get_pic_args(self): if self.gcc_type in (GCC_MINGW, GCC_OSX): return [] # On Window and OS X, pic is always on. return ['-fPIC'] def get_buildtype_args(self, buildtype): return gnulike_buildtype_args[buildtype] def get_buildtype_linker_args(self, buildtype): if self.gcc_type == GCC_OSX: return apple_buildtype_linker_args[buildtype] return gnulike_buildtype_linker_args[buildtype] def get_always_args(self): return ['-pipe'] def get_pch_suffix(self): return 'gch' def split_shlib_to_parts(self, fname): return os.path.split(fname)[0], fname def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): return get_gcc_soname_args(self.gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) def get_std_shared_lib_link_args(self): if self.gcc_type == GCC_OSX: return ['-bundle'] return ['-shared'] class GnuCCompiler(GnuCompiler, CCompiler): def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None): CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) GnuCompiler.__init__(self, gcc_type, defines) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'1': default_warn_args, '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} def get_options(self): opts = {'c_std': coredata.UserComboOption('c_std', 'C language standard to use', ['none', 'c89', 'c99', 'c11', 'gnu89', 'gnu99', 'gnu11'], 'none')} if self.gcc_type == GCC_MINGW: opts.update({ 'c_winlibs': coredata.UserStringArrayOption('c_winlibs', 'Standard Win libraries to link against', gnu_winlibs), }) return opts def get_option_compile_args(self, options): args = [] std = options['c_std'] if std.value != 'none': args.append('-std=' + std.value) return args def get_option_link_args(self, options): if self.gcc_type == GCC_MINGW: return options['c_winlibs'].value[:] return [] def get_std_shared_lib_link_args(self): return ['-shared'] class GnuCPPCompiler(GnuCompiler, CPPCompiler): def __init__(self, exelist, version, gcc_type, is_cross, exe_wrap, defines): CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap) GnuCompiler.__init__(self, gcc_type, defines) default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor'] self.warn_args = {'1': default_warn_args, '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} def get_options(self): opts = {'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use', ['none', 'c++03', 'c++11', 'c++14', 'c++1z', 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++1z'], 'none'), 'cpp_debugstl': coredata.UserBooleanOption('cpp_debugstl', 'STL debug mode', False)} if self.gcc_type == GCC_MINGW: opts.update({ 'cpp_winlibs': coredata.UserStringArrayOption('cpp_winlibs', 'Standard Win libraries to link against', gnu_winlibs), }) return opts def get_option_compile_args(self, options): args = [] std = options['cpp_std'] if std.value != 'none': args.append('-std=' + std.value) if options['cpp_debugstl'].value: args.append('-D_GLIBCXX_DEBUG=1') return args def get_option_link_args(self, options): if self.gcc_type == GCC_MINGW: return options['cpp_winlibs'].value[:] return [] class GnuObjCCompiler(GnuCompiler, ObjCCompiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None, defines=None): ObjCCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) # Not really correct, but GNU objc is only used on non-OSX non-win. File a bug # if this breaks your use case. GnuCompiler.__init__(self, GCC_STANDARD, defines) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'1': default_warn_args, '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} class GnuObjCPPCompiler(GnuCompiler, ObjCPPCompiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None, defines=None): ObjCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) # Not really correct, but GNU objc is only used on non-OSX non-win. File a bug # if this breaks your use case. GnuCompiler.__init__(self, GCC_STANDARD, defines) default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor'] self.warn_args = {'1': default_warn_args, '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} class ClangCompiler: def __init__(self, clang_type): self.id = 'clang' self.clang_type = clang_type self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage', 'b_ndebug', 'b_staticpic'] if self.clang_type != CLANG_OSX: self.base_options.append('b_lundef') self.base_options.append('b_asneeded') # All Clang backends can do assembly and LLVM IR self.can_compile_suffixes.update(['ll', 's']) def get_pic_args(self): if self.clang_type in (CLANG_WIN, CLANG_OSX): return [] # On Window and OS X, pic is always on. return ['-fPIC'] def get_buildtype_args(self, buildtype): return gnulike_buildtype_args[buildtype] def get_buildtype_linker_args(self, buildtype): if self.clang_type == CLANG_OSX: return apple_buildtype_linker_args[buildtype] return gnulike_buildtype_linker_args[buildtype] def get_pch_suffix(self): return 'pch' def get_pch_use_args(self, pch_dir, header): # Workaround for Clang bug http://llvm.org/bugs/show_bug.cgi?id=15136 # This flag is internal to Clang (or at least not documented on the man page) # so it might change semantics at any time. return ['-include-pch', os.path.join(pch_dir, self.get_pch_name(header))] def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): if self.clang_type == CLANG_STANDARD: gcc_type = GCC_STANDARD elif self.clang_type == CLANG_OSX: gcc_type = GCC_OSX elif self.clang_type == CLANG_WIN: gcc_type = GCC_MINGW else: raise MesonException('Unreachable code when converting clang type to gcc type.') return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) def has_multi_arguments(self, args, env): return super().has_multi_arguments( ['-Werror=unknown-warning-option'] + args, env) def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] # Starting with XCode 8, we need to pass this to force linker # visibility to obey OS X and iOS minimum version targets with # -mmacosx-version-min, -miphoneos-version-min, etc. # https://github.com/Homebrew/homebrew-core/issues/3727 if self.clang_type == CLANG_OSX and version_compare(self.version, '>=8.0'): extra_args.append('-Wl,-no_weak_imports') return super().has_function(funcname, prefix, env, extra_args, dependencies) def get_std_shared_module_link_args(self): if self.clang_type == CLANG_OSX: return ['-bundle', '-Wl,-undefined,dynamic_lookup'] return ['-shared'] class ClangCCompiler(ClangCompiler, CCompiler): def __init__(self, exelist, version, clang_type, is_cross, exe_wrapper=None): CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) ClangCompiler.__init__(self, clang_type) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'1': default_warn_args, '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} def get_options(self): return {'c_std': coredata.UserComboOption('c_std', 'C language standard to use', ['none', 'c89', 'c99', 'c11', 'gnu89', 'gnu99', 'gnu11'], 'none')} def get_option_compile_args(self, options): args = [] std = options['c_std'] if std.value != 'none': args.append('-std=' + std.value) return args def get_option_link_args(self, options): return [] class ClangCPPCompiler(ClangCompiler, CPPCompiler): def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None): CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) ClangCompiler.__init__(self, cltype) default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor'] self.warn_args = {'1': default_warn_args, '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} def get_options(self): return {'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use', ['none', 'c++03', 'c++11', 'c++14', 'c++1z', 'gnu++11', 'gnu++14', 'gnu++1z'], 'none')} def get_option_compile_args(self, options): args = [] std = options['cpp_std'] if std.value != 'none': args.append('-std=' + std.value) return args def get_option_link_args(self, options): return [] class ClangObjCCompiler(ClangCompiler, GnuObjCCompiler): def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None): GnuObjCCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) ClangCompiler.__init__(self, cltype) self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage'] class ClangObjCPPCompiler(ClangCompiler, GnuObjCPPCompiler): def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None): GnuObjCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) ClangCompiler.__init__(self, cltype) self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage'] # Tested on linux for ICC 14.0.3, 15.0.6, 16.0.4, 17.0.1 class IntelCompiler: def __init__(self, icc_type): self.id = 'intel' self.icc_type = icc_type self.lang_header = 'none' self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage', 'b_colorout', 'b_ndebug', 'b_staticpic', 'b_lundef', 'b_asneeded'] # Assembly self.can_compile_suffixes.add('s') def get_pic_args(self): return ['-fPIC'] def get_buildtype_args(self, buildtype): return gnulike_buildtype_args[buildtype] def get_buildtype_linker_args(self, buildtype): return gnulike_buildtype_linker_args[buildtype] def get_pch_suffix(self): return 'pchi' def get_pch_use_args(self, pch_dir, header): return ['-pch', '-pch_dir', os.path.join(pch_dir), '-x', self.lang_header, '-include', header, '-x', 'none'] def get_pch_name(self, header_name): return os.path.split(header_name)[-1] + '.' + self.get_pch_suffix() def split_shlib_to_parts(self, fname): return os.path.split(fname)[0], fname def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): if self.icc_type == ICC_STANDARD: gcc_type = GCC_STANDARD elif self.icc_type == ICC_OSX: gcc_type = GCC_OSX elif self.icc_type == ICC_WIN: gcc_type = GCC_MINGW else: raise MesonException('Unreachable code when converting icc type to gcc type.') return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) def get_std_shared_lib_link_args(self): # FIXME: Don't know how icc works on OSX # if self.icc_type == ICC_OSX: # return ['-bundle'] return ['-shared'] class IntelCCompiler(IntelCompiler, CCompiler): def __init__(self, exelist, version, icc_type, is_cross, exe_wrapper=None): CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) IntelCompiler.__init__(self, icc_type) self.lang_header = 'c-header' default_warn_args = ['-Wall', '-w3', '-diag-disable:remark', '-Wpch-messages'] self.warn_args = {'1': default_warn_args, '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} def get_options(self): c_stds = ['c89', 'c99'] g_stds = ['gnu89', 'gnu99'] if mesonlib.version_compare(self.version, '>=16.0.0'): c_stds += ['c11'] opts = {'c_std': coredata.UserComboOption('c_std', 'C language standard to use', ['none'] + c_stds + g_stds, 'none')} return opts def get_option_compile_args(self, options): args = [] std = options['c_std'] if std.value != 'none': args.append('-std=' + std.value) return args def get_std_shared_lib_link_args(self): return ['-shared'] def has_multi_arguments(self, args, env): return super().has_multi_arguments(args + ['-diag-error', '10006'], env) class IntelCPPCompiler(IntelCompiler, CPPCompiler): def __init__(self, exelist, version, icc_type, is_cross, exe_wrap): CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap) IntelCompiler.__init__(self, icc_type) self.lang_header = 'c++-header' default_warn_args = ['-Wall', '-w3', '-diag-disable:remark', '-Wpch-messages', '-Wnon-virtual-dtor'] self.warn_args = {'1': default_warn_args, '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} def get_options(self): c_stds = [] g_stds = ['gnu++98'] if mesonlib.version_compare(self.version, '>=15.0.0'): c_stds += ['c++11', 'c++14'] g_stds += ['gnu++11'] if mesonlib.version_compare(self.version, '>=16.0.0'): c_stds += ['c++17'] if mesonlib.version_compare(self.version, '>=17.0.0'): g_stds += ['gnu++14'] opts = {'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use', ['none'] + c_stds + g_stds, 'none'), 'cpp_debugstl': coredata.UserBooleanOption('cpp_debugstl', 'STL debug mode', False)} return opts def get_option_compile_args(self, options): args = [] std = options['cpp_std'] if std.value != 'none': args.append('-std=' + std.value) if options['cpp_debugstl'].value: args.append('-D_GLIBCXX_DEBUG=1') return args def get_option_link_args(self, options): return [] def has_multi_arguments(self, args, env): return super().has_multi_arguments(args + ['-diag-error', '10006'], env) class FortranCompiler(Compiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None): self.language = 'fortran' super().__init__(exelist, version) self.is_cross = is_cross self.exe_wrapper = exe_wrapper # Not really correct but I don't have Fortran compilers to test with. Sorry. self.gcc_type = GCC_STANDARD self.id = "IMPLEMENTATION CLASSES MUST SET THIS" def name_string(self): return ' '.join(self.exelist) def get_pic_args(self): if self.gcc_type in (GCC_MINGW, GCC_OSX): return [] # On Window and OS X, pic is always on. return ['-fPIC'] def get_std_shared_lib_link_args(self): return ['-shared'] def needs_static_linker(self): return True def sanity_check(self, work_dir, environment): source_name = os.path.join(work_dir, 'sanitycheckf.f90') binary_name = os.path.join(work_dir, 'sanitycheckf') with open(source_name, 'w') as ofile: ofile.write('''program prog print *, "Fortran compilation is working." end program prog ''') extra_flags = self.get_cross_extra_flags(environment, compile=True, link=True) pc = subprocess.Popen(self.exelist + extra_flags + [source_name, '-o', binary_name]) pc.wait() if pc.returncode != 0: raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) if self.is_cross: if self.exe_wrapper is None: # Can't check if the binaries run so we have to assume they do return cmdlist = self.exe_wrapper + [binary_name] else: cmdlist = [binary_name] pe = subprocess.Popen(cmdlist, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) pe.wait() if pe.returncode != 0: raise EnvironmentException('Executables created by Fortran compiler %s are not runnable.' % self.name_string()) def get_std_warn_args(self, level): return FortranCompiler.std_warn_args def get_buildtype_args(self, buildtype): return gnulike_buildtype_args[buildtype] def get_buildtype_linker_args(self, buildtype): if mesonlib.is_osx(): return apple_buildtype_linker_args[buildtype] return gnulike_buildtype_linker_args[buildtype] def split_shlib_to_parts(self, fname): return os.path.split(fname)[0], fname def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): return get_gcc_soname_args(self.gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) def get_dependency_gen_args(self, outtarget, outfile): # Disabled until this is fixed: # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=62162 # return ['-cpp', '-MMD', '-MQ', outtarget] return [] def get_output_args(self, target): return ['-o', target] def get_preprocess_only_args(self): return ['-E'] def get_compile_only_args(self): return ['-c'] def get_linker_exelist(self): return self.exelist[:] def get_linker_output_args(self, outputname): return ['-o', outputname] def get_include_args(self, path, is_system): return ['-I' + path] def get_module_outdir_args(self, path): return ['-J' + path] def depfile_for_object(self, objfile): return objfile + '.' + self.get_depfile_suffix() def get_depfile_suffix(self): return 'd' def get_std_exe_link_args(self): return [] def build_rpath_args(self, build_dir, rpath_paths, install_rpath): return build_unix_rpath_args(build_dir, rpath_paths, install_rpath) def module_name_to_filename(self, module_name): return module_name.lower() + '.mod' def get_warn_args(self, level): return ['-Wall'] def get_no_warn_args(self): return ['-w'] class GnuFortranCompiler(FortranCompiler): def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None): super().__init__(exelist, version, is_cross, exe_wrapper=None) self.gcc_type = gcc_type self.defines = defines or {} self.id = 'gcc' def has_define(self, define): return define in self.defines def get_define(self, define): if define in self.defines: return self.defines[define] def get_always_args(self): return ['-pipe'] def gen_import_library_args(self, implibname): """ The name of the outputted import library Used only on Windows """ return ['-Wl,--out-implib=' + implibname] class G95FortranCompiler(FortranCompiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None): super().__init__(exelist, version, is_cross, exe_wrapper=None) self.id = 'g95' def get_module_outdir_args(self, path): return ['-fmod=' + path] def get_always_args(self): return ['-pipe'] def get_no_warn_args(self): # FIXME: Confirm that there's no compiler option to disable all warnings return [] def gen_import_library_args(self, implibname): """ The name of the outputted import library Used only on Windows """ return ['-Wl,--out-implib=' + implibname] class SunFortranCompiler(FortranCompiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None): super().__init__(exelist, version, is_cross, exe_wrapper=None) self.id = 'sun' def get_dependency_gen_args(self, outtarget, outfile): return ['-fpp'] def get_always_args(self): return [] def get_warn_args(self, level): return [] def get_module_outdir_args(self, path): return ['-moddir=' + path] class IntelFortranCompiler(IntelCompiler, FortranCompiler): std_warn_args = ['-warn', 'all'] def __init__(self, exelist, version, is_cross, exe_wrapper=None): self.file_suffixes = ('f90', 'f', 'for', 'ftn', 'fpp') FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) # FIXME: Add support for OS X and Windows in detect_fortran_compiler so # we are sent the type of compiler IntelCompiler.__init__(self, ICC_STANDARD) self.id = 'intel' def get_module_outdir_args(self, path): return ['-module', path] def get_warn_args(self, level): return IntelFortranCompiler.std_warn_args class PathScaleFortranCompiler(FortranCompiler): std_warn_args = ['-fullwarn'] def __init__(self, exelist, version, is_cross, exe_wrapper=None): super().__init__(exelist, version, is_cross, exe_wrapper=None) self.id = 'pathscale' def get_module_outdir_args(self, path): return ['-module', path] def get_std_warn_args(self, level): return PathScaleFortranCompiler.std_warn_args class PGIFortranCompiler(FortranCompiler): std_warn_args = ['-Minform=inform'] def __init__(self, exelist, version, is_cross, exe_wrapper=None): super().__init__(exelist, version, is_cross, exe_wrapper=None) self.id = 'pgi' def get_module_outdir_args(self, path): return ['-module', path] def get_warn_args(self, level): return PGIFortranCompiler.std_warn_args def get_no_warn_args(self): return ['-silent'] class Open64FortranCompiler(FortranCompiler): std_warn_args = ['-fullwarn'] def __init__(self, exelist, version, is_cross, exe_wrapper=None): super().__init__(exelist, version, is_cross, exe_wrapper=None) self.id = 'open64' def get_module_outdir_args(self, path): return ['-module', path] def get_warn_args(self, level): return Open64FortranCompiler.std_warn_args class NAGFortranCompiler(FortranCompiler): std_warn_args = [] def __init__(self, exelist, version, is_cross, exe_wrapper=None): super().__init__(exelist, version, is_cross, exe_wrapper=None) self.id = 'nagfor' def get_module_outdir_args(self, path): return ['-mdir', path] def get_warn_args(self, level): return NAGFortranCompiler.std_warn_args class StaticLinker: pass class VisualStudioLinker(StaticLinker): always_args = ['/NOLOGO'] def __init__(self, exelist): self.exelist = exelist def get_exelist(self): return self.exelist[:] def get_std_link_args(self): return [] def get_buildtype_linker_args(self, buildtype): return [] def get_output_args(self, target): return ['/OUT:' + target] def get_coverage_link_args(self): return [] def get_always_args(self): return VisualStudioLinker.always_args def get_linker_always_args(self): return VisualStudioLinker.always_args def build_rpath_args(self, build_dir, rpath_paths, install_rpath): return [] def thread_link_flags(self): return [] def get_option_link_args(self, options): return [] @classmethod def unix_args_to_native(cls, args): return VisualStudioCCompiler.unix_args_to_native(args) def get_link_debugfile_args(self, targetfile): pdbarr = targetfile.split('.')[:-1] pdbarr += ['pdb'] return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)] class ArLinker(StaticLinker): def __init__(self, exelist): self.exelist = exelist self.id = 'ar' pc, stdo = Popen_safe(self.exelist + ['-h'])[0:2] # Enable deterministic builds if they are available. if '[D]' in stdo: self.std_args = ['csrD'] else: self.std_args = ['csr'] def build_rpath_args(self, build_dir, rpath_paths, install_rpath): return [] def get_exelist(self): return self.exelist[:] def get_std_link_args(self): return self.std_args def get_output_args(self, target): return [target] def get_buildtype_linker_args(self, buildtype): return [] def get_linker_always_args(self): return [] def get_coverage_link_args(self): return [] def get_always_args(self): return [] def thread_link_flags(self): return [] def get_option_link_args(self, options): return [] @classmethod def unix_args_to_native(cls, args): return args[:] def get_link_debugfile_args(self, targetfile): return []