aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/environment.py
blob: 28c26e2f21b432041d7e65ddcb5fbca03be8980f (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
# Copyright 2012-2016 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.

import configparser, os, platform, re, shlex, shutil, subprocess

from . import coredata
from .linkers import ArLinker, VisualStudioLinker
from . import mesonlib
from .mesonlib import EnvironmentException, Popen_safe
from . import mlog

from . import compilers
from .compilers import (
    CLANG_OSX,
    CLANG_STANDARD,
    CLANG_WIN,
    GCC_CYGWIN,
    GCC_MINGW,
    GCC_OSX,
    GCC_STANDARD,
    ICC_STANDARD,
    is_assembly,
    is_header,
    is_library,
    is_llvm_ir,
    is_object,
    is_source,
)
from .compilers import (
    ArmCCompiler,
    ArmCPPCompiler,
    ArmclangCCompiler,
    ArmclangCPPCompiler,
    ClangCCompiler,
    ClangCPPCompiler,
    ClangObjCCompiler,
    ClangObjCPPCompiler,
    G95FortranCompiler,
    GnuCCompiler,
    GnuCPPCompiler,
    GnuFortranCompiler,
    GnuObjCCompiler,
    GnuObjCPPCompiler,
    ElbrusCCompiler,
    ElbrusCPPCompiler,
    ElbrusFortranCompiler,
    IntelCCompiler,
    IntelCPPCompiler,
    IntelFortranCompiler,
    JavaCompiler,
    MonoCompiler,
    VisualStudioCsCompiler,
    NAGFortranCompiler,
    Open64FortranCompiler,
    PathScaleFortranCompiler,
    PGIFortranCompiler,
    RustCompiler,
    SunFortranCompiler,
    ValaCompiler,
    VisualStudioCCompiler,
    VisualStudioCPPCompiler,
)

build_filename = 'meson.build'

known_cpu_families = (
    'aarch64',
    'arm',
    'e2k',
    'ia64',
    'mips',
    'mips64',
    'parisc',
    'ppc',
    'ppc64',
    'riscv32',
    'riscv64',
    'sparc',
    'sparc64',
    'x86',
    'x86_64'
)

def detect_gcovr(version='3.1', log=False):
    gcovr_exe = 'gcovr'
    try:
        p, found = Popen_safe([gcovr_exe, '--version'])[0:2]
    except (FileNotFoundError, PermissionError):
        # Doesn't exist in PATH or isn't executable
        return None, None
    found = search_version(found)
    if p.returncode == 0:
        if log:
            mlog.log('Found gcovr-{} at {}'.format(found, shlex.quote(shutil.which(gcovr_exe))))
        return gcovr_exe, mesonlib.version_compare(found, '>=' + version)
    return None, None

def find_coverage_tools():
    gcovr_exe, gcovr_new_rootdir = detect_gcovr()

    lcov_exe = 'lcov'
    genhtml_exe = 'genhtml'

    if not mesonlib.exe_exists([lcov_exe, '--version']):
        lcov_exe = None
    if not mesonlib.exe_exists([genhtml_exe, '--version']):
        genhtml_exe = None

    return gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe

def detect_ninja(version='1.5', log=False):
    for n in ['ninja', 'ninja-build']:
        try:
            p, found = Popen_safe([n, '--version'])[0:2]
        except (FileNotFoundError, PermissionError):
            # Doesn't exist in PATH or isn't executable
            continue
        found = found.strip()
        # Perhaps we should add a way for the caller to know the failure mode
        # (not found or too old)
        if p.returncode == 0 and mesonlib.version_compare(found, '>=' + version):
            if log:
                mlog.log('Found ninja-{} at {}'.format(found, shlex.quote(shutil.which(n))))
            return n

def detect_native_windows_arch():
    """
    The architecture of Windows itself: x86 or amd64
    """
    # These env variables are always available. See:
    # https://msdn.microsoft.com/en-us/library/aa384274(VS.85).aspx
    # https://blogs.msdn.microsoft.com/david.wang/2006/03/27/howto-detect-process-bitness/
    arch = os.environ.get('PROCESSOR_ARCHITEW6432', '').lower()
    if not arch:
        try:
            # If this doesn't exist, something is messing with the environment
            arch = os.environ['PROCESSOR_ARCHITECTURE'].lower()
        except KeyError:
            raise EnvironmentException('Unable to detect native OS architecture')
    return arch

def detect_windows_arch(compilers):
    """
    Detecting the 'native' architecture of Windows is not a trivial task. We
    cannot trust that the architecture that Python is built for is the 'native'
    one because you can run 32-bit apps on 64-bit Windows using WOW64 and
    people sometimes install 32-bit Python on 64-bit Windows.

    We also can't rely on the architecture of the OS itself, since it's
    perfectly normal to compile and run 32-bit applications on Windows as if
    they were native applications. It's a terrible experience to require the
    user to supply a cross-info file to compile 32-bit applications on 64-bit
    Windows. Thankfully, the only way to compile things with Visual Studio on
    Windows is by entering the 'msvc toolchain' environment, which can be
    easily detected.

    In the end, the sanest method is as follows:
    1. Check if we're in an MSVC toolchain environment, and if so, return the
       MSVC toolchain architecture as our 'native' architecture.
    2. If not, check environment variables that are set by Windows and WOW64 to
       find out the architecture that Windows is built for, and use that as our
       'native' architecture.
    """
    os_arch = detect_native_windows_arch()
    if os_arch != 'amd64':
        return os_arch
    # If we're on 64-bit Windows, 32-bit apps can be compiled without
    # cross-compilation. So if we're doing that, just set the native arch as
    # 32-bit and pretend like we're running under WOW64. Else, return the
    # actual Windows architecture that we deduced above.
    for compiler in compilers.values():
        # Check if we're using and inside an MSVC toolchain environment
        if compiler.id == 'msvc' and 'VCINSTALLDIR' in os.environ:
            if float(compiler.get_toolset_version()) < 10.0:
                # On MSVC 2008 and earlier, check 'BUILD_PLAT', where
                # 'Win32' means 'x86'
                platform = os.environ.get('BUILD_PLAT', 'x86')
                if platform == 'Win32':
                    return 'x86'
            else:
                # On MSVC 2010 and later 'Platform' is only set when the
                # target arch is not 'x86'.  It's 'x64' when targeting
                # x86_64 and 'arm' when targeting ARM.
                platform = os.environ.get('Platform', 'x86').lower()
            if platform == 'x86':
                return platform
        if compiler.id == 'gcc' and compiler.has_builtin_define('__i386__'):
            return 'x86'
    return os_arch

def detect_cpu_family(compilers):
    """
    Python is inconsistent in its platform module.
    It returns different values for the same cpu.
    For x86 it might return 'x86', 'i686' or somesuch.
    Do some canonicalization.
    """
    if mesonlib.is_windows():
        trial = detect_windows_arch(compilers)
    else:
        trial = platform.machine().lower()
    if trial.startswith('i') and trial.endswith('86'):
        return 'x86'
    if trial.startswith('arm'):
        return 'arm'
    if trial.startswith('ppc64'):
        return 'ppc64'
    if trial in ('amd64', 'x64'):
        trial = 'x86_64'
    if trial == 'x86_64':
        # On Linux (and maybe others) there can be any mixture of 32/64 bit
        # code in the kernel, Python, system etc. The only reliable way
        # to know is to check the compiler defines.
        for c in compilers.values():
            try:
                if c.has_builtin_define('__i386__'):
                    return 'x86'
            except mesonlib.MesonException:
                # Ignore compilers that do not support has_builtin_define.
                pass
        return 'x86_64'
    # Add fixes here as bugs are reported.

    if trial not in known_cpu_families:
        mlog.warning('Unknown CPU family {!r}, please report this at '
                     'https://github.com/mesonbuild/meson/issues/new with the'
                     'output of `uname -a` and `cat /proc/cpuinfo`'.format(trial))

    return trial

def detect_cpu(compilers):
    if mesonlib.is_windows():
        trial = detect_windows_arch(compilers)
    else:
        trial = platform.machine().lower()
    if trial in ('amd64', 'x64'):
        trial = 'x86_64'
    if trial == 'x86_64':
        # Same check as above for cpu_family
        for c in compilers.values():
            try:
                if c.has_builtin_define('__i386__'):
                    return 'i686' # All 64 bit cpus have at least this level of x86 support.
            except mesonlib.MesonException:
                pass
        return 'x86_64'
    if trial == 'e2k':
        # Make more precise CPU detection for Elbrus platform.
        trial = platform.processor().lower()
    # Add fixes here as bugs are reported.
    return trial

def detect_system():
    system = platform.system().lower()
    if system.startswith('cygwin'):
        return 'cygwin'
    return system

def detect_msys2_arch():
    if 'MSYSTEM_CARCH' in os.environ:
        return os.environ['MSYSTEM_CARCH']
    return None

def search_version(text):
    # Usually of the type 4.1.4 but compiler output may contain
    # stuff like this:
    # (Sourcery CodeBench Lite 2014.05-29) 4.8.3 20140320 (prerelease)
    # Limiting major version number to two digits seems to work
    # thus far. When we get to GCC 100, this will break, but
    # if we are still relevant when that happens, it can be
    # considered an achievement in itself.
    #
    # This regex is reaching magic levels. If it ever needs
    # to be updated, do not complexify but convert to something
    # saner instead.
    version_regex = '(?<!(\d|\.))(\d{1,2}(\.\d+)+(-[a-zA-Z0-9]+)?)'
    match = re.search(version_regex, text)
    if match:
        return match.group(0)
    return 'unknown version'

class Environment:
    private_dir = 'meson-private'
    log_dir = 'meson-logs'

    def __init__(self, source_dir, build_dir, options):
        self.source_dir = source_dir
        self.build_dir = build_dir
        self.scratch_dir = os.path.join(build_dir, Environment.private_dir)
        self.log_dir = os.path.join(build_dir, Environment.log_dir)
        os.makedirs(self.scratch_dir, exist_ok=True)
        os.makedirs(self.log_dir, exist_ok=True)
        try:
            self.coredata = coredata.load(self.get_build_dir())
            self.first_invocation = False
        except FileNotFoundError:
            # WARNING: Don't use any values from coredata in __init__. It gets
            # re-initialized with project options by the interpreter during
            # build file parsing.
            self.coredata = coredata.CoreData(options)
            # Used by the regenchecker script, which runs meson
            self.coredata.meson_command = mesonlib.meson_command
            self.first_invocation = True
        self.cross_info = None
        self.exe_wrapper = None
        if self.coredata.cross_file:
            self.cross_info = CrossBuildInfo(self.coredata.cross_file)
            if 'exe_wrapper' in self.cross_info.config['binaries']:
                from .dependencies import ExternalProgram
                self.exe_wrapper = ExternalProgram.from_cross_info(self.cross_info, 'exe_wrapper')
        self.cmd_line_options = options.cmd_line_options.copy()

        # List of potential compilers.
        if mesonlib.is_windows():
            self.default_c = ['cl', 'cc', 'gcc', 'clang']
            self.default_cpp = ['cl', 'c++', 'g++', 'clang++']
        else:
            self.default_c = ['cc', 'gcc', 'clang']
            self.default_cpp = ['c++', 'g++', 'clang++']
        if mesonlib.is_windows():
            self.default_cs = ['csc', 'mcs']
        else:
            self.default_cs = ['mcs', 'csc']
        self.default_objc = ['cc']
        self.default_objcpp = ['c++']
        self.default_fortran = ['gfortran', 'g95', 'f95', 'f90', 'f77', 'ifort']
        self.default_rust = ['rustc']
        self.default_static_linker = ['ar']
        self.vs_static_linker = ['lib']
        self.gcc_static_linker = ['gcc-ar']
        self.clang_static_linker = ['llvm-ar']

        # Various prefixes and suffixes for import libraries, shared libraries,
        # static libraries, and executables.
        # Versioning is added to these names in the backends as-needed.
        cross = self.is_cross_build()
        if mesonlib.for_windows(cross, self):
            self.exe_suffix = 'exe'
            self.object_suffix = 'obj'
            self.win_libdir_layout = True
        elif mesonlib.for_cygwin(cross, self):
            self.exe_suffix = 'exe'
            self.object_suffix = 'o'
            self.win_libdir_layout = True
        else:
            self.exe_suffix = ''
            self.object_suffix = 'o'
            self.win_libdir_layout = False
        if 'STRIP' in os.environ:
            self.native_strip_bin = shlex.split(os.environ['STRIP'])
        else:
            self.native_strip_bin = ['strip']

    def is_cross_build(self):
        return self.cross_info is not None

    def dump_coredata(self):
        return coredata.save(self.coredata, self.get_build_dir())

    def get_script_dir(self):
        import mesonbuild.scripts
        return os.path.dirname(mesonbuild.scripts.__file__)

    def get_log_dir(self):
        return self.log_dir

    def get_coredata(self):
        return self.coredata

    def get_build_command(self, unbuffered=False):
        cmd = mesonlib.meson_command[:]
        if unbuffered and 'python' in cmd[0]:
            cmd.insert(1, '-u')
        return cmd

    def is_header(self, fname):
        return is_header(fname)

    def is_source(self, fname):
        return is_source(fname)

    def is_assembly(self, fname):
        return is_assembly(fname)

    def is_llvm_ir(self, fname):
        return is_llvm_ir(fname)

    def is_object(self, fname):
        return is_object(fname)

    def is_library(self, fname):
        return is_library(fname)

    @staticmethod
    def get_gnu_compiler_defines(compiler):
        """
        Detect GNU compiler platform type (Apple, MinGW, Unix)
        """
        # Arguments to output compiler pre-processor defines to stdout
        # gcc, g++, and gfortran all support these arguments
        args = compiler + ['-E', '-dM', '-']
        p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE)
        if p.returncode != 0:
            raise EnvironmentException('Unable to detect GNU compiler type:\n' + output + error)
        # Parse several lines of the type:
        # `#define ___SOME_DEF some_value`
        # and extract `___SOME_DEF`
        defines = {}
        for line in output.split('\n'):
            if not line:
                continue
            d, *rest = line.split(' ', 2)
            if d != '#define':
                continue
            if len(rest) == 1:
                defines[rest] = True
            if len(rest) == 2:
                defines[rest[0]] = rest[1]
        return defines

    @staticmethod
    def get_gnu_version_from_defines(defines):
        dot = '.'
        major = defines.get('__GNUC__', '0')
        minor = defines.get('__GNUC_MINOR__', '0')
        patch = defines.get('__GNUC_PATCHLEVEL__', '0')
        return dot.join((major, minor, patch))

    @staticmethod
    def get_lcc_version_from_defines(defines):
        dot = '.'
        generation_and_major = defines.get('__LCC__', '100')
        generation = generation_and_major[:1]
        major = generation_and_major[1:]
        minor = defines.get('__LCC_MINOR__', '0')
        return dot.join((generation, major, minor))

    @staticmethod
    def get_gnu_compiler_type(defines):
        # Detect GCC type (Apple, MinGW, Cygwin, Unix)
        if '__APPLE__' in defines:
            return GCC_OSX
        elif '__MINGW32__' in defines or '__MINGW64__' in defines:
            return GCC_MINGW
        elif '__CYGWIN__' in defines:
            return GCC_CYGWIN
        return GCC_STANDARD

    def warn_about_lang_pointing_to_cross(self, compiler_exe, evar):
        evar_str = os.environ.get(evar, 'WHO_WOULD_CALL_THEIR_COMPILER_WITH_THIS_NAME')
        if evar_str == compiler_exe:
            mlog.warning('''Env var %s seems to point to the cross compiler.
This is probably wrong, it should always point to the native compiler.''' % evar)

    def _get_compilers(self, lang, evar, want_cross):
        '''
        The list of compilers is detected in the exact same way for
        C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here.
        '''
        if self.is_cross_build() and want_cross:
            if lang not in self.cross_info.config['binaries']:
                raise EnvironmentException('{!r} compiler binary not defined in cross file'.format(lang))
            compilers = mesonlib.stringlistify(self.cross_info.config['binaries'][lang])
            # Ensure ccache exists and remove it if it doesn't
            if compilers[0] == 'ccache':
                compilers = compilers[1:]
                ccache = self.detect_ccache()
            else:
                ccache = []
            self.warn_about_lang_pointing_to_cross(compilers[0], evar)
            # Return value has to be a list of compiler 'choices'
            compilers = [compilers]
            is_cross = True
            exe_wrap = self.get_exe_wrapper()
        elif evar in os.environ:
            compilers = shlex.split(os.environ[evar])
            # Ensure ccache exists and remove it if it doesn't
            if compilers[0] == 'ccache':
                compilers = compilers[1:]
                ccache = self.detect_ccache()
            else:
                ccache = []
            # Return value has to be a list of compiler 'choices'
            compilers = [compilers]
            is_cross = False
            exe_wrap = None
        else:
            compilers = getattr(self, 'default_' + lang)
            ccache = self.detect_ccache()
            is_cross = False
            exe_wrap = None
        return compilers, ccache, is_cross, exe_wrap

    def _handle_exceptions(self, exceptions, binaries, bintype='compiler'):
        errmsg = 'Unknown {}(s): {}'.format(bintype, binaries)
        if exceptions:
            errmsg += '\nThe follow exceptions were encountered:'
            for (c, e) in exceptions.items():
                errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e)
        raise EnvironmentException(errmsg)

    def _detect_c_or_cpp_compiler(self, lang, evar, want_cross):
        popen_exceptions = {}
        compilers, ccache, is_cross, exe_wrap = self._get_compilers(lang, evar, want_cross)
        for compiler in compilers:
            if isinstance(compiler, str):
                compiler = [compiler]
            if 'cl' in compiler or 'cl.exe' in compiler:
                # Watcom C provides it's own cl.exe clone that mimics an older
                # version of Microsoft's compiler. Since Watcom's cl.exe is
                # just a wrapper, we skip using it if we detect its presence
                # so as not to confuse Meson when configuring for MSVC.
                #
                # Additionally the help text of Watcom's cl.exe is paged, and
                # the binary will not exit without human intervention. In
                # practice, Meson will block waiting for Watcom's cl.exe to
                # exit, which requires user input and thus will never exit.
                if 'WATCOM' in os.environ:
                    def sanitize(p):
                        return os.path.normcase(os.path.abspath(p))

                    watcom_cls = [sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl')),
                                  sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl.exe'))]
                    found_cl = sanitize(shutil.which('cl'))
                    if found_cl in watcom_cls:
                        continue
                arg = '/?'
            elif 'armcc' in compiler[0]:
                arg = '--vsn'
            else:
                arg = '--version'
            try:
                p, out, err = Popen_safe(compiler + [arg])
            except OSError as e:
                popen_exceptions[' '.join(compiler + [arg])] = e
                continue
            version = search_version(out)
            full_version = out.split('\n', 1)[0]

            guess_gcc_or_lcc = False
            if 'Free Software Foundation' in out:
                guess_gcc_or_lcc = 'gcc'
            if 'e2k' in out and 'lcc' in out:
                guess_gcc_or_lcc = 'lcc'

            if guess_gcc_or_lcc:
                defines = self.get_gnu_compiler_defines(compiler)
                if not defines:
                    popen_exceptions[' '.join(compiler)] = 'no pre-processor defines'
                    continue
                gtype = self.get_gnu_compiler_type(defines)
                if guess_gcc_or_lcc == 'lcc':
                    version = self.get_lcc_version_from_defines(defines)
                    cls = ElbrusCCompiler if lang == 'c' else ElbrusCPPCompiler
                else:
                    version = self.get_gnu_version_from_defines(defines)
                    cls = GnuCCompiler if lang == 'c' else GnuCPPCompiler
                return cls(ccache + compiler, version, gtype, is_cross, exe_wrap, defines, full_version=full_version)

            if 'armclang' in out:
                # The compiler version is not present in the first line of output,
                # instead it is present in second line, startswith 'Component:'.
                # So, searching for the 'Component' in out although we know it is
                # present in second line, as we are not sure about the
                # output format in future versions
                arm_ver_str = re.search('.*Component.*', out)
                if arm_ver_str is None:
                    popen_exceptions[' '.join(compiler)] = 'version string not found'
                    continue
                arm_ver_str = arm_ver_str.group(0)
                # Override previous values
                version = search_version(arm_ver_str)
                full_version = arm_ver_str
                cls = ArmclangCCompiler if lang == 'c' else ArmclangCPPCompiler
                return cls(ccache + compiler, version, is_cross, exe_wrap, full_version=full_version)
            if 'clang' in out:
                if 'Apple' in out or mesonlib.for_darwin(want_cross, self):
                    cltype = CLANG_OSX
                elif 'windows' in out or mesonlib.for_windows(want_cross, self):
                    cltype = CLANG_WIN
                else:
                    cltype = CLANG_STANDARD
                cls = ClangCCompiler if lang == 'c' else ClangCPPCompiler
                return cls(ccache + compiler, version, cltype, is_cross, exe_wrap, full_version=full_version)
            if 'Microsoft' in out or 'Microsoft' in err:
                # Latest versions of Visual Studio print version
                # number to stderr but earlier ones print version
                # on stdout.  Why? Lord only knows.
                # Check both outputs to figure out version.
                version = search_version(err)
                if version == 'unknown version':
                    version = search_version(out)
                if version == 'unknown version':
                    m = 'Failed to detect MSVC compiler arch: stderr was\n{!r}'
                    raise EnvironmentException(m.format(err))
                is_64 = err.split('\n')[0].endswith(' x64')
                cls = VisualStudioCCompiler if lang == 'c' else VisualStudioCPPCompiler
                return cls(compiler, version, is_cross, exe_wrap, is_64)
            if '(ICC)' in out:
                # TODO: add microsoft add check OSX
                inteltype = ICC_STANDARD
                cls = IntelCCompiler if lang == 'c' else IntelCPPCompiler
                return cls(ccache + compiler, version, inteltype, is_cross, exe_wrap, full_version=full_version)
            if 'ARM' in out:
                cls = ArmCCompiler if lang == 'c' else ArmCPPCompiler
                return cls(ccache + compiler, version, is_cross, exe_wrap, full_version=full_version)
        self._handle_exceptions(popen_exceptions, compilers)

    def detect_c_compiler(self, want_cross):
        return self._detect_c_or_cpp_compiler('c', 'CC', want_cross)

    def detect_cpp_compiler(self, want_cross):
        return self._detect_c_or_cpp_compiler('cpp', 'CXX', want_cross)

    def detect_fortran_compiler(self, want_cross):
        popen_exceptions = {}
        compilers, ccache, is_cross, exe_wrap = self._get_compilers('fortran', 'FC', want_cross)
        for compiler in compilers:
            if isinstance(compiler, str):
                compiler = [compiler]
            for arg in ['--version', '-V']:
                try:
                    p, out, err = Popen_safe(compiler + [arg])
                except OSError as e:
                    popen_exceptions[' '.join(compiler + [arg])] = e
                    continue

                version = search_version(out)
                full_version = out.split('\n', 1)[0]

                guess_gcc_or_lcc = False
                if 'GNU Fortran' in out:
                    guess_gcc_or_lcc = 'gcc'
                if 'e2k' in out and 'lcc' in out:
                    guess_gcc_or_lcc = 'lcc'

                if guess_gcc_or_lcc:
                    defines = self.get_gnu_compiler_defines(compiler)
                    if not defines:
                        popen_exceptions[' '.join(compiler)] = 'no pre-processor defines'
                        continue
                    gtype = self.get_gnu_compiler_type(defines)
                    if guess_gcc_or_lcc == 'lcc':
                        version = self.get_lcc_version_from_defines(defines)
                        cls = ElbrusFortranCompiler
                    else:
                        version = self.get_gnu_version_from_defines(defines)
                        cls = GnuFortranCompiler
                    return cls(compiler, version, gtype, is_cross, exe_wrap, defines, full_version=full_version)

                if 'G95' in out:
                    return G95FortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)

                if 'Sun Fortran' in err:
                    version = search_version(err)
                    return SunFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)

                if 'ifort (IFORT)' in out:
                    return IntelFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)

                if 'PathScale EKOPath(tm)' in err:
                    return PathScaleFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)

                if 'PGI Compilers' in out:
                    return PGIFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)

                if 'Open64 Compiler Suite' in err:
                    return Open64FortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)

                if 'NAG Fortran' in err:
                    return NAGFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
        self._handle_exceptions(popen_exceptions, compilers)

    def get_scratch_dir(self):
        return self.scratch_dir

    def detect_objc_compiler(self, want_cross):
        popen_exceptions = {}
        compilers, ccache, is_cross, exe_wrap = self._get_compilers('objc', 'OBJC', want_cross)
        for compiler in compilers:
            if isinstance(compiler, str):
                compiler = [compiler]
            arg = ['--version']
            try:
                p, out, err = Popen_safe(compiler + arg)
            except OSError as e:
                popen_exceptions[' '.join(compiler + arg)] = e
                continue
            version = search_version(out)
            if 'Free Software Foundation' in out or ('e2k' in out and 'lcc' in out):
                defines = self.get_gnu_compiler_defines(compiler)
                if not defines:
                    popen_exceptions[' '.join(compiler)] = 'no pre-processor defines'
                    continue
                gtype = self.get_gnu_compiler_type(defines)
                version = self.get_gnu_version_from_defines(defines)
                return GnuObjCCompiler(ccache + compiler, version, gtype, is_cross, exe_wrap, defines)
            if out.startswith('Apple LLVM'):
                return ClangObjCCompiler(ccache + compiler, version, CLANG_OSX, is_cross, exe_wrap)
            if out.startswith('clang'):
                return ClangObjCCompiler(ccache + compiler, version, CLANG_STANDARD, is_cross, exe_wrap)
        self._handle_exceptions(popen_exceptions, compilers)

    def detect_objcpp_compiler(self, want_cross):
        popen_exceptions = {}
        compilers, ccache, is_cross, exe_wrap = self._get_compilers('objcpp', 'OBJCXX', want_cross)
        for compiler in compilers:
            if isinstance(compiler, str):
                compiler = [compiler]
            arg = ['--version']
            try:
                p, out, err = Popen_safe(compiler + arg)
            except OSError as e:
                popen_exceptions[' '.join(compiler + arg)] = e
                continue
            version = search_version(out)
            if 'Free Software Foundation' in out or ('e2k' in out and 'lcc' in out):
                defines = self.get_gnu_compiler_defines(compiler)
                if not defines:
                    popen_exceptions[' '.join(compiler)] = 'no pre-processor defines'
                    continue
                gtype = self.get_gnu_compiler_type(defines)
                version = self.get_gnu_version_from_defines(defines)
                return GnuObjCPPCompiler(ccache + compiler, version, gtype, is_cross, exe_wrap, defines)
            if out.startswith('Apple LLVM'):
                return ClangObjCPPCompiler(ccache + compiler, version, CLANG_OSX, is_cross, exe_wrap)
            if out.startswith('clang'):
                return ClangObjCPPCompiler(ccache + compiler, version, CLANG_STANDARD, is_cross, exe_wrap)
        self._handle_exceptions(popen_exceptions, compilers)

    def detect_java_compiler(self):
        exelist = ['javac']
        try:
            p, out, err = Popen_safe(exelist + ['-version'])
        except OSError:
            raise EnvironmentException('Could not execute Java compiler "%s"' % ' '.join(exelist))
        version = search_version(err)
        if 'javac' in out or 'javac' in err:
            return JavaCompiler(exelist, version)
        raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')

    def detect_cs_compiler(self):
        compilers, ccache, is_cross, exe_wrap = self._get_compilers('cs', 'CSC', False)
        popen_exceptions = {}
        for comp in compilers:
            if not isinstance(comp, list):
                comp = [comp]
            try:
                p, out, err = Popen_safe(comp + ['--version'])
            except OSError as e:
                popen_exceptions[' '.join(comp + ['--version'])] = e
                continue

            version = search_version(out)
            if 'Mono' in out:
                return MonoCompiler(comp, version)
            elif "Visual C#" in out:
                return VisualStudioCsCompiler(comp, version)

        self._handle_exceptions(popen_exceptions, compilers)

    def detect_vala_compiler(self):
        if 'VALAC' in os.environ:
            exelist = shlex.split(os.environ['VALAC'])
        else:
            exelist = ['valac']
        try:
            p, out = Popen_safe(exelist + ['--version'])[0:2]
        except OSError:
            raise EnvironmentException('Could not execute Vala compiler "%s"' % ' '.join(exelist))
        version = search_version(out)
        if 'Vala' in out:
            return ValaCompiler(exelist, version)
        raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')

    def detect_rust_compiler(self, want_cross):
        popen_exceptions = {}
        compilers, ccache, is_cross, exe_wrap = self._get_compilers('rust', 'RUSTC', want_cross)
        for compiler in compilers:
            if isinstance(compiler, str):
                compiler = [compiler]
            arg = ['--version']
            try:
                p, out = Popen_safe(compiler + arg)[0:2]
            except OSError as e:
                popen_exceptions[' '.join(compiler + arg)] = e
                continue

            version = search_version(out)

            if 'rustc' in out:
                return RustCompiler(compiler, version, is_cross, exe_wrap)

        self._handle_exceptions(popen_exceptions, compilers)

    def detect_d_compiler(self, want_cross):
        is_cross = False
        # Search for a D compiler.
        # We prefer LDC over GDC unless overridden with the DC
        # environment variable because LDC has a much more
        # up to date language version at time (2016).
        if 'DC' in os.environ:
            exelist = shlex.split(os.environ['DC'])
        elif self.is_cross_build() and want_cross:
            exelist = mesonlib.stringlistify(self.cross_info.config['binaries']['d'])
            is_cross = True
        elif shutil.which("ldc2"):
            exelist = ['ldc2']
        elif shutil.which("ldc"):
            exelist = ['ldc']
        elif shutil.which("gdc"):
            exelist = ['gdc']
        elif shutil.which("dmd"):
            exelist = ['dmd']
        else:
            raise EnvironmentException('Could not find any supported D compiler.')

        try:
            p, out = Popen_safe(exelist + ['--version'])[0:2]
        except OSError:
            raise EnvironmentException('Could not execute D compiler "%s"' % ' '.join(exelist))
        version = search_version(out)
        full_version = out.split('\n', 1)[0]
        if 'LLVM D compiler' in out:
            return compilers.LLVMDCompiler(exelist, version, is_cross, full_version=full_version)
        elif 'gdc' in out:
            return compilers.GnuDCompiler(exelist, version, is_cross, full_version=full_version)
        elif 'The D Language Foundation' in out or 'Digital Mars' in out:
            return compilers.DmdDCompiler(exelist, version, is_cross, full_version=full_version)
        raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')

    def detect_swift_compiler(self):
        exelist = ['swiftc']
        try:
            p, _, err = Popen_safe(exelist + ['-v'])
        except OSError:
            raise EnvironmentException('Could not execute Swift compiler "%s"' % ' '.join(exelist))
        version = search_version(err)
        if 'Swift' in err:
            return compilers.SwiftCompiler(exelist, version)
        raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')

    def detect_static_linker(self, compiler):
        if compiler.is_cross:
            linker = self.cross_info.config['binaries']['ar']
            if isinstance(linker, str):
                linker = [linker]
            linkers = [linker]
        else:
            evar = 'AR'
            if evar in os.environ:
                linkers = [shlex.split(os.environ[evar])]
            elif isinstance(compiler, compilers.VisualStudioCCompiler):
                linkers = [self.vs_static_linker]
            elif isinstance(compiler, compilers.GnuCompiler):
                # Use gcc-ar if available; needed for LTO
                linkers = [self.gcc_static_linker, self.default_static_linker]
            elif isinstance(compiler, compilers.ClangCompiler):
                # Use llvm-ar if available; needed for LTO
                linkers = [self.clang_static_linker, self.default_static_linker]
            else:
                linkers = [self.default_static_linker]
        popen_exceptions = {}
        for linker in linkers:
            if 'lib' in linker or 'lib.exe' in linker:
                arg = '/?'
            else:
                arg = '--version'
            try:
                p, out, err = Popen_safe(linker + [arg])
            except OSError as e:
                popen_exceptions[' '.join(linker + [arg])] = e
                continue
            if '/OUT:' in out or '/OUT:' in err:
                return VisualStudioLinker(linker)
            if p.returncode == 0:
                return ArLinker(linker)
            if p.returncode == 1 and err.startswith('usage'): # OSX
                return ArLinker(linker)
        self._handle_exceptions(popen_exceptions, linkers, 'linker')
        raise EnvironmentException('Unknown static linker "%s"' % ' '.join(linkers))

    def detect_ccache(self):
        try:
            has_ccache = subprocess.call(['ccache', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        except OSError:
            has_ccache = 1
        if has_ccache == 0:
            cmdlist = ['ccache']
        else:
            cmdlist = []
        return cmdlist

    def get_source_dir(self):
        return self.source_dir

    def get_build_dir(self):
        return self.build_dir

    def get_exe_suffix(self):
        return self.exe_suffix

    def get_import_lib_dir(self):
        "Install dir for the import library (library used for linking)"
        return self.get_libdir()

    def get_shared_module_dir(self):
        "Install dir for shared modules that are loaded at runtime"
        return self.get_libdir()

    def get_shared_lib_dir(self):
        "Install dir for the shared library"
        if self.win_libdir_layout:
            return self.get_bindir()
        return self.get_libdir()

    def get_static_lib_dir(self):
        "Install dir for the static library"
        return self.get_libdir()

    def get_object_suffix(self):
        return self.object_suffix

    def get_prefix(self):
        return self.coredata.get_builtin_option('prefix')

    def get_libdir(self):
        return self.coredata.get_builtin_option('libdir')

    def get_libexecdir(self):
        return self.coredata.get_builtin_option('libexecdir')

    def get_bindir(self):
        return self.coredata.get_builtin_option('bindir')

    def get_includedir(self):
        return self.coredata.get_builtin_option('includedir')

    def get_mandir(self):
        return self.coredata.get_builtin_option('mandir')

    def get_datadir(self):
        return self.coredata.get_builtin_option('datadir')

    def get_compiler_system_dirs(self):
        for comp in self.coredata.compilers.values():
            if isinstance(comp, compilers.ClangCompiler):
                index = 1
                break
            elif isinstance(comp, compilers.GnuCompiler):
                index = 2
                break
        else:
            # This option is only supported by gcc and clang. If we don't get a
            # GCC or Clang compiler return and empty list.
            return []

        p, out, _ = Popen_safe(comp.get_exelist() + ['-print-search-dirs'])
        if p.returncode != 0:
            raise mesonlib.MesonException('Could not calculate system search dirs')
        out = out.split('\n')[index].lstrip('libraries: =').split(':')
        return [os.path.normpath(p) for p in out]

    def get_exe_wrapper(self):
        if not self.cross_info.need_exe_wrapper():
            from .dependencies import EmptyExternalProgram
            return EmptyExternalProgram()
        return self.exe_wrapper


class CrossBuildInfo:
    def __init__(self, filename):
        self.config = {'properties': {}}
        self.parse_datafile(filename)
        if 'host_machine' not in self.config and 'target_machine' not in self.config:
            raise mesonlib.MesonException('Cross info file must have either host or a target machine.')
        if 'host_machine' in self.config and 'binaries' not in self.config:
            raise mesonlib.MesonException('Cross file with "host_machine" is missing "binaries".')

    def ok_type(self, i):
        return isinstance(i, (str, int, bool))

    def parse_datafile(self, filename):
        config = configparser.ConfigParser()
        try:
            with open(filename, 'r') as f:
                config.read_file(f, filename)
        except FileNotFoundError:
            raise EnvironmentException('File not found: %s.' % filename)
        # This is a bit hackish at the moment.
        for s in config.sections():
            self.config[s] = {}
            for entry in config[s]:
                value = config[s][entry]
                if ' ' in entry or '\t' in entry or "'" in entry or '"' in entry:
                    raise EnvironmentException('Malformed variable name %s in cross file..' % entry)
                try:
                    res = eval(value, {'__builtins__': None}, {'true': True, 'false': False})
                except Exception:
                    raise EnvironmentException('Malformed value in cross file variable %s.' % entry)

                if entry == 'cpu_family' and res not in known_cpu_families:
                    mlog.warning('Unknown CPU family %s, please report this at https://github.com/mesonbuild/meson/issues/new' % value)

                if self.ok_type(res):
                    self.config[s][entry] = res
                elif isinstance(res, list):
                    for i in res:
                        if not self.ok_type(i):
                            raise EnvironmentException('Malformed value in cross file variable %s.' % entry)
                    self.config[s][entry] = res
                else:
                    raise EnvironmentException('Malformed value in cross file variable %s.' % entry)

    def has_host(self):
        return 'host_machine' in self.config

    def has_target(self):
        return 'target_machine' in self.config

    def has_stdlib(self, language):
        return language + '_stdlib' in self.config['properties']

    def get_stdlib(self, language):
        return self.config['properties'][language + '_stdlib']

    def get_host_system(self):
        "Name of host system like 'linux', or None"
        if self.has_host():
            return self.config['host_machine']['system']
        return None

    def get_properties(self):
        return self.config['properties']

    def get_root(self):
        return self.get_properties().get('root', None)

    def get_sys_root(self):
        return self.get_properties().get('sys_root', None)

    # When compiling a cross compiler we use the native compiler for everything.
    # But not when cross compiling a cross compiler.
    def need_cross_compiler(self):
        return 'host_machine' in self.config

    def need_exe_wrapper(self):
        value = self.config['properties'].get('needs_exe_wrapper', None)
        if value is not None:
            return value
        # Can almost always run 32-bit binaries on 64-bit natively if the host
        # and build systems are the same. We don't pass any compilers to
        # detect_cpu_family() here because we always want to know the OS
        # architecture, not what the compiler environment tells us.
        if self.has_host() and detect_cpu_family({}) == 'x86_64' and \
           self.config['host_machine']['cpu_family'] == 'x86' and \
           self.config['host_machine']['system'] == detect_system():
            return False
        return True


class MachineInfo:
    def __init__(self, system, cpu_family, cpu, endian):
        self.system = system
        self.cpu_family = cpu_family
        self.cpu = cpu
        self.endian = endian
n class="hl opt">, recursive) def get_all_link_deps(self): return self.get_transitive_link_deps() @lru_cache(maxsize=None) def get_transitive_link_deps(self): result = [] for i in self.link_targets: result += i.get_all_link_deps() return result def get_link_deps_mapping(self, prefix, environment): return self.get_transitive_link_deps_mapping(prefix, environment) @lru_cache(maxsize=None) def get_transitive_link_deps_mapping(self, prefix, environment): result = {} for i in self.link_targets: mapping = i.get_link_deps_mapping(prefix, environment) #we are merging two dictionaries, while keeping the earlier one dominant result_tmp = mapping.copy() result_tmp.update(result) result = result_tmp return result @lru_cache(maxsize=None) def get_link_dep_subdirs(self): result = OrderedSet() for i in self.link_targets: result.add(i.get_subdir()) result.update(i.get_link_dep_subdirs()) return result def get_default_install_dir(self, environment): return environment.get_libdir() def get_custom_install_dir(self): return self.install_dir def get_custom_install_mode(self): return self.install_mode def process_kwargs(self, kwargs, environment): self.process_kwargs_base(kwargs) self.copy_kwargs(kwargs) kwargs.get('modules', []) self.need_install = kwargs.get('install', self.need_install) llist = extract_as_list(kwargs, 'link_with') for linktarget in llist: # Sorry for this hack. Keyword targets are kept in holders # in kwargs. Unpack here without looking at the exact type. if hasattr(linktarget, "held_object"): linktarget = linktarget.held_object if isinstance(linktarget, dependencies.ExternalLibrary): raise MesonException('''An external library was used in link_with keyword argument, which is reserved for libraries built as part of this project. External libraries must be passed using the dependencies keyword argument instead, because they are conceptually "external dependencies", just like those detected with the dependency() function.''') self.link(linktarget) lwhole = extract_as_list(kwargs, 'link_whole') for linktarget in lwhole: self.link_whole(linktarget) c_pchlist, cpp_pchlist, clist, cpplist, cudalist, cslist, valalist, objclist, objcpplist, fortranlist, rustlist \ = extract_as_list(kwargs, 'c_pch', 'cpp_pch', 'c_args', 'cpp_args', 'cuda_args', 'cs_args', 'vala_args', 'objc_args', 'objcpp_args', 'fortran_args', 'rust_args') self.add_pch('c', c_pchlist) self.add_pch('cpp', cpp_pchlist) compiler_args = {'c': clist, 'cpp': cpplist, 'cuda': cudalist, 'cs': cslist, 'vala': valalist, 'objc': objclist, 'objcpp': objcpplist, 'fortran': fortranlist, 'rust': rustlist } for key, value in compiler_args.items(): self.add_compiler_args(key, value) if not isinstance(self, Executable) or 'export_dynamic' in kwargs: self.vala_header = kwargs.get('vala_header', self.name + '.h') self.vala_vapi = kwargs.get('vala_vapi', self.name + '.vapi') self.vala_gir = kwargs.get('vala_gir', None) dlist = stringlistify(kwargs.get('d_args', [])) self.add_compiler_args('d', dlist) dfeatures = dict() dfeature_unittest = kwargs.get('d_unittest', False) if dfeature_unittest: dfeatures['unittest'] = dfeature_unittest dfeature_versions = kwargs.get('d_module_versions', []) if dfeature_versions: dfeatures['versions'] = dfeature_versions dfeature_debug = kwargs.get('d_debug', []) if dfeature_debug: dfeatures['debug'] = dfeature_debug if 'd_import_dirs' in kwargs: dfeature_import_dirs = extract_as_list(kwargs, 'd_import_dirs', unholder=True) for d in dfeature_import_dirs: if not isinstance(d, IncludeDirs): raise InvalidArguments('Arguments to d_import_dirs must be include_directories.') dfeatures['import_dirs'] = dfeature_import_dirs if dfeatures: self.d_features = dfeatures self.link_args = extract_as_list(kwargs, 'link_args') for i in self.link_args: if not isinstance(i, str): raise InvalidArguments('Link_args arguments must be strings.') for l in self.link_args: if '-Wl,-rpath' in l or l.startswith('-rpath'): mlog.warning('''Please do not define rpath with a linker argument, use install_rpath or build_rpath properties instead. This will become a hard error in a future Meson release.''') self.process_link_depends(kwargs.get('link_depends', []), environment) # Target-specific include dirs must be added BEFORE include dirs from # internal deps (added inside self.add_deps()) to override them. inclist = extract_as_list(kwargs, 'include_directories') self.add_include_dirs(inclist) # Add dependencies (which also have include_directories) deplist = extract_as_list(kwargs, 'dependencies') self.add_deps(deplist) # If an item in this list is False, the output corresponding to # the list index of that item will not be installed self.install_dir = typeslistify(kwargs.get('install_dir', [None]), (str, bool)) self.install_mode = kwargs.get('install_mode', None) main_class = kwargs.get('main_class', '') if not isinstance(main_class, str): raise InvalidArguments('Main class must be a string') self.main_class = main_class if isinstance(self, Executable): self.gui_app = kwargs.get('gui_app', False) if not isinstance(self.gui_app, bool): raise InvalidArguments('Argument gui_app must be boolean.') elif 'gui_app' in kwargs: raise InvalidArguments('Argument gui_app can only be used on executables.') extra_files = extract_as_list(kwargs, 'extra_files') for i in extra_files: assert(isinstance(i, File)) trial = os.path.join(environment.get_source_dir(), i.subdir, i.fname) if not(os.path.isfile(trial)): raise InvalidArguments('Tried to add non-existing extra file %s.' % i) self.extra_files = extra_files self.install_rpath = kwargs.get('install_rpath', '') if not isinstance(self.install_rpath, str): raise InvalidArguments('Install_rpath is not a string.') self.build_rpath = kwargs.get('build_rpath', '') if not isinstance(self.build_rpath, str): raise InvalidArguments('Build_rpath is not a string.') resources = extract_as_list(kwargs, 'resources') for r in resources: if not isinstance(r, str): raise InvalidArguments('Resource argument is not a string.') trial = os.path.join(environment.get_source_dir(), self.subdir, r) if not os.path.isfile(trial): raise InvalidArguments('Tried to add non-existing resource %s.' % r) self.resources = resources if 'name_prefix' in kwargs: name_prefix = kwargs['name_prefix'] if isinstance(name_prefix, list): if name_prefix: raise InvalidArguments('name_prefix array must be empty to signify null.') elif not isinstance(name_prefix, str): raise InvalidArguments('name_prefix must be a string.') self.prefix = name_prefix self.name_prefix_set = True if 'name_suffix' in kwargs: name_suffix = kwargs['name_suffix'] if isinstance(name_suffix, list): if name_suffix: raise InvalidArguments('name_suffix array must be empty to signify null.') else: if not isinstance(name_suffix, str): raise InvalidArguments('name_suffix must be a string.') if name_suffix == '': raise InvalidArguments('name_suffix should not be an empty string. ' 'If you want meson to use the default behaviour ' 'for each platform pass `[]` (empty array)') self.suffix = name_suffix self.name_suffix_set = True if isinstance(self, StaticLibrary): # You can't disable PIC on OS X. The compiler ignores -fno-PIC. # PIC is always on for Windows (all code is position-independent # since library loading is done differently) m = self.environment.machines[self.for_machine] if m.is_darwin() or m.is_windows(): self.pic = True else: self.pic = self._extract_pic_pie(kwargs, 'pic') if isinstance(self, Executable): # Executables must be PIE on Android if self.environment.machines[self.for_machine].is_android(): self.pie = True else: self.pie = self._extract_pic_pie(kwargs, 'pie') self.implicit_include_directories = kwargs.get('implicit_include_directories', True) if not isinstance(self.implicit_include_directories, bool): raise InvalidArguments('Implicit_include_directories must be a boolean.') self.gnu_symbol_visibility = kwargs.get('gnu_symbol_visibility', '') if not isinstance(self.gnu_symbol_visibility, str): raise InvalidArguments('GNU symbol visibility must be a string.') if self.gnu_symbol_visibility != '': permitted = ['default', 'internal', 'hidden', 'protected', 'inlineshidden'] if self.gnu_symbol_visibility not in permitted: raise InvalidArguments('GNU symbol visibility arg %s not one of: %s', self.symbol_visibility, ', '.join(permitted)) def _extract_pic_pie(self, kwargs, arg): # Check if we have -fPIC, -fpic, -fPIE, or -fpie in cflags all_flags = self.extra_args['c'] + self.extra_args['cpp'] if '-f' + arg.lower() in all_flags or '-f' + arg.upper() in all_flags: mlog.warning("Use the '{}' kwarg instead of passing '{}' manually to {!r}".format(arg, '-f' + arg, self.name)) return True val = kwargs.get(arg, False) if not isinstance(val, bool): raise InvalidArguments('Argument {} to {!r} must be boolean'.format(arg, self.name)) return val def get_filename(self): return self.filename def get_outputs(self): return self.outputs def get_extra_args(self, language): return self.extra_args.get(language, []) def get_dependencies(self, exclude=None, for_pkgconfig=False): transitive_deps = [] if exclude is None: exclude = [] for t in itertools.chain(self.link_targets, self.link_whole_targets): if t in transitive_deps or t in exclude: continue # When generating `Libs:` and `Libs.private:` lists in pkg-config # files we don't want to include static libraries that we link_whole # or are uninstalled (they're implicitly promoted to link_whole). # But we still need to include their transitive dependencies, # a static library we link_whole would itself link to a shared # library or an installed static library. if not for_pkgconfig or (not t.is_internal() and t not in self.link_whole_targets): transitive_deps.append(t) if isinstance(t, StaticLibrary): transitive_deps += t.get_dependencies(transitive_deps + exclude, for_pkgconfig) return transitive_deps def get_source_subdir(self): return self.subdir def get_sources(self): return self.sources def get_objects(self): return self.objects def get_generated_sources(self): return self.generated def should_install(self): return self.need_install def has_pch(self): return len(self.pch) > 0 def get_pch(self, language): try: return self.pch[language] except KeyError: return[] def get_include_dirs(self): return self.include_dirs def add_deps(self, deps): deps = listify(deps) for dep in deps: if hasattr(dep, 'held_object'): dep = dep.held_object if isinstance(dep, dependencies.InternalDependency): # Those parts that are internal. self.process_sourcelist(dep.sources) self.add_include_dirs(dep.include_directories, dep.get_include_type()) for l in dep.libraries: self.link(l) for l in dep.whole_libraries: self.link_whole(l) if dep.get_compile_args() or dep.get_link_args(): # Those parts that are external. extpart = dependencies.InternalDependency('undefined', [], dep.get_compile_args(), dep.get_link_args(), [], [], [], [], {}) self.external_deps.append(extpart) # Deps of deps. self.add_deps(dep.ext_deps) elif isinstance(dep, dependencies.Dependency): if dep not in self.external_deps: self.external_deps.append(dep) self.process_sourcelist(dep.get_sources()) self.add_deps(dep.ext_deps) elif isinstance(dep, BuildTarget): raise InvalidArguments('''Tried to use a build target as a dependency. You probably should put it in link_with instead.''') else: # This is a bit of a hack. We do not want Build to know anything # about the interpreter so we can't import it and use isinstance. # This should be reliable enough. if hasattr(dep, 'project_args_frozen') or hasattr(dep, 'global_args_frozen'): raise InvalidArguments('Tried to use subproject object as a dependency.\n' 'You probably wanted to use a dependency declared in it instead.\n' 'Access it by calling get_variable() on the subproject object.') raise InvalidArguments('Argument is of an unacceptable type {!r}.\nMust be ' 'either an external dependency (returned by find_library() or ' 'dependency()) or an internal dependency (returned by ' 'declare_dependency()).'.format(type(dep).__name__)) def get_external_deps(self): return self.external_deps def is_internal(self): return isinstance(self, StaticLibrary) and not self.need_install def link(self, target): for t in listify(target, unholder=True): if isinstance(self, StaticLibrary) and self.need_install and t.is_internal(): # When we're a static library and we link_with to an # internal/convenience library, promote to link_whole. return self.link_whole(t) if not isinstance(t, (Target, CustomTargetIndex)): raise InvalidArguments('{!r} is not a target.'.format(t)) if not t.is_linkable_target(): raise InvalidArguments('Link target {!r} is not linkable.'.format(t)) if isinstance(self, SharedLibrary) and isinstance(t, StaticLibrary) and not t.pic: msg = "Can't link non-PIC static library {!r} into shared library {!r}. ".format(t.name, self.name) msg += "Use the 'pic' option to static_library to build with PIC." raise InvalidArguments(msg) if self.for_machine is not t.for_machine: msg = 'Tried to mix libraries for machines {} and {} in target {!r}'.format(self.for_machine, t.for_machine, self.name) if self.environment.is_cross_build(): raise InvalidArguments(msg + ' This is not possible in a cross build.') else: mlog.warning(msg + ' This will fail in cross build.') self.link_targets.append(t) def link_whole(self, target): for t in listify(target, unholder=True): if isinstance(t, (CustomTarget, CustomTargetIndex)): if not t.is_linkable_target(): raise InvalidArguments('Custom target {!r} is not linkable.'.format(t)) if not t.get_filename().endswith('.a'): raise InvalidArguments('Can only link_whole custom targets that are .a archives.') if isinstance(self, StaticLibrary): # FIXME: We could extract the .a archive to get object files raise InvalidArguments('Cannot link_whole a custom target into a static library') elif not isinstance(t, StaticLibrary): raise InvalidArguments('{!r} is not a static library.'.format(t)) elif isinstance(self, SharedLibrary) and not t.pic: msg = "Can't link non-PIC static library {!r} into shared library {!r}. ".format(t.name, self.name) msg += "Use the 'pic' option to static_library to build with PIC." raise InvalidArguments(msg) if self.for_machine is not t.for_machine: msg = 'Tried to mix libraries for machines {1} and {2} in target {0!r}'.format(self.name, self.for_machine, t.for_machine) if self.environment.is_cross_build(): raise InvalidArguments(msg + ' This is not possible in a cross build.') else: mlog.warning(msg + ' This will fail in cross build.') if isinstance(self, StaticLibrary): # When we're a static library and we link_whole: to another static # library, we need to add that target's objects to ourselves. self.objects += t.extract_all_objects_recurse() self.link_whole_targets.append(t) def extract_all_objects_recurse(self): objs = [self.extract_all_objects()] for t in self.link_targets: if t.is_internal(): objs += t.extract_all_objects_recurse() return objs def add_pch(self, language, pchlist): if not pchlist: return elif len(pchlist) == 1: if not environment.is_header(pchlist[0]): raise InvalidArguments('PCH argument %s is not a header.' % pchlist[0]) elif len(pchlist) == 2: if environment.is_header(pchlist[0]): if not environment.is_source(pchlist[1]): raise InvalidArguments('PCH definition must contain one header and at most one source.') elif environment.is_source(pchlist[0]): if not environment.is_header(pchlist[1]): raise InvalidArguments('PCH definition must contain one header and at most one source.') pchlist = [pchlist[1], pchlist[0]] else: raise InvalidArguments('PCH argument %s is of unknown type.' % pchlist[0]) if (os.path.dirname(pchlist[0]) != os.path.dirname(pchlist[1])): raise InvalidArguments('PCH files must be stored in the same folder.') mlog.warning('PCH source files are deprecated, only a single header file should be used.') elif len(pchlist) > 2: raise InvalidArguments('PCH definition may have a maximum of 2 files.') for f in pchlist: if not isinstance(f, str): raise MesonException('PCH arguments must be strings.') if not os.path.isfile(os.path.join(self.environment.source_dir, self.subdir, f)): raise MesonException('File %s does not exist.' % f) self.pch[language] = pchlist def add_include_dirs(self, args, set_is_system: T.Optional[str] = None): ids = [] for a in args: # FIXME same hack, forcibly unpack from holder. if hasattr(a, 'held_object'): a = a.held_object if not isinstance(a, IncludeDirs): raise InvalidArguments('Include directory to be added is not an include directory object.') ids.append(a) if set_is_system is None: set_is_system = 'preserve' if set_is_system != 'preserve': is_system = set_is_system == 'system' ids = [IncludeDirs(x.get_curdir(), x.get_incdirs(), is_system, x.get_extra_build_dirs()) for x in ids] self.include_dirs += ids def add_compiler_args(self, language, args): args = listify(args) for a in args: if not isinstance(a, (str, File)): raise InvalidArguments('A non-string passed to compiler args.') if language in self.extra_args: self.extra_args[language] += args else: self.extra_args[language] = args def get_aliases(self): return {} def get_langs_used_by_deps(self) -> T.List[str]: ''' Sometimes you want to link to a C++ library that exports C API, which means the linker must link in the C++ stdlib, and we must use a C++ compiler for linking. The same is also applicable for objc/objc++, etc, so we can keep using clink_langs for the priority order. See: https://github.com/mesonbuild/meson/issues/1653 ''' langs = [] # User specified link_language of target (for multi-language targets) if self.link_language: return [self.link_language] # Check if any of the external libraries were written in this language for dep in self.external_deps: if dep.language is None: continue if dep.language not in langs: langs.append(dep.language) # Check if any of the internal libraries this target links to were # written in this language for link_target in itertools.chain(self.link_targets, self.link_whole_targets): if isinstance(link_target, (CustomTarget, CustomTargetIndex)): continue for language in link_target.compilers: if language not in langs: langs.append(language) return langs def get_clink_dynamic_linker_and_stdlibs(self): ''' We use the order of languages in `clink_langs` to determine which linker to use in case the target has sources compiled with multiple compilers. All languages other than those in this list have their own linker. Note that Vala outputs C code, so Vala sources can use any linker that can link compiled C. We don't actually need to add an exception for Vala here because of that. ''' # Populate list of all compilers, not just those being used to compile # sources in this target all_compilers = self.environment.coredata.compilers[self.for_machine] # Languages used by dependencies dep_langs = self.get_langs_used_by_deps() # Pick a compiler based on the language priority-order for l in clink_langs: if l in self.compilers or l in dep_langs: try: linker = all_compilers[l] except KeyError: raise MesonException( 'Could not get a dynamic linker for build target {!r}. ' 'Requires a linker for language "{}", but that is not ' 'a project language.'.format(self.name, l)) stdlib_args = [] added_languages = set() for dl in itertools.chain(self.compilers, dep_langs): if dl != linker.language: stdlib_args += all_compilers[dl].language_stdlib_only_link_flags() added_languages.add(dl) return linker, stdlib_args m = 'Could not get a dynamic linker for build target {!r}' raise AssertionError(m.format(self.name)) def get_using_rustc(self): if len(self.sources) > 0 and self.sources[0].fname.endswith('.rs'): return True def get_using_msvc(self): ''' Check if the dynamic linker is MSVC. Used by Executable, StaticLibrary, and SharedLibrary for deciding when to use MSVC-specific file naming and debug filenames. If at least some code is built with MSVC and the final library is linked with MSVC, we can be sure that some debug info will be generated. We only check the dynamic linker here because the static linker is guaranteed to be of the same type. Interesting cases: 1. The Vala compiler outputs C code to be compiled by whatever C compiler we're using, so all objects will still be created by the MSVC compiler. 2. If the target contains only objects, process_compilers guesses and picks the first compiler that smells right. ''' linker, _ = self.get_clink_dynamic_linker_and_stdlibs() # Mixing many languages with MSVC is not supported yet so ignore stdlibs. if linker and linker.get_id() in {'msvc', 'clang-cl', 'intel-cl', 'llvm', 'dmd', 'nvcc'}: return True return False def check_module_linking(self): ''' Warn if shared modules are linked with target: (link_with) #2865 ''' for link_target in self.link_targets: if isinstance(link_target, SharedModule): if self.environment.machines[self.for_machine].is_darwin(): raise MesonException('''target links against shared modules. This is not permitted on OSX''') else: mlog.warning('''target links against shared modules. This is not recommended as it is not supported on some platforms''') return class Generator: def __init__(self, args, kwargs): if len(args) != 1: raise InvalidArguments('Generator requires exactly one positional argument: the executable') exe = args[0] if hasattr(exe, 'held_object'): exe = exe.held_object if not isinstance(exe, (Executable, dependencies.ExternalProgram)): raise InvalidArguments('First generator argument must be an executable.') self.exe = exe self.depfile = None self.capture = False self.depends = [] self.process_kwargs(kwargs) def __repr__(self): repr_str = "<{0}: {1}>" return repr_str.format(self.__class__.__name__, self.exe) def get_exe(self): return self.exe def process_kwargs(self, kwargs): if 'arguments' not in kwargs: raise InvalidArguments('Generator must have "arguments" keyword argument.') args = kwargs['arguments'] if isinstance(args, str): args = [args] if not isinstance(args, list): raise InvalidArguments('"Arguments" keyword argument must be a string or a list of strings.') for a in args: if not isinstance(a, str): raise InvalidArguments('A non-string object in "arguments" keyword argument.') self.arglist = args if 'output' not in kwargs: raise InvalidArguments('Generator must have "output" keyword argument.') outputs = listify(kwargs['output']) for rule in outputs: if not isinstance(rule, str): raise InvalidArguments('"output" may only contain strings.') if '@BASENAME@' not in rule and '@PLAINNAME@' not in rule: raise InvalidArguments('Every element of "output" must contain @BASENAME@ or @PLAINNAME@.') if has_path_sep(rule): raise InvalidArguments('"outputs" must not contain a directory separator.') if len(outputs) > 1: for o in outputs: if '@OUTPUT@' in o: raise InvalidArguments('Tried to use @OUTPUT@ in a rule with more than one output.') self.outputs = outputs if 'depfile' in kwargs: depfile = kwargs['depfile'] if not isinstance(depfile, str): raise InvalidArguments('Depfile must be a string.') if os.path.basename(depfile) != depfile: raise InvalidArguments('Depfile must be a plain filename without a subdirectory.') self.depfile = depfile if 'capture' in kwargs: capture = kwargs['capture'] if not isinstance(capture, bool): raise InvalidArguments('Capture must be boolean.') self.capture = capture if 'depends' in kwargs: depends = listify(kwargs['depends'], unholder=True) for d in depends: if not isinstance(d, BuildTarget): raise InvalidArguments('Depends entries must be build targets.') self.depends.append(d) def get_base_outnames(self, inname): plainname = os.path.basename(inname) basename = os.path.splitext(plainname)[0] bases = [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.outputs] return bases def get_dep_outname(self, inname): if self.depfile is None: raise InvalidArguments('Tried to get dep name for rule that does not have dependency file defined.') plainname = os.path.basename(inname) basename = os.path.splitext(plainname)[0] return self.depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) def get_arglist(self, inname): plainname = os.path.basename(inname) basename = os.path.splitext(plainname)[0] return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.arglist] def is_parent_path(self, parent, trial): relpath = pathlib.PurePath(trial).relative_to(parent) return relpath.parts[0] != '..' # For subdirs we can only go "down". def process_files(self, name, files, state, preserve_path_from=None, extra_args=None): output = GeneratedList(self, state.subdir, preserve_path_from, extra_args=extra_args if extra_args is not None else []) for f in files: if isinstance(f, str): f = File.from_source_file(state.environment.source_dir, state.subdir, f) elif not isinstance(f, File): raise InvalidArguments('{} arguments must be strings or files not {!r}.'.format(name, f)) if preserve_path_from: abs_f = f.absolute_path(state.environment.source_dir, state.environment.build_dir) if not self.is_parent_path(preserve_path_from, abs_f): raise InvalidArguments('When using preserve_path_from, all input files must be in a subdirectory of the given dir.') output.add_file(f, state) return output class GeneratedList: def __init__(self, generator, subdir, preserve_path_from=None, extra_args=None): if hasattr(generator, 'held_object'): generator = generator.held_object self.generator = generator self.name = self.generator.exe self.subdir = subdir self.infilelist = [] self.outfilelist = [] self.outmap = {} self.extra_depends = [] self.depend_files = [] self.preserve_path_from = preserve_path_from self.extra_args = extra_args if extra_args is not None else [] if isinstance(generator.exe, dependencies.ExternalProgram): if not generator.exe.found(): raise InvalidArguments('Tried to use not-found external program as generator') path = generator.exe.get_path() if os.path.isabs(path): # Can only add a dependency on an external program which we # know the absolute path of self.depend_files.append(File.from_absolute_file(path)) def add_preserved_path_segment(self, infile, outfiles, state): result = [] in_abs = infile.absolute_path(state.environment.source_dir, state.environment.build_dir) assert(os.path.isabs(self.preserve_path_from)) rel = os.path.relpath(in_abs, self.preserve_path_from) path_segment = os.path.dirname(rel) for of in outfiles: result.append(os.path.join(path_segment, of)) return result def add_file(self, newfile, state): self.infilelist.append(newfile) outfiles = self.generator.get_base_outnames(newfile.fname) if self.preserve_path_from: outfiles = self.add_preserved_path_segment(newfile, outfiles, state) self.outfilelist += outfiles self.outmap[newfile] = outfiles def get_inputs(self): return self.infilelist def get_outputs(self): return self.outfilelist def get_outputs_for(self, filename): return self.outmap[filename] def get_generator(self): return self.generator def get_extra_args(self): return self.extra_args class Executable(BuildTarget): known_kwargs = known_exe_kwargs def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs): self.typename = 'executable' if 'pie' not in kwargs and 'b_pie' in environment.coredata.base_options: kwargs['pie'] = environment.coredata.base_options['b_pie'].value super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs) # Unless overridden, executables have no suffix or prefix. Except on # Windows and with C#/Mono executables where the suffix is 'exe' if not hasattr(self, 'prefix'): self.prefix = '' if not hasattr(self, 'suffix'): machine = environment.machines[for_machine] # Executable for Windows or C#/Mono if machine.is_windows() or machine.is_cygwin() or 'cs' in self.compilers: self.suffix = 'exe' elif machine.system.startswith('wasm') or machine.system == 'emscripten': self.suffix = 'js' elif ('c' in self.compilers and self.compilers['c'].get_id().startswith('arm') or 'cpp' in self.compilers and self.compilers['cpp'].get_id().startswith('arm')): self.suffix = 'axf' elif ('c' in self.compilers and self.compilers['c'].get_id().startswith('ccrx') or 'cpp' in self.compilers and self.compilers['cpp'].get_id().startswith('ccrx')): self.suffix = 'abs' else: self.suffix = environment.machines[for_machine].get_exe_suffix() self.filename = self.name if self.suffix: self.filename += '.' + self.suffix self.outputs = [self.filename] # The import library this target will generate self.import_filename = None # The import library that Visual Studio would generate (and accept) self.vs_import_filename = None # The import library that GCC would generate (and prefer) self.gcc_import_filename = None # The debugging information file this target will generate self.debug_filename = None # Check for export_dynamic self.export_dynamic = False if kwargs.get('export_dynamic'): if not isinstance(kwargs['export_dynamic'], bool): raise InvalidArguments('"export_dynamic" keyword argument must be a boolean') self.export_dynamic = True if kwargs.get('implib'): self.export_dynamic = True if self.export_dynamic and kwargs.get('implib') is False: raise InvalidArguments('"implib" keyword argument must not be false for if "export_dynamic" is true') m = environment.machines[for_machine] # If using export_dynamic, set the import library name if self.export_dynamic: implib_basename = self.name + '.exe' if not isinstance(kwargs.get('implib', False), bool): implib_basename = kwargs['implib'] if m.is_windows() or m.is_cygwin(): self.vs_import_filename = '{0}.lib'.format(implib_basename) self.gcc_import_filename = 'lib{0}.a'.format(implib_basename) if self.get_using_msvc(): self.import_filename = self.vs_import_filename else: self.import_filename = self.gcc_import_filename if m.is_windows() and ('cs' in self.compilers or self.get_using_rustc() or self.get_using_msvc()): self.debug_filename = self.name + '.pdb' # Only linkwithable if using export_dynamic self.is_linkwithable = self.export_dynamic def get_default_install_dir(self, environment): return environment.get_bindir() def description(self): '''Human friendly description of the executable''' return self.name def type_suffix(self): return "@exe" def get_import_filename(self): """ The name of the import library that will be outputted by the compiler Returns None if there is no import library required for this platform """ return self.import_filename def get_import_filenameslist(self): if self.import_filename: return [self.vs_import_filename, self.gcc_import_filename] return [] def get_debug_filename(self): """ The name of debuginfo file that will be created by the compiler Returns None if the build won't create any debuginfo file """ return self.debug_filename def is_linkable_target(self): return self.is_linkwithable class StaticLibrary(BuildTarget): known_kwargs = known_stlib_kwargs def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs): self.typename = 'static library' if 'pic' not in kwargs and 'b_staticpic' in environment.coredata.base_options: kwargs['pic'] = environment.coredata.base_options['b_staticpic'].value super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs) if 'cs' in self.compilers: raise InvalidArguments('Static libraries not supported for C#.') if 'rust' in self.compilers: # If no crate type is specified, or it's the generic lib type, use rlib if not hasattr(self, 'rust_crate_type') or self.rust_crate_type == 'lib': mlog.debug('Defaulting Rust static library target crate type to rlib') self.rust_crate_type = 'rlib' # Don't let configuration proceed with a non-static crate type elif self.rust_crate_type not in ['rlib', 'staticlib']: raise InvalidArguments('Crate type "{0}" invalid for static libraries; must be "rlib" or "staticlib"'.format(self.rust_crate_type)) # By default a static library is named libfoo.a even on Windows because # MSVC does not have a consistent convention for what static libraries # are called. The MSVC CRT uses libfoo.lib syntax but nothing else uses # it and GCC only looks for static libraries called foo.lib and # libfoo.a. However, we cannot use foo.lib because that's the same as # the import library. Using libfoo.a is ok because people using MSVC # always pass the library filename while linking anyway. if not hasattr(self, 'prefix'): self.prefix = 'lib' if not hasattr(self, 'suffix'): if 'rust' in self.compilers: if not hasattr(self, 'rust_crate_type') or self.rust_crate_type == 'rlib': # default Rust static library suffix self.suffix = 'rlib' elif self.rust_crate_type == 'staticlib': self.suffix = 'a' else: self.suffix = 'a' self.filename = self.prefix + self.name + '.' + self.suffix self.outputs = [self.filename] def get_link_deps_mapping(self, prefix, environment): return {} def get_default_install_dir(self, environment): return environment.get_static_lib_dir() def type_suffix(self): return "@sta" def process_kwargs(self, kwargs, environment): super().process_kwargs(kwargs, environment) if 'rust_crate_type' in kwargs: rust_crate_type = kwargs['rust_crate_type'] if isinstance(rust_crate_type, str): self.rust_crate_type = rust_crate_type else: raise InvalidArguments('Invalid rust_crate_type "{0}": must be a string.'.format(rust_crate_type)) def is_linkable_target(self): return True class SharedLibrary(BuildTarget): known_kwargs = known_shlib_kwargs def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs): self.typename = 'shared library' self.soversion = None self.ltversion = None # Max length 2, first element is compatibility_version, second is current_version self.darwin_versions = [] self.vs_module_defs = None # The import library this target will generate self.import_filename = None # The import library that Visual Studio would generate (and accept) self.vs_import_filename = None # The import library that GCC would generate (and prefer) self.gcc_import_filename = None # The debugging information file this target will generate self.debug_filename = None super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs) if 'rust' in self.compilers: # If no crate type is specified, or it's the generic lib type, use dylib if not hasattr(self, 'rust_crate_type') or self.rust_crate_type == 'lib': mlog.debug('Defaulting Rust dynamic library target crate type to "dylib"') self.rust_crate_type = 'dylib' # Don't let configuration proceed with a non-dynamic crate type elif self.rust_crate_type not in ['dylib', 'cdylib']: raise InvalidArguments('Crate type "{0}" invalid for dynamic libraries; must be "dylib" or "cdylib"'.format(self.rust_crate_type)) if not hasattr(self, 'prefix'): self.prefix = None if not hasattr(self, 'suffix'): self.suffix = None self.basic_filename_tpl = '{0.prefix}{0.name}.{0.suffix}' self.determine_filenames(environment) def get_link_deps_mapping(self, prefix, environment): result = {} mappings = self.get_transitive_link_deps_mapping(prefix, environment) old = get_target_macos_dylib_install_name(self) if old not in mappings: fname = self.get_filename() outdirs, _ = self.get_install_dir(self.environment) new = os.path.join(prefix, outdirs[0], fname) result.update({old: new}) mappings.update(result) return mappings def get_default_install_dir(self, environment): return environment.get_shared_lib_dir() def determine_filenames(self, env): """ See https://github.com/mesonbuild/meson/pull/417 for details. First we determine the filename template (self.filename_tpl), then we set the output filename (self.filename). The template is needed while creating aliases (self.get_aliases), which are needed while generating .so shared libraries for Linux. Besides this, there's also the import library name, which is only used on Windows since on that platform the linker uses a separate library called the "import library" during linking instead of the shared library (DLL). The toolchain will output an import library in one of two formats: GCC or Visual Studio. When we're building with Visual Studio, the import library that will be generated by the toolchain is self.vs_import_filename, and with MinGW/GCC, it's self.gcc_import_filename. self.import_filename will always contain the import library name this target will generate. """ prefix = '' suffix = '' create_debug_file = False self.filename_tpl = self.basic_filename_tpl # NOTE: manual prefix/suffix override is currently only tested for C/C++ # C# and Mono if 'cs' in self.compilers: prefix = '' suffix = 'dll' self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' create_debug_file = True # C, C++, Swift, Vala # Only Windows uses a separate import library for linking # For all other targets/platforms import_filename stays None elif env.machines[self.for_machine].is_windows(): suffix = 'dll' self.vs_import_filename = '{0}{1}.lib'.format(self.prefix if self.prefix is not None else '', self.name) self.gcc_import_filename = '{0}{1}.dll.a'.format(self.prefix if self.prefix is not None else 'lib', self.name) if self.get_using_rustc(): # Shared library is of the form foo.dll prefix = '' # Import library is called foo.dll.lib self.import_filename = '{0}.dll.lib'.format(self.name) create_debug_file = True elif self.get_using_msvc(): # Shared library is of the form foo.dll prefix = '' # Import library is called foo.lib self.import_filename = self.vs_import_filename create_debug_file = True # Assume GCC-compatible naming else: # Shared library is of the form libfoo.dll prefix = 'lib' # Import library is called libfoo.dll.a self.import_filename = self.gcc_import_filename # Shared library has the soversion if it is defined if self.soversion: self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' else: self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' elif env.machines[self.for_machine].is_cygwin(): suffix = 'dll' self.gcc_import_filename = '{0}{1}.dll.a'.format(self.prefix if self.prefix is not None else 'lib', self.name) # Shared library is of the form cygfoo.dll # (ld --dll-search-prefix=cyg is the default) prefix = 'cyg' # Import library is called libfoo.dll.a self.import_filename = self.gcc_import_filename if self.soversion: self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' else: self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' elif env.machines[self.for_machine].is_darwin(): prefix = 'lib' suffix = 'dylib' # On macOS, the filename can only contain the major version if self.soversion: # libfoo.X.dylib self.filename_tpl = '{0.prefix}{0.name}.{0.soversion}.{0.suffix}' else: # libfoo.dylib self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' elif env.machines[self.for_machine].is_android(): prefix = 'lib' suffix = 'so' # Android doesn't support shared_library versioning self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' else: prefix = 'lib' suffix = 'so' if self.ltversion: # libfoo.so.X[.Y[.Z]] (.Y and .Z are optional) self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.ltversion}' elif self.soversion: # libfoo.so.X self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.soversion}' else: # No versioning, libfoo.so self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' if self.prefix is None: self.prefix = prefix if self.suffix is None: self.suffix = suffix self.filename = self.filename_tpl.format(self) self.outputs = [self.filename] if create_debug_file: self.debug_filename = os.path.splitext(self.filename)[0] + '.pdb' @staticmethod def _validate_darwin_versions(darwin_versions): try: if isinstance(darwin_versions, int): darwin_versions = str(darwin_versions) if isinstance(darwin_versions, str): darwin_versions = 2 * [darwin_versions] if not isinstance(darwin_versions, list): raise InvalidArguments('Shared library darwin_versions: must be a string, integer,' 'or a list, not {!r}'.format(darwin_versions)) if len(darwin_versions) > 2: raise InvalidArguments('Shared library darwin_versions: list must contain 2 or fewer elements') if len(darwin_versions) == 1: darwin_versions = 2 * darwin_versions for i, v in enumerate(darwin_versions[:]): if isinstance(v, int): v = str(v) if not isinstance(v, str): raise InvalidArguments('Shared library darwin_versions: list elements ' 'must be strings or integers, not {!r}'.format(v)) if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', v): raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z where ' 'X, Y, Z are numbers, and Y and Z are optional') parts = v.split('.') if len(parts) in (1, 2, 3) and int(parts[0]) > 65535: raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z ' 'where X is [0, 65535] and Y, Z are optional') if len(parts) in (2, 3) and int(parts[1]) > 255: raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z ' 'where Y is [0, 255] and Y, Z are optional') if len(parts) == 3 and int(parts[2]) > 255: raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z ' 'where Z is [0, 255] and Y, Z are optional') darwin_versions[i] = v except ValueError: raise InvalidArguments('Shared library darwin_versions: value is invalid') return darwin_versions def process_kwargs(self, kwargs, environment): super().process_kwargs(kwargs, environment) if not self.environment.machines[self.for_machine].is_android(): supports_versioning = True else: supports_versioning = False if supports_versioning: # Shared library version if 'version' in kwargs: self.ltversion = kwargs['version'] if not isinstance(self.ltversion, str): raise InvalidArguments('Shared library version needs to be a string, not ' + type(self.ltversion).__name__) if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', self.ltversion): raise InvalidArguments('Invalid Shared library version "{0}". Must be of the form X.Y.Z where all three are numbers. Y and Z are optional.'.format(self.ltversion)) # Try to extract/deduce the soversion if 'soversion' in kwargs: self.soversion = kwargs['soversion'] if isinstance(self.soversion, int): self.soversion = str(self.soversion) if not isinstance(self.soversion, str): raise InvalidArguments('Shared library soversion is not a string or integer.') elif self.ltversion: # library version is defined, get the soversion from that # We replicate what Autotools does here and take the first # number of the version by default. self.soversion = self.ltversion.split('.')[0] # macOS, iOS and tvOS dylib compatibility_version and current_version if 'darwin_versions' in kwargs: self.darwin_versions = self._validate_darwin_versions(kwargs['darwin_versions']) elif self.soversion: # If unspecified, pick the soversion self.darwin_versions = 2 * [self.soversion] # Visual Studio module-definitions file if 'vs_module_defs' in kwargs: path = kwargs['vs_module_defs'] if hasattr(path, 'held_object'): path = path.held_object if isinstance(path, str): if os.path.isabs(path): self.vs_module_defs = File.from_absolute_file(path) else: self.vs_module_defs = File.from_source_file(environment.source_dir, self.subdir, path) self.link_depends.append(self.vs_module_defs) elif isinstance(path, File): # When passing a generated file. self.vs_module_defs = path self.link_depends.append(path) elif hasattr(path, 'get_filename'): # When passing output of a Custom Target path = File.from_built_file(path.subdir, path.get_filename()) self.vs_module_defs = path self.link_depends.append(path) else: raise InvalidArguments( 'Shared library vs_module_defs must be either a string, ' 'a file object or a Custom Target') if 'rust_crate_type' in kwargs: rust_crate_type = kwargs['rust_crate_type'] if isinstance(rust_crate_type, str): self.rust_crate_type = rust_crate_type else: raise InvalidArguments('Invalid rust_crate_type "{0}": must be a string.'.format(rust_crate_type)) def get_import_filename(self): """ The name of the import library that will be outputted by the compiler Returns None if there is no import library required for this platform """ return self.import_filename def get_debug_filename(self): """ The name of debuginfo file that will be created by the compiler Returns None if the build won't create any debuginfo file """ return self.debug_filename def get_import_filenameslist(self): if self.import_filename: return [self.vs_import_filename, self.gcc_import_filename] return [] def get_all_link_deps(self): return [self] + self.get_transitive_link_deps() def get_aliases(self): """ If the versioned library name is libfoo.so.0.100.0, aliases are: * libfoo.so.0 (soversion) -> libfoo.so.0.100.0 * libfoo.so (unversioned; for linking) -> libfoo.so.0 Same for dylib: * libfoo.dylib (unversioned; for linking) -> libfoo.0.dylib """ aliases = {} # Aliases are only useful with .so and .dylib libraries. Also if # there's no self.soversion (no versioning), we don't need aliases. if self.suffix not in ('so', 'dylib') or not self.soversion: return {} # With .so libraries, the minor and micro versions are also in the # filename. If ltversion != soversion we create an soversion alias: # libfoo.so.0 -> libfoo.so.0.100.0 # Where libfoo.so.0.100.0 is the actual library if self.suffix == 'so' and self.ltversion and self.ltversion != self.soversion: alias_tpl = self.filename_tpl.replace('ltversion', 'soversion') ltversion_filename = alias_tpl.format(self) aliases[ltversion_filename] = self.filename # libfoo.so.0/libfoo.0.dylib is the actual library else: ltversion_filename = self.filename # Unversioned alias: # libfoo.so -> libfoo.so.0 # libfoo.dylib -> libfoo.0.dylib aliases[self.basic_filename_tpl.format(self)] = ltversion_filename return aliases def type_suffix(self): return "@sha" def is_linkable_target(self): return True # A shared library that is meant to be used with dlopen rather than linking # into something else. class SharedModule(SharedLibrary): known_kwargs = known_shmod_kwargs def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs): if 'version' in kwargs: raise MesonException('Shared modules must not specify the version kwarg.') if 'soversion' in kwargs: raise MesonException('Shared modules must not specify the soversion kwarg.') super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs) self.typename = 'shared module' def get_default_install_dir(self, environment): return environment.get_shared_module_dir() class CustomTarget(Target): known_kwargs = set([ 'input', 'output', 'command', 'capture', 'install', 'install_dir', 'install_mode', 'build_always', 'build_always_stale', 'depends', 'depend_files', 'depfile', 'build_by_default', 'override_options', 'console', ]) def __init__(self, name, subdir, subproject, kwargs, absolute_paths=False, backend=None): self.typename = 'custom' # TODO expose keyword arg to make MachineChoice.HOST configurable super().__init__(name, subdir, subproject, False, MachineChoice.HOST) self.dependencies = [] self.extra_depends = [] self.depend_files = [] # Files that this target depends on but are not on the command line. self.depfile = None self.process_kwargs(kwargs, backend) self.extra_files = [] # Whether to use absolute paths for all files on the commandline self.absolute_paths = absolute_paths unknowns = [] for k in kwargs: if k not in CustomTarget.known_kwargs: unknowns.append(k) if len(unknowns) > 0: mlog.warning('Unknown keyword arguments in target %s: %s' % (self.name, ', '.join(unknowns))) def get_default_install_dir(self, environment): return None def __repr__(self): repr_str = "<{0} {1}: {2}>" return repr_str.format(self.__class__.__name__, self.get_id(), self.command) def get_target_dependencies(self): deps = self.dependencies[:] deps += self.extra_depends for c in self.sources: if hasattr(c, 'held_object'): c = c.held_object if isinstance(c, (BuildTarget, CustomTarget)): deps.append(c) return deps def get_transitive_build_target_deps(self): ''' Recursively fetch the build targets that this custom target depends on, whether through `command:`, `depends:`, or `sources:` The recursion is only performed on custom targets. This is useful for setting PATH on Windows for finding required DLLs. F.ex, if you have a python script that loads a C module that links to other DLLs in your project. ''' bdeps = set() deps = self.get_target_dependencies() for d in deps: if isinstance(d, BuildTarget): bdeps.add(d) elif isinstance(d, CustomTarget): bdeps.update(d.get_transitive_build_target_deps()) return bdeps def flatten_command(self, cmd): cmd = listify(cmd, unholder=True) final_cmd = [] for c in cmd: if isinstance(c, str): final_cmd.append(c) elif isinstance(c, File): self.depend_files.append(c) final_cmd.append(c) elif isinstance(c, dependencies.ExternalProgram): if not c.found(): raise InvalidArguments('Tried to use not-found external program in "command"') path = c.get_path() if os.path.isabs(path): # Can only add a dependency on an external program which we # know the absolute path of self.depend_files.append(File.from_absolute_file(path)) final_cmd += c.get_command() elif isinstance(c, (BuildTarget, CustomTarget)): self.dependencies.append(c) final_cmd.append(c) elif isinstance(c, list): final_cmd += self.flatten_command(c) else: raise InvalidArguments('Argument {!r} in "command" is invalid'.format(c)) return final_cmd def process_kwargs(self, kwargs, backend): self.process_kwargs_base(kwargs) self.sources = extract_as_list(kwargs, 'input', unholder=True) if 'output' not in kwargs: raise InvalidArguments('Missing keyword argument "output".') self.outputs = listify(kwargs['output']) # This will substitute values from the input into output and return it. inputs = get_sources_string_names(self.sources, backend) values = get_filenames_templates_dict(inputs, []) for i in self.outputs: if not(isinstance(i, str)): raise InvalidArguments('Output argument not a string.') if i == '': raise InvalidArguments('Output must not be empty.') if i.strip() == '': raise InvalidArguments('Output must not consist only of whitespace.') if has_path_sep(i): raise InvalidArguments('Output {!r} must not contain a path segment.'.format(i)) if '@INPUT@' in i or '@INPUT0@' in i: m = 'Output cannot contain @INPUT@ or @INPUT0@, did you ' \ 'mean @PLAINNAME@ or @BASENAME@?' raise InvalidArguments(m) # We already check this during substitution, but the error message # will be unclear/confusing, so check it here. if len(inputs) != 1 and ('@PLAINNAME@' in i or '@BASENAME@' in i): m = "Output cannot contain @PLAINNAME@ or @BASENAME@ when " \ "there is more than one input (we can't know which to use)" raise InvalidArguments(m) self.outputs = substitute_values(self.outputs, values) self.capture = kwargs.get('capture', False) if self.capture and len(self.outputs) != 1: raise InvalidArguments('Capturing can only output to a single file.') self.console = kwargs.get('console', False) if not isinstance(self.console, bool): raise InvalidArguments('"console" kwarg only accepts booleans') if self.capture and self.console: raise InvalidArguments("Can't both capture output and output to console") if 'command' not in kwargs: raise InvalidArguments('Missing keyword argument "command".') if 'depfile' in kwargs: depfile = kwargs['depfile'] if not isinstance(depfile, str): raise InvalidArguments('Depfile must be a string.') if os.path.basename(depfile) != depfile: raise InvalidArguments('Depfile must be a plain filename without a subdirectory.') self.depfile = depfile self.command = self.flatten_command(kwargs['command']) if self.capture: for c in self.command: if isinstance(c, str) and '@OUTPUT@' in c: raise InvalidArguments('@OUTPUT@ is not allowed when capturing output.') if 'install' in kwargs: self.install = kwargs['install'] if not isinstance(self.install, bool): raise InvalidArguments('"install" must be boolean.') if self.install: if 'install_dir' not in kwargs: raise InvalidArguments('"install_dir" must be specified ' 'when installing a target') if isinstance(kwargs['install_dir'], list): FeatureNew('multiple install_dir for custom_target', '0.40.0').use(self.subproject) # If an item in this list is False, the output corresponding to # the list index of that item will not be installed self.install_dir = typeslistify(kwargs['install_dir'], (str, bool)) self.install_mode = kwargs.get('install_mode', None) else: self.install = False self.install_dir = [None] self.install_mode = None if 'build_always' in kwargs and 'build_always_stale' in kwargs: raise InvalidArguments('build_always and build_always_stale are mutually exclusive. Combine build_by_default and build_always_stale.') elif 'build_always' in kwargs: mlog.deprecation('build_always is deprecated. Combine build_by_default and build_always_stale instead.') if 'build_by_default' not in kwargs: self.build_by_default = kwargs['build_always'] self.build_always_stale = kwargs['build_always'] elif 'build_always_stale' in kwargs: self.build_always_stale = kwargs['build_always_stale'] if not isinstance(self.build_always_stale, bool): raise InvalidArguments('Argument build_always_stale must be a boolean.') extra_deps, depend_files = extract_as_list(kwargs, 'depends', 'depend_files', pop = False) for ed in extra_deps: while hasattr(ed, 'held_object'): ed = ed.held_object if not isinstance(ed, (CustomTarget, BuildTarget)): raise InvalidArguments('Can only depend on toplevel targets: custom_target or build_target (executable or a library) got: %s(%s)' % (type(ed), ed)) self.extra_depends.append(ed) for i in depend_files: if isinstance(i, (File, str)): self.depend_files.append(i) else: mlog.debug(i) raise InvalidArguments('Unknown type {!r} in depend_files.'.format(type(i).__name__)) def get_dependencies(self): return self.dependencies def should_install(self): return self.install def get_custom_install_dir(self): return self.install_dir def get_custom_install_mode(self): return self.install_mode def get_outputs(self): return self.outputs def get_filename(self): return self.outputs[0] def get_sources(self): return self.sources def get_generated_lists(self): genlists = [] for c in self.sources: if hasattr(c, 'held_object'): c = c.held_object if isinstance(c, GeneratedList): genlists.append(c) return genlists def get_generated_sources(self): return self.get_generated_lists() def get_dep_outname(self, infilenames): if self.depfile is None: raise InvalidArguments('Tried to get depfile name for custom_target that does not have depfile defined.') if len(infilenames): plainname = os.path.basename(infilenames[0]) basename = os.path.splitext(plainname)[0] return self.depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) else: if '@BASENAME@' in self.depfile or '@PLAINNAME@' in self.depfile: raise InvalidArguments('Substitution in depfile for custom_target that does not have an input file.') return self.depfile def is_linkable_target(self): if len(self.outputs) != 1: return False suf = os.path.splitext(self.outputs[0])[-1] if suf == '.a' or suf == '.dll' or suf == '.lib' or suf == '.so': return True def get_link_deps_mapping(self, prefix, environment): return {} def get_link_dep_subdirs(self): return OrderedSet() def get_all_link_deps(self): return [] def type_suffix(self): return "@cus" def __getitem__(self, index): return CustomTargetIndex(self, self.outputs[index]) def __setitem__(self, index, value): raise NotImplementedError def __delitem__(self, index): raise NotImplementedError def __iter__(self): for i in self.outputs: yield CustomTargetIndex(self, i) class RunTarget(Target): def __init__(self, name, command, args, dependencies, subdir, subproject): self.typename = 'run' # These don't produce output artifacts super().__init__(name, subdir, subproject, False, MachineChoice.BUILD) self.command = command self.args = args self.dependencies = dependencies def __repr__(self): repr_str = "<{0} {1}: {2}>" return repr_str.format(self.__class__.__name__, self.get_id(), self.command) def process_kwargs(self, kwargs): return self.process_kwargs_base(kwargs) def get_dependencies(self): return self.dependencies def get_generated_sources(self): return [] def get_sources(self): return [] def should_install(self): return False def get_filename(self): return self.name def get_outputs(self): if isinstance(self.name, str): return [self.name] elif isinstance(self.name, list): return self.name else: raise RuntimeError('RunTarget: self.name is neither a list nor a string. This is a bug') def type_suffix(self): return "@run" class AliasTarget(RunTarget): def __init__(self, name, dependencies, subdir, subproject): super().__init__(name, '', [], dependencies, subdir, subproject) class Jar(BuildTarget): known_kwargs = known_jar_kwargs def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs): self.typename = 'jar' super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs) for s in self.sources: if not s.endswith('.java'): raise InvalidArguments('Jar source %s is not a java file.' % s) for t in self.link_targets: if not isinstance(t, Jar): raise InvalidArguments('Link target %s is not a jar target.' % t) self.filename = self.name + '.jar' self.outputs = [self.filename] self.java_args = kwargs.get('java_args', []) def get_main_class(self): return self.main_class def type_suffix(self): return "@jar" def get_java_args(self): return self.java_args def validate_install(self, environment): # All jar targets are installable. pass def is_linkable_target(self): return True def get_classpath_args(self): cp_paths = [os.path.join(l.get_subdir(), l.get_filename()) for l in self.link_targets] cp_string = os.pathsep.join(cp_paths) if cp_string: return ['-cp', os.pathsep.join(cp_paths)] return [] class CustomTargetIndex: """A special opaque object returned by indexing a CustomTarget. This object exists in meson, but acts as a proxy in the backends, making targets depend on the CustomTarget it's derived from, but only adding one source file to the sources. """ def __init__(self, target, output): self.typename = 'custom' self.target = target self.output = output self.for_machine = target.for_machine def __repr__(self): return '<CustomTargetIndex: {!r}[{}]>'.format( self.target, self.target.get_outputs().index(self.output)) def get_outputs(self): return [self.output] def get_subdir(self): return self.target.get_subdir() def get_filename(self): return self.output def get_id(self): return self.target.get_id() def get_all_link_deps(self): return self.target.get_all_link_deps() def get_link_deps_mapping(self, prefix, environment): return self.target.get_link_deps_mapping(prefix, environment) def get_link_dep_subdirs(self): return self.target.get_link_dep_subdirs() def is_linkable_target(self): suf = os.path.splitext(self.output)[-1] if suf == '.a' or suf == '.dll' or suf == '.lib' or suf == '.so': return True class ConfigureFile: def __init__(self, subdir, sourcename, targetname, configuration_data): self.subdir = subdir self.sourcename = sourcename self.targetname = targetname self.configuration_data = configuration_data def __repr__(self): repr_str = "<{0}: {1} -> {2}>" src = os.path.join(self.subdir, self.sourcename) dst = os.path.join(self.subdir, self.targetname) return repr_str.format(self.__class__.__name__, src, dst) def get_configuration_data(self): return self.configuration_data def get_subdir(self): return self.subdir def get_source_name(self): return self.sourcename def get_target_name(self): return self.targetname class ConfigurationData: def __init__(self): super().__init__() self.values = {} def __repr__(self): return repr(self.values) def __contains__(self, value): return value in self.values def get(self, name): return self.values[name] # (val, desc) def keys(self): return self.values.keys() # A bit poorly named, but this represents plain data files to copy # during install. class Data: def __init__(self, sources, install_dir, install_mode=None, rename=None): self.sources = sources self.install_dir = install_dir self.install_mode = install_mode self.sources = listify(self.sources) for s in self.sources: assert(isinstance(s, File)) if rename is None: self.rename = [os.path.basename(f.fname) for f in self.sources] else: self.rename = stringlistify(rename) if len(self.rename) != len(self.sources): raise MesonException('Size of rename argument is different from number of sources') class RunScript(dict): def __init__(self, script, args): super().__init__() assert(isinstance(script, list)) assert(isinstance(args, list)) self['exe'] = script self['args'] = args class TestSetup: def __init__(self, exe_wrapper: T.Optional[T.List[str]], gdb: bool, timeout_multiplier: int, env: EnvironmentVariables): self.exe_wrapper = exe_wrapper self.gdb = gdb self.timeout_multiplier = timeout_multiplier self.env = env def get_sources_string_names(sources, backend): ''' For the specified list of @sources which can be strings, Files, or targets, get all the output basenames. ''' names = [] for s in sources: if hasattr(s, 'held_object'): s = s.held_object if isinstance(s, str): names.append(s) elif isinstance(s, (BuildTarget, CustomTarget, CustomTargetIndex, GeneratedList)): names += s.get_outputs() elif isinstance(s, ExtractedObjects): names += s.get_outputs(backend) elif isinstance(s, File): names.append(s.fname) else: raise AssertionError('Unknown source type: {!r}'.format(s)) return names def load(build_dir: str) -> Build: filename = os.path.join(build_dir, 'meson-private', 'build.dat') load_fail_msg = 'Build data file {!r} is corrupted. Try with a fresh build tree.'.format(filename) nonexisting_fail_msg = 'No such build data file as "{!r}".'.format(filename) try: with open(filename, 'rb') as f: obj = pickle.load(f) except FileNotFoundError: raise MesonException(nonexisting_fail_msg) except (pickle.UnpicklingError, EOFError): raise MesonException(load_fail_msg) except AttributeError: raise MesonException( "Build data file {!r} references functions or classes that don't " "exist. This probably means that it was generated with an old " "version of meson. Try running from the source directory " "meson {} --wipe".format(filename, build_dir)) if not isinstance(obj, Build): raise MesonException(load_fail_msg) return obj def save(obj, filename): with open(filename, 'wb') as f: pickle.dump(obj, f)